| import json |
| import logging |
| import os |
| from pathlib import Path |
| from typing import Any, Optional |
|
|
| logger = logging.getLogger(__name__) |
|
|
| _DEFAULTS: dict[str, Any] = { |
| "log_level": "INFO", |
| "debug": False, |
| "max_retries": 3, |
| "request_timeout": 30, |
| "page_size": 20, |
| "db_path": "data.db", |
| "secret_key": "", |
| } |
|
|
|
|
| class Config: |
| def __init__(self, path: Optional[str] = None) -> None: |
| self._data: dict[str, Any] = dict(_DEFAULTS) |
| if path and Path(path).exists(): |
| self._load(path) |
| self._from_env() |
|
|
| def _load(self, path: str) -> None: |
| try: |
| with open(path) as f: |
| self._data.update(json.load(f)) |
| except (json.JSONDecodeError, OSError) as exc: |
| logger.warning("Config load failed (%s): %s", path, exc) |
|
|
| def _from_env(self) -> None: |
| pairs = [ |
| ("APP_DEBUG", "debug", lambda v: v.lower() == "true"), |
| ("APP_LOG_LEVEL", "log_level", str), |
| ("APP_DB_PATH", "db_path", str), |
| ("APP_SECRET_KEY","secret_key",str), |
| ] |
| for env, key, cast in pairs: |
| raw = os.environ.get(env) |
| if raw is not None: |
| try: |
| self._data[key] = cast(raw) |
| except (ValueError, TypeError): |
| pass |
|
|
| def get(self, key: str, default: Any = None) -> Any: |
| return self._data.get(key, default) |
|
|
| def __getattr__(self, key: str) -> Any: |
| if key.startswith("_"): |
| raise AttributeError(key) |
| try: |
| return self._data[key] |
| except KeyError: |
| raise AttributeError(key) |
|
|
|
|
| _cfg: Optional[Config] = None |
|
|
| def get_config() -> Config: |
| global _cfg |
| if _cfg is None: |
| _cfg = Config(os.environ.get("APP_CONFIG")) |
| return _cfg |
|
|