import os import yaml from dotenv import load_dotenv # Load .env file at the module level load_dotenv() def _load_yaml_config(path: str) -> dict: """Loads a YAML config file if it exists, otherwise returns an empty dict.""" if os.path.exists(path): print(f"✅ Loading configuration from {path}") with open(path, 'r') as f: return yaml.safe_load(f) or {} print(f"⚠️ '{path}' not found. Using environment variables and defaults.") return {} class Settings: """ Holds all application settings, calculated once on initialization. Priority: YAML > Environment Variables > Defaults. """ def __init__(self): # 1. Load the YAML configuration data config_path = os.getenv("CONFIG_PATH", "config.yaml") yaml_config = _load_yaml_config(config_path) # 2. Define a helper to safely get nested values from the loaded YAML def get_from_yaml(keys): d = yaml_config for key in keys: d = d.get(key) if isinstance(d, dict) else None return d # 3. Set all attributes with the correct priority # --- Application --- self.PROJECT_NAME: str = get_from_yaml(["application", "project_name"]) or \ os.getenv("PROJECT_NAME") or \ "Cortex Hub" self.VERSION: str = get_from_yaml(["application", "version"]) or "1.0.0" # --- Database --- self.DB_MODE: str = get_from_yaml(["database", "mode"]) or \ os.getenv("DB_MODE") or \ "sqlite" if self.DB_MODE == "sqlite": self.DATABASE_URL: str = "sqlite:///./data/ai_hub.db" else: self.DATABASE_URL: str = get_from_yaml(["database", "url"]) or \ os.getenv("DATABASE_URL") or \ "postgresql://user:password@localhost/ai_hub_db" # --- LLM API Keys & Models (Secrets should come from ENV) --- self.DEEPSEEK_API_KEY: str = os.getenv("DEEPSEEK_API_KEY") self.GEMINI_API_KEY: str = os.getenv("GEMINI_API_KEY") # Fail fast if required secrets are missing or empty if not self.DEEPSEEK_API_KEY: raise ValueError("DEEPSEEK_API_KEY is not set or is empty in the environment.") if not self.GEMINI_API_KEY: raise ValueError("GEMINI_API_KEY is not set or is empty in the environment.") self.DEEPSEEK_MODEL_NAME: str = get_from_yaml(["llm_providers", "deepseek_model_name"]) or \ os.getenv("DEEPSEEK_MODEL_NAME") or \ "deepseek-chat" self.GEMINI_MODEL_NAME: str = get_from_yaml(["llm_providers", "gemini_model_name"]) or \ os.getenv("GEMINI_MODEL_NAME") or \ "gemini-1.5-flash-latest" # --- Vector Store --- self.FAISS_INDEX_PATH: str = get_from_yaml(["vector_store", "index_path"]) or \ os.getenv("FAISS_INDEX_PATH") or \ "data/faiss_index.bin" dimension_str = get_from_yaml(["vector_store", "embedding_dimension"]) or \ os.getenv("EMBEDDING_DIMENSION") or \ "768" self.EMBEDDING_DIMENSION: int = int(dimension_str) # Instantiate a single, importable settings object for the entire application. # The __init__ method runs once, and all values are now set. settings = Settings()