Newer
Older
cortex-hub / ai-hub / app / config.py
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()