from __future__ import annotations import dataclasses import logging import os.path import yaml _cached_config: Config | None = None _logger = logging.getLogger(__name__) @dataclasses.dataclass class Config: _source_filename: str controllers: list[str] server_names: list[str] comm_secret: str cookie_secret: str venue_info_url: str | None session_timeout: int # in seconds api_port: int controller_port: int database_file: str # empty str: disable database custom: str debug: bool @staticmethod def load_default_once(filename: str | None = None) -> Config: global _cached_config if not _cached_config: _cached_config = Config.load(filename) elif not filename is None and filename != _cached_config._source_filename: raise RuntimeError( f"Already loaded different config from {_cached_config._source_filename!r} instead of {filename!r}", ) return _cached_config @staticmethod def load(filename: str | None = None) -> Config: if filename is None: for name in ("capport.yaml", "/etc/capport.yaml"): if os.path.exists(name): return Config.load(name) raise RuntimeError("Missing config file") with open(filename) as f: data = yaml.safe_load(f) if not isinstance(data, dict): raise RuntimeError(f"Invalid yaml config data, expected dict: {data!r}") controllers = list(map(str, data.pop("controllers"))) config = Config( _source_filename=filename, controllers=controllers, server_names=data.pop("server-names", []), comm_secret=str(data.pop("comm-secret")), cookie_secret=str(data.pop("cookie-secret")), venue_info_url=str(data.pop("venue-info-url")), session_timeout=data.pop("session-timeout", 3600), api_port=data.pop("api-port", 8000), controller_port=data.pop("controller-port", 5000), database_file=str(data.pop("database-file", "capport.state")), custom=str(data.pop("custom", "custom")), debug=data.pop("debug", False), ) if data: _logger.error(f"Unknown config elements: {list(data.keys())}") return config