107 lines
4.7 KiB
Python
107 lines
4.7 KiB
Python
|
|
"""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_<SECTION>_<KEY> (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
|