86 lines
2.6 KiB
Python
86 lines
2.6 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""配置管理主类"""
|
|
from copy import deepcopy
|
|
from .defaults import DEFAULTS
|
|
from .env_parser import load_env_overrides
|
|
|
|
class AppConfig:
|
|
"""应用配置管理器"""
|
|
|
|
def __init__(self, config_dict: dict):
|
|
self.config = config_dict
|
|
|
|
@classmethod
|
|
def load(cls, cli_overrides: dict = None):
|
|
"""加载配置: DEFAULTS < ENV < CLI"""
|
|
cfg = load_env_overrides(DEFAULTS)
|
|
|
|
if cli_overrides:
|
|
cls._deep_merge(cfg, cli_overrides)
|
|
|
|
# 规范化
|
|
cls._normalize(cfg)
|
|
cls._validate(cfg)
|
|
|
|
return cls(cfg)
|
|
|
|
@staticmethod
|
|
def _deep_merge(dst, src):
|
|
"""深度合并字典"""
|
|
for k, v in src.items():
|
|
if isinstance(v, dict) and isinstance(dst.get(k), dict):
|
|
AppConfig._deep_merge(dst[k], v)
|
|
else:
|
|
dst[k] = v
|
|
|
|
@staticmethod
|
|
def _normalize(cfg):
|
|
"""规范化配置"""
|
|
# 转换 store_id 为整数
|
|
try:
|
|
cfg["app"]["store_id"] = int(str(cfg["app"]["store_id"]).strip())
|
|
except Exception:
|
|
raise SystemExit("app.store_id 必须为整数")
|
|
|
|
# DSN 组装
|
|
if not cfg["db"]["dsn"]:
|
|
cfg["db"]["dsn"] = (
|
|
f"postgresql://{cfg['db']['user']}:{cfg['db']['password']}"
|
|
f"@{cfg['db']['host']}:{cfg['db']['port']}/{cfg['db']['name']}"
|
|
)
|
|
|
|
# 会话参数
|
|
cfg["db"].setdefault("session", {})
|
|
sess = cfg["db"]["session"]
|
|
sess.setdefault("timezone", cfg["app"]["timezone"])
|
|
|
|
for k in ("statement_timeout_ms", "lock_timeout_ms", "idle_in_tx_timeout_ms"):
|
|
if k in sess and sess[k] is not None:
|
|
try:
|
|
sess[k] = int(sess[k])
|
|
except Exception:
|
|
raise SystemExit(f"db.session.{k} 需为整数毫秒")
|
|
|
|
@staticmethod
|
|
def _validate(cfg):
|
|
"""验证必填配置"""
|
|
missing = []
|
|
if not cfg["app"]["store_id"]:
|
|
missing.append("app.store_id")
|
|
if missing:
|
|
raise SystemExit("缺少必需配置: " + ", ".join(missing))
|
|
|
|
def get(self, key: str, default=None):
|
|
"""获取配置值(支持点号路径)"""
|
|
keys = key.split(".")
|
|
val = self.config
|
|
for k in keys:
|
|
if isinstance(val, dict):
|
|
val = val.get(k)
|
|
else:
|
|
return default
|
|
return val if val is not None else default
|
|
|
|
def __getitem__(self, key):
|
|
return self.config[key]
|