media-rip/backend/app/core/config.py
xpltd efc2ead796 M001: media.rip() v1.0 — complete application
Full-featured self-hosted yt-dlp web frontend:
- Python 3.12+ / FastAPI backend with async SQLite, SSE transport, session isolation
- Vue 3 / TypeScript / Pinia frontend with real-time progress, theme picker
- 3 built-in themes (cyberpunk/dark/light) + drop-in custom theme system
- Admin auth (bcrypt), purge system, cookie upload, file serving
- Docker multi-stage build, GitHub Actions CI/CD
- 179 backend tests, 29 frontend tests (208 total)

Slices: S01 (Foundation), S02 (SSE+Sessions), S03 (Frontend),
        S04 (Admin+Auth), S05 (Themes), S06 (Docker+CI)
2026-03-18 20:00:17 -05:00

143 lines
3.8 KiB
Python

"""Application configuration via pydantic-settings.
Loads settings from (highest → lowest priority):
1. Environment variables (prefix ``MEDIARIP``, nested delimiter ``__``)
2. YAML config file (optional — zero-config if missing)
3. Init kwargs
4. .env file
Zero-config mode: if no YAML file is provided or the file doesn't exist,
all settings fall back to sensible defaults.
"""
from __future__ import annotations
import logging
from pathlib import Path
from typing import Any
from pydantic import BaseModel
from pydantic_settings import (
BaseSettings,
PydanticBaseSettingsSource,
SettingsConfigDict,
YamlConfigSettingsSource,
)
logger = logging.getLogger("mediarip.config")
# ---------------------------------------------------------------------------
# Nested config sections
# ---------------------------------------------------------------------------
class ServerConfig(BaseModel):
"""Core server settings."""
host: str = "0.0.0.0"
port: int = 8000
log_level: str = "info"
db_path: str = "mediarip.db"
class DownloadsConfig(BaseModel):
"""Download behaviour defaults."""
output_dir: str = "/downloads"
max_concurrent: int = 3
source_templates: dict[str, str] = {
"youtube.com": "%(uploader)s/%(title)s.%(ext)s",
"soundcloud.com": "%(uploader)s/%(title)s.%(ext)s",
"*": "%(title)s.%(ext)s",
}
default_template: str = "%(title)s.%(ext)s"
class SessionConfig(BaseModel):
"""Session management settings."""
mode: str = "isolated"
timeout_hours: int = 72
class PurgeConfig(BaseModel):
"""Automatic purge / cleanup settings."""
enabled: bool = False
max_age_hours: int = 168 # 7 days
cron: str = "0 3 * * *" # 3 AM daily
class UIConfig(BaseModel):
"""UI preferences."""
default_theme: str = "dark"
class AdminConfig(BaseModel):
"""Admin panel settings."""
enabled: bool = False
username: str = "admin"
password_hash: str = ""
# ---------------------------------------------------------------------------
# Safe YAML source — tolerates missing files
# ---------------------------------------------------------------------------
class _SafeYamlSource(YamlConfigSettingsSource):
"""YAML source that returns an empty dict when the file is missing."""
def __call__(self) -> dict[str, Any]:
yaml_file = self.yaml_file_path
if yaml_file is None:
return {}
if not Path(yaml_file).is_file():
logger.debug("YAML config file not found at %s — using defaults", yaml_file)
return {}
return super().__call__()
# ---------------------------------------------------------------------------
# Root config
# ---------------------------------------------------------------------------
class AppConfig(BaseSettings):
"""Top-level application configuration.
Priority (highest wins): env vars → YAML file → init kwargs → .env file.
"""
model_config = SettingsConfigDict(
env_prefix="MEDIARIP__",
env_nested_delimiter="__",
yaml_file=None,
)
server: ServerConfig = ServerConfig()
downloads: DownloadsConfig = DownloadsConfig()
session: SessionConfig = SessionConfig()
purge: PurgeConfig = PurgeConfig()
ui: UIConfig = UIConfig()
admin: AdminConfig = AdminConfig()
themes_dir: str = "./themes"
@classmethod
def settings_customise_sources(
cls,
settings_cls: type[BaseSettings],
init_settings: PydanticBaseSettingsSource,
env_settings: PydanticBaseSettingsSource,
dotenv_settings: PydanticBaseSettingsSource,
file_secret_settings: PydanticBaseSettingsSource,
) -> tuple[PydanticBaseSettingsSource, ...]:
return (
env_settings,
_SafeYamlSource(settings_cls),
init_settings,
dotenv_settings,
)