Files
Aurora-Core/app/core/config.py
T

118 lines
4.0 KiB
Python
Raw Normal View History

2026-09-04 17:34:03 -05:00
"""Environment-based application configuration.
All runtime configuration flows through this module. Nothing else in the
codebase should read `os.environ` directly, so that configuration stays
auditable and testable in one place.
"""
from __future__ import annotations
from functools import lru_cache
from pydantic import Field, field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
model_config = SettingsConfigDict(
env_file=".env",
env_file_encoding="utf-8",
case_sensitive=False,
extra="ignore",
)
# --- Runtime ---
AURORA_ENV: str = Field(default="development")
AURORA_LOG_LEVEL: str = Field(default="INFO")
AURORA_API_DOCS_ENABLED: bool = Field(default=True)
# --- Database ---
DATABASE_URL: str = Field(
default="postgresql+asyncpg://aurora:aurora@localhost:5432/aurora_core"
)
DATABASE_URL_SYNC: str = Field(
default="postgresql+psycopg2://aurora:aurora@localhost:5432/aurora_core"
)
DATABASE_POOL_SIZE: int = Field(default=10)
DATABASE_MAX_OVERFLOW: int = Field(default=20)
# Set true when DATABASE_URL points at PgBouncer in transaction pooling
# mode (docker-compose.prod.yml): asyncpg's server-side prepared
# statements don't survive connections being handed to different clients
# between transactions, so they must be disabled.
DATABASE_BEHIND_TRANSACTION_POOLER: bool = Field(default=False)
# --- Redis ---
REDIS_URL: str = Field(default="redis://localhost:6379/0")
REDIS_REQUIRED: bool = Field(default=False)
# --- Security ---
AURORA_SECRET_KEY: str = Field(default="changeme-generate-a-real-secret")
AURORA_ADMIN_API_KEYS: str = Field(default="")
# --- CORS ---
CORS_ORIGINS: str = Field(default="http://localhost:3000")
# --- Providers ---
TMDB_API_KEY: str = Field(default="")
# --- Workers ---
WORKER_CONCURRENCY: int = Field(default=4)
# --- Caching ---
CACHE_DEFAULT_TTL: int = Field(default=300)
# --- Rate limiting ---
RATE_LIMIT_ENABLED: bool = Field(default=True)
RATE_LIMIT_PER_MINUTE: int = Field(default=120)
# --- Artwork storage (section 10) ---
# Local-disk self-hosting, deliberately simple for 0.1: fine for a
# single-host deployment where the api and worker containers share one
# volume (see docker-compose.yml). A genuinely multi-host deployment
# would need real object storage (S3/R2/...) behind the same
# ArtworkStorage interface instead — see app/services/artwork/storage.py.
MEDIA_ROOT: str = Field(default="./media")
# Root-relative URL path artwork is served under (app/main.py mounts
# MEDIA_ROOT there). Kept relative rather than a full domain so it keeps
# working whether Aurora is reached via localhost, a Traefik host, or
# (once configured) amdbs.net.
MEDIA_URL_PREFIX: str = Field(default="/media")
ARTWORK_DOWNLOAD_MAX_BYTES: int = Field(default=15 * 1024 * 1024)
2026-09-04 17:34:03 -05:00
@field_validator("AURORA_ENV")
@classmethod
def _validate_env(cls, v: str) -> str:
allowed = {"development", "test", "staging", "production"}
if v not in allowed:
raise ValueError(f"AURORA_ENV must be one of {allowed}, got {v!r}")
return v
@property
def cors_origins_list(self) -> list[str]:
return [origin.strip() for origin in self.CORS_ORIGINS.split(",") if origin.strip()]
@property
def admin_api_keys_set(self) -> set[str]:
return {key.strip() for key in self.AURORA_ADMIN_API_KEYS.split(",") if key.strip()}
@property
def is_production(self) -> bool:
return self.AURORA_ENV == "production"
@property
def docs_url(self) -> str | None:
if not self.AURORA_API_DOCS_ENABLED:
return None
return "/docs"
@property
def openapi_url(self) -> str | None:
if not self.AURORA_API_DOCS_ENABLED:
return None
return "/openapi.json"
@lru_cache
def get_settings() -> Settings:
return Settings()