Files
photoanalyzer/photo_pipeline/config.py

63 lines
1.9 KiB
Python

"""Typed application configuration.
Values come from ``PHOTO_PIPELINE_*`` environment variables. Secrets use
``SecretStr`` so they are masked in logs, reprs, and model dumps, and are never
returned by the API. Only ``.get_secret_value()`` exposes them, and only where a
real external call needs them.
pydantic-settings would do this too, but a prefix-scan over the declared fields
is a few lines and one fewer dependency.
"""
from __future__ import annotations
import os
from pathlib import Path
from typing import Mapping
from pydantic import BaseModel, ConfigDict, SecretStr
ENV_PREFIX = "PHOTO_PIPELINE_"
class Config(BaseModel):
model_config = ConfigDict(frozen=True)
data_dir: Path = Path("data")
db_path: Path | None = None # defaults to data_dir/photo_pipeline.db
host: str = "127.0.0.1"
port: int = 8000
log_level: str = "INFO"
log_format: str = "json" # "json" or "text"
# Library boundary for path validation (os.pathsep-separated in the env var).
library_roots: tuple[Path, ...] = ()
thumbnail_cache_quota_bytes: int = 500_000_000
thumbnail_max_pixels: int = 100_000_000
vision_api_key: SecretStr | None = None
immich_api_key: SecretStr | None = None
@property
def database_path(self) -> Path:
return self.db_path or (self.data_dir / "photo_pipeline.db")
@property
def database_url(self) -> str:
return f"sqlite:///{self.database_path}"
@property
def thumbnail_cache_dir(self) -> Path:
return self.data_dir / "cache" / "thumbs"
@classmethod
def from_env(cls, environ: Mapping[str, str] | None = None) -> "Config":
env = os.environ if environ is None else environ
data: dict = {}
for name in cls.model_fields:
raw = env.get(ENV_PREFIX + name.upper())
if not raw:
continue
data[name] = raw.split(os.pathsep) if name == "library_roots" else raw
return cls(**data)