47baee854b
Test coverage: - test_utils.py: formatting helpers (human_size, ticks_to_minutes, format_duration, format_bitrate, ffprobe summaries, stream parsing) - test_path_utils.py: path resolution (prefix, media root mapping, edge cases with spaces/special chars) - test_domain_media.py: Jellyfin item normalization (HDR detection, media sources, stream extraction, display formatting) - test_jobs.py: job template rendering and shell quoting safety - test_media_index.py: SQLite index CRUD, querying, filtering, sorting, pagination - test_config.py: pydantic-settings env loading - test_api.py: full FastAPI integration tests with mocked SSH/Jellyfin (all endpoints: dashboard, monitoring, media, files, jobs) All tests run without network/SSH dependencies using mocks.
67 lines
1.7 KiB
Python
67 lines
1.7 KiB
Python
"""Backend configuration using pydantic-settings.
|
|
|
|
Reads from environment variables and .env file automatically.
|
|
Uses a flat settings model so that standard env vars like SSH_HOST,
|
|
JELLYFIN_URL etc. are picked up directly without nested-model complications.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from functools import lru_cache
|
|
from pathlib import Path
|
|
|
|
from pydantic_settings import BaseSettings
|
|
|
|
|
|
class Settings(BaseSettings):
|
|
"""Flat application settings read from env vars / .env file."""
|
|
|
|
# Jellyfin
|
|
jellyfin_url: str = ""
|
|
jellyfin_api_key: str = ""
|
|
jellyfin_user_id: str = ""
|
|
|
|
# SSH
|
|
ssh_host: str = ""
|
|
ssh_username: str = ""
|
|
ssh_port: int = 22
|
|
ssh_key_filename: str = str(Path.home() / ".ssh" / "id_rsa")
|
|
ssh_password: str = ""
|
|
|
|
# Remote paths
|
|
remote_media_root: str = ""
|
|
remote_path_prefix: str = ""
|
|
|
|
# Derived convenience
|
|
@property
|
|
def media_root(self) -> str:
|
|
return self.remote_media_root or ""
|
|
|
|
@property
|
|
def path_prefix(self) -> str:
|
|
return self.remote_path_prefix or ""
|
|
|
|
model_config = {"env_file": ".env", "env_file_encoding": "utf-8", "extra": "ignore"}
|
|
|
|
|
|
def _find_env_file() -> str | None:
|
|
"""Look for .env in CWD, then parent directories up to the repo root."""
|
|
current = Path.cwd()
|
|
for directory in [current, *current.parents]:
|
|
candidate = directory / ".env"
|
|
if candidate.is_file():
|
|
return str(candidate)
|
|
# Stop at repo root (has .git)
|
|
if (directory / ".git").exists():
|
|
break
|
|
return None
|
|
|
|
|
|
@lru_cache
|
|
def get_settings() -> Settings:
|
|
"""Return a cached Settings instance."""
|
|
env_file = _find_env_file()
|
|
if env_file:
|
|
return Settings(_env_file=env_file)
|
|
return Settings()
|