Files
posefit-server/configs/load.py
T
wsy182 ae52578ed7 Separate config models from loader
- configs/models.py: AppConfig and all section dataclasses
- configs/load.py: pure loading logic (yaml, env overrides)
- config = load_config() singleton for consumers
2026-06-10 10:24:45 +08:00

88 lines
2.6 KiB
Python

from __future__ import annotations
import dataclasses
import os
from pathlib import Path
from typing import Any
import yaml
from configs.models import (
AppConfig,
AudioConfig,
DeadBugConfig,
LoggingConfig,
ModelConfig,
ServerConfig,
VideoConfig,
)
_PROJECT_ROOT = Path(__file__).resolve().parent.parent
_ENV_MAP = {
"POSEFIT_WS_HOST": ("server", "host"),
"POSEFIT_WS_PORT": ("server", "port", int),
"POSEFIT_WS_MAX_SIZE": ("server", "max_ws_size", int),
"POSEFIT_PROCESS_EVERY_N_FRAMES": ("video", "process_every_n_frames", int),
"POSEFIT_MODEL_PATH": ("model", "path"),
"POSEFIT_PREFER_GPU": ("model", "prefer_gpu", lambda v: v not in ("0", "false", "False")),
"POSEFIT_VISIBILITY_THRESHOLD": ("dead_bug", "visibility_threshold", float),
"POSEFIT_EXTENSION_CONFIRM_FRAMES": ("dead_bug", "extension_confirm_frames", int),
"POSEFIT_RESET_CONFIRM_FRAMES": ("dead_bug", "reset_confirm_frames", int),
"POSEFIT_REP_ANNOUNCER_ENABLED": ("audio", "rep_announcer_enabled", lambda v: v not in ("0", "false", "False")),
"POSEFIT_REP_ANNOUNCER_RATE": ("audio", "rep_announcer_rate", int),
"POSEFIT_REP_ANNOUNCER_VOLUME": ("audio", "rep_announcer_volume", float),
"POSEFIT_LOG_ROTATION": ("logging", "rotation"),
"POSEFIT_LOG_RETENTION": ("logging", "retention"),
"POSEFIT_LOG_DIR": ("logging", "dir"),
}
_SECTION_CLASS = {
"server": ServerConfig,
"video": VideoConfig,
"model": ModelConfig,
"dead_bug": DeadBugConfig,
"audio": AudioConfig,
"logging": LoggingConfig,
}
def _dict_to_dataclass(cls: type, data: dict[str, Any] | None) -> dict[str, Any]:
if data is None:
return {}
field_names = {f.name for f in dataclasses.fields(cls)}
return {k: v for k, v in data.items() if k in field_names}
def _read_yaml(path: Path) -> dict[str, Any]:
if not path.exists():
return {}
with open(path, encoding="utf-8") as f:
return yaml.safe_load(f) or {}
def _apply_env_overrides(raw: dict[str, Any]) -> None:
for env_var, (section, key, *rest) in _ENV_MAP.items():
value = os.getenv(env_var)
if value is None:
continue
if rest:
value = rest[0](value)
raw.setdefault(section, {})[key] = value
def load_config(config_path: str | Path | None = None) -> AppConfig:
if config_path is None:
config_path = _PROJECT_ROOT / "config.yaml"
raw = _read_yaml(Path(config_path))
_apply_env_overrides(raw)
return AppConfig(**{
section: cls(**_dict_to_dataclass(cls, raw.get(section)))
for section, cls in _SECTION_CLASS.items()
})
config = load_config()