diff --git a/ai-hub/app/config.py b/ai-hub/app/config.py index 8bf3780..82ad185 100644 --- a/ai-hub/app/config.py +++ b/ai-hub/app/config.py @@ -34,7 +34,7 @@ class Settings: """ Holds all application settings, validated and structured by Pydantic. - Priority Order: YAML File > Environment Variables > Pydantic Defaults + Priority Order: Environment Variables > YAML File > Pydantic Defaults """ def __init__(self): # Load base configuration from YAML @@ -50,7 +50,6 @@ # Parse the loaded data to get defaults for any missing sections config_from_pydantic = AppConfig.parse_obj(yaml_data) - # --- **CRITICAL FIX**: Apply the correct priority logic --- # Helper to safely get nested values from the raw YAML dict def get_from_yaml(keys): d = yaml_data @@ -58,21 +57,21 @@ d = d.get(key) if isinstance(d, dict) else None return d - # --- Set final attributes with YAML > ENV > Default priority --- - self.PROJECT_NAME: str = get_from_yaml(["application", "project_name"]) or \ - os.getenv("PROJECT_NAME") or \ + # --- Set final attributes with ENV > YAML > Default priority --- + self.PROJECT_NAME: str = os.getenv("PROJECT_NAME") or \ + get_from_yaml(["application", "project_name"]) or \ config_from_pydantic.application.project_name self.VERSION: str = config_from_pydantic.application.version - self.DB_MODE: str = get_from_yaml(["database", "mode"]) or \ - os.getenv("DB_MODE") or \ + self.DB_MODE: str = os.getenv("DB_MODE") or \ + get_from_yaml(["database", "mode"]) or \ config_from_pydantic.database.mode 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 \ + self.DATABASE_URL: str = os.getenv("DATABASE_URL") or \ + get_from_yaml(["database", "url"]) or \ config_from_pydantic.database.url self.DEEPSEEK_API_KEY: str = os.getenv("DEEPSEEK_API_KEY") @@ -80,22 +79,22 @@ if not self.DEEPSEEK_API_KEY or not self.GEMINI_API_KEY: raise ValueError("API keys must be set in the environment.") - self.DEEPSEEK_MODEL_NAME: str = get_from_yaml(["llm_providers", "deepseek_model_name"]) or \ - os.getenv("DEEPSEEK_MODEL_NAME") or \ + self.DEEPSEEK_MODEL_NAME: str = os.getenv("DEEPSEEK_MODEL_NAME") or \ + get_from_yaml(["llm_providers", "deepseek_model_name"]) or \ config_from_pydantic.llm_providers.deepseek_model_name - self.GEMINI_MODEL_NAME: str = get_from_yaml(["llm_providers", "gemini_model_name"]) or \ - os.getenv("GEMINI_MODEL_NAME") or \ - config_from_pydantic.llm_providers.gemini_model_name + self.GEMINI_MODEL_NAME: str = os.getenv("GEMINI_MODEL_NAME") or \ + get_from_yaml(["llm_providers", "gemini_model_name"]) or \ + config_from_pydantic.llm_providers.gemini_model_name - self.FAISS_INDEX_PATH: str = get_from_yaml(["vector_store", "index_path"]) or \ - os.getenv("FAISS_INDEX_PATH") or \ + self.FAISS_INDEX_PATH: str = os.getenv("FAISS_INDEX_PATH") or \ + get_from_yaml(["vector_store", "index_path"]) or \ config_from_pydantic.vector_store.index_path - dimension_str = get_from_yaml(["vector_store", "embedding_dimension"]) or \ - os.getenv("EMBEDDING_DIMENSION") or \ + dimension_str = os.getenv("EMBEDDING_DIMENSION") or \ + get_from_yaml(["vector_store", "embedding_dimension"]) or \ config_from_pydantic.vector_store.embedding_dimension self.EMBEDDING_DIMENSION: int = int(dimension_str) # Instantiate the single settings object for the application -settings = Settings() \ No newline at end of file +settings = Settings() diff --git a/ai-hub/app/config.py b/ai-hub/app/config.py index 8bf3780..82ad185 100644 --- a/ai-hub/app/config.py +++ b/ai-hub/app/config.py @@ -34,7 +34,7 @@ class Settings: """ Holds all application settings, validated and structured by Pydantic. - Priority Order: YAML File > Environment Variables > Pydantic Defaults + Priority Order: Environment Variables > YAML File > Pydantic Defaults """ def __init__(self): # Load base configuration from YAML @@ -50,7 +50,6 @@ # Parse the loaded data to get defaults for any missing sections config_from_pydantic = AppConfig.parse_obj(yaml_data) - # --- **CRITICAL FIX**: Apply the correct priority logic --- # Helper to safely get nested values from the raw YAML dict def get_from_yaml(keys): d = yaml_data @@ -58,21 +57,21 @@ d = d.get(key) if isinstance(d, dict) else None return d - # --- Set final attributes with YAML > ENV > Default priority --- - self.PROJECT_NAME: str = get_from_yaml(["application", "project_name"]) or \ - os.getenv("PROJECT_NAME") or \ + # --- Set final attributes with ENV > YAML > Default priority --- + self.PROJECT_NAME: str = os.getenv("PROJECT_NAME") or \ + get_from_yaml(["application", "project_name"]) or \ config_from_pydantic.application.project_name self.VERSION: str = config_from_pydantic.application.version - self.DB_MODE: str = get_from_yaml(["database", "mode"]) or \ - os.getenv("DB_MODE") or \ + self.DB_MODE: str = os.getenv("DB_MODE") or \ + get_from_yaml(["database", "mode"]) or \ config_from_pydantic.database.mode 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 \ + self.DATABASE_URL: str = os.getenv("DATABASE_URL") or \ + get_from_yaml(["database", "url"]) or \ config_from_pydantic.database.url self.DEEPSEEK_API_KEY: str = os.getenv("DEEPSEEK_API_KEY") @@ -80,22 +79,22 @@ if not self.DEEPSEEK_API_KEY or not self.GEMINI_API_KEY: raise ValueError("API keys must be set in the environment.") - self.DEEPSEEK_MODEL_NAME: str = get_from_yaml(["llm_providers", "deepseek_model_name"]) or \ - os.getenv("DEEPSEEK_MODEL_NAME") or \ + self.DEEPSEEK_MODEL_NAME: str = os.getenv("DEEPSEEK_MODEL_NAME") or \ + get_from_yaml(["llm_providers", "deepseek_model_name"]) or \ config_from_pydantic.llm_providers.deepseek_model_name - self.GEMINI_MODEL_NAME: str = get_from_yaml(["llm_providers", "gemini_model_name"]) or \ - os.getenv("GEMINI_MODEL_NAME") or \ - config_from_pydantic.llm_providers.gemini_model_name + self.GEMINI_MODEL_NAME: str = os.getenv("GEMINI_MODEL_NAME") or \ + get_from_yaml(["llm_providers", "gemini_model_name"]) or \ + config_from_pydantic.llm_providers.gemini_model_name - self.FAISS_INDEX_PATH: str = get_from_yaml(["vector_store", "index_path"]) or \ - os.getenv("FAISS_INDEX_PATH") or \ + self.FAISS_INDEX_PATH: str = os.getenv("FAISS_INDEX_PATH") or \ + get_from_yaml(["vector_store", "index_path"]) or \ config_from_pydantic.vector_store.index_path - dimension_str = get_from_yaml(["vector_store", "embedding_dimension"]) or \ - os.getenv("EMBEDDING_DIMENSION") or \ + dimension_str = os.getenv("EMBEDDING_DIMENSION") or \ + get_from_yaml(["vector_store", "embedding_dimension"]) or \ config_from_pydantic.vector_store.embedding_dimension self.EMBEDDING_DIMENSION: int = int(dimension_str) # Instantiate the single settings object for the application -settings = Settings() \ No newline at end of file +settings = Settings() diff --git a/ai-hub/tests/test_config.py b/ai-hub/tests/test_config.py index ae6a642..135fcc7 100644 --- a/ai-hub/tests/test_config.py +++ b/ai-hub/tests/test_config.py @@ -14,20 +14,22 @@ yaml.dump(config_content, f) return str(config_path) -def test_yaml_overrides_env_var(monkeypatch, tmp_config_file): +def test_env_var_overrides_yaml(monkeypatch, tmp_config_file): """ - Tests that a value from the YAML file has priority over an environment variable. + Tests that a value from an environment variable has priority over the YAML file. """ # Arrange: Set BOTH a YAML value and an environment variable monkeypatch.setenv("CONFIG_PATH", tmp_config_file) - monkeypatch.setenv("DEEPSEEK_MODEL_NAME", "deepseek-from-env") # This should be ignored + # The value from the environment variable + monkeypatch.setenv("DEEPSEEK_MODEL_NAME", "deepseek-from-env") # Act + # We need to import the module after setting the env variable for the test to work from app import config importlib.reload(config) - # Assert: The value from the YAML file should be used - assert config.settings.DEEPSEEK_MODEL_NAME == "deepseek-from-yaml" + # Assert: The value from the environment variable should be used + assert config.settings.DEEPSEEK_MODEL_NAME == "deepseek-from-env" def test_env_var_overrides_default(monkeypatch): """ @@ -59,4 +61,4 @@ importlib.reload(config) # Assert: The hardcoded default value should be used - assert config.settings.DEEPSEEK_MODEL_NAME == "deepseek-chat" \ No newline at end of file + assert config.settings.DEEPSEEK_MODEL_NAME == "deepseek-chat"