"""Environment-based configuration using Pydantic Settings. All settings are configurable via environment variables or .env file. """ from __future__ import annotations from pydantic import BaseModel, Field class APIConfig(BaseModel): """API server configuration.""" host: str = Field(default="0.0.0.0", description="Server bind host") port: int = Field(default=8000, description="Server bind port") workers: int = Field(default=1, description="Number of worker processes") cors_origins: list[str] = Field(default=["*"], description="CORS allowed origins") api_key: str | None = Field(default=None, description="API key for authentication") rate_limit: int = Field(default=120, description="Rate limit (requests per window)") rate_window: float = Field(default=60.0, description="Rate limit window in seconds") class DatabaseConfig(BaseModel): """Database configuration.""" url: str = Field(default="sqlite:///fusionagi.db", description="Database URL") pool_size: int = Field(default=5, description="Connection pool size") max_overflow: int = Field(default=10, description="Max overflow connections") echo: bool = Field(default=False, description="Echo SQL statements") class CacheConfig(BaseModel): """Cache configuration.""" enabled: bool = Field(default=True, description="Enable response caching") max_size: int = Field(default=1000, description="Max cached entries") ttl_seconds: float = Field(default=300.0, description="Cache TTL in seconds") backend: str = Field(default="memory", description="Cache backend (memory or redis)") redis_url: str | None = Field(default=None, description="Redis URL if backend is redis") class LoggingConfig(BaseModel): """Logging configuration.""" level: str = Field(default="INFO", description="Log level") format: str = Field(default="json", description="Log format (json or text)") correlation_id_header: str = Field(default="X-Request-ID", description="Request ID header") class GovernanceConfig(BaseModel): """Governance configuration.""" mode: str = Field(default="advisory", description="Governance mode (advisory or enforcing)") max_file_size: int | None = Field(default=None, description="Max file size in bytes (None=unlimited)") allow_private_urls: bool = Field(default=True, description="Allow private/internal URLs") class FusionAGIConfig(BaseModel): """Root configuration for FusionAGI.""" api: APIConfig = Field(default_factory=APIConfig) database: DatabaseConfig = Field(default_factory=DatabaseConfig) cache: CacheConfig = Field(default_factory=CacheConfig) logging: LoggingConfig = Field(default_factory=LoggingConfig) governance: GovernanceConfig = Field(default_factory=GovernanceConfig) tenant_isolation: bool = Field(default=True, description="Enable tenant isolation") max_concurrent_tasks: int = Field(default=5, description="Max background tasks") def load_config() -> FusionAGIConfig: """Load configuration from environment variables. Environment variables are mapped using the pattern: FUSIONAGI_
_ (e.g., FUSIONAGI_API_PORT=9000) """ import os config = FusionAGIConfig() env_map = { "FUSIONAGI_API_HOST": ("api", "host"), "FUSIONAGI_API_PORT": ("api", "port"), "FUSIONAGI_API_WORKERS": ("api", "workers"), "FUSIONAGI_API_KEY": ("api", "api_key"), "FUSIONAGI_RATE_LIMIT": ("api", "rate_limit"), "FUSIONAGI_RATE_WINDOW": ("api", "rate_window"), "FUSIONAGI_DB_URL": ("database", "url"), "FUSIONAGI_DB_POOL_SIZE": ("database", "pool_size"), "FUSIONAGI_CACHE_ENABLED": ("cache", "enabled"), "FUSIONAGI_CACHE_TTL": ("cache", "ttl_seconds"), "FUSIONAGI_CACHE_BACKEND": ("cache", "backend"), "FUSIONAGI_REDIS_URL": ("cache", "redis_url"), "FUSIONAGI_LOG_LEVEL": ("logging", "level"), "FUSIONAGI_LOG_FORMAT": ("logging", "format"), "FUSIONAGI_GOVERNANCE_MODE": ("governance", "mode"), } for env_var, (section, key) in env_map.items(): value = os.environ.get(env_var) if value is not None: section_obj = getattr(config, section) field_info = type(section_obj).model_fields.get(key) if field_info and field_info.annotation: annotation = field_info.annotation if annotation is int: value = int(value) # type: ignore[assignment] elif annotation is float: value = float(value) # type: ignore[assignment] elif annotation is bool: value = value.lower() in ("true", "1", "yes") # type: ignore[assignment] setattr(section_obj, key, value) return config