"""Enhanced configuration system with Secret Manager integration.""" import os import asyncio from typing import Dict, Optional, Any from functools import lru_cache from pydantic_settings import BaseSettings from .config import Settings as BaseConfig from .logging import get_logger logger = get_logger(__name__) class SecretsConfig(BaseConfig): """Enhanced configuration that loads secrets from GCP Secret Manager.""" def __init__(self, **kwargs): # Initialize with base configuration first super().__init__(**kwargs) # Flag to track if secrets have been loaded self._secrets_loaded = False self._secret_values: Dict[str, str] = {} async def load_secrets(self) -> None: """Load secrets from Secret Manager asynchronously.""" if self._secrets_loaded: return try: # Only import here to avoid circular imports from app.services.secrets_manager import secrets_manager # Define which config fields should be loaded from secrets secret_mappings = { # Config field -> Secret Manager name "jwt_secret": "jwt-secret", "jwt_refresh_secret": "jwt-refresh-secret", "mongodb_uri": "mongodb-url", "redis_url": "redis-url", "gemini_api_key": "gemini-api-key", "sendgrid_api_key": "sendgrid-api-key", "elevenlabs_api_key": "elevenlabs-api-key", "sentry_dsn": "sentry-dsn" } # Get all secrets in batch secret_names = list(secret_mappings.values()) retrieved_secrets = await secrets_manager.get_secrets_batch(secret_names) # Map secrets back to config fields for config_field, secret_name in secret_mappings.items(): if secret_name in retrieved_secrets: self._secret_values[config_field] = retrieved_secrets[secret_name] # Override the config value setattr(self, config_field, retrieved_secrets[secret_name]) logger.debug(f"Loaded secret for {config_field}") else: logger.warning(f"Secret {secret_name} not available, using environment/default") self._secrets_loaded = True logger.info(f"Successfully loaded {len(retrieved_secrets)} secrets from Secret Manager") except Exception as e: logger.warning(f"Failed to load secrets from Secret Manager: {e}") logger.warning("Falling back to environment variables") self._secrets_loaded = True # Mark as loaded to prevent retries def get_secret_value(self, field_name: str) -> Optional[str]: """Get a secret value if it was loaded from Secret Manager.""" return self._secret_values.get(field_name) async def refresh_secrets(self) -> None: """Force refresh secrets from Secret Manager.""" self._secrets_loaded = False self._secret_values.clear() # Clear the secrets manager cache from app.services.secrets_manager import secrets_manager secrets_manager.clear_cache() await self.load_secrets() @property def is_production(self) -> bool: """Check if running in production environment.""" return self.app_env == "prod" @property def is_development(self) -> bool: """Check if running in development environment.""" return self.app_env == "dev" @property def google_cloud_project(self) -> str: """Get Google Cloud Project ID.""" return self.gcp_project_id @property def jwt_refresh_secret(self) -> str: """Get JWT refresh secret (fallback to main secret if not set).""" return getattr(self, '_jwt_refresh_secret', self.jwt_secret) @jwt_refresh_secret.setter def jwt_refresh_secret(self, value: str) -> None: """Set JWT refresh secret.""" self._jwt_refresh_secret = value # Global configuration instance _config_instance: Optional[SecretsConfig] = None async def initialize_config() -> SecretsConfig: """Initialize configuration with secrets loading.""" global _config_instance if _config_instance is None: _config_instance = SecretsConfig() await _config_instance.load_secrets() return _config_instance def get_settings() -> SecretsConfig: """Get settings instance (synchronous).""" global _config_instance if _config_instance is None: # Initialize without secrets for backwards compatibility _config_instance = SecretsConfig() logger.warning("Settings accessed before async initialization - secrets not loaded") return _config_instance @lru_cache() def get_settings_cached() -> SecretsConfig: """Get cached settings instance.""" return get_settings() # Backwards compatibility settings = get_settings()