"""租户与站点配置模型。 - TenantConfig.data = deep_merge(DEFAULT_CONFIG, snapshot):租户快照 ⊕ 出厂默认 - Site.brand 的合并基线是**租户级** data["brand"](而非出厂默认), 这样租户级 brand 自定义也能被各站点继承。 """ from dataclasses import dataclass from app.platform.defaults import DEFAULT_CONFIG, deep_merge @dataclass(frozen=True) class Site: slug: str name: str domains: tuple[str, ...] brand: dict # 站点门面覆盖层 ⊕ 租户默认 brand(已深合并)。 # 只读约定:构造后不得修改 status: str = "active" # 终审 I-1:站点停用接线——数据源只输出 # active 站点(停用站点的域名不进索引 → 404), # 本字段记录该契约(当前恒为 "active") @dataclass(frozen=True) class TenantConfig: slug: str name: str status: str # active | disabled data: dict # deep_merge(DEFAULT_CONFIG, snapshot)。 # 只读约定:构造后不得修改 sites: tuple[Site, ...] = () @classmethod def from_snapshot(cls, slug, name, snapshot, sites=(), status="active"): merged = deep_merge(DEFAULT_CONFIG, snapshot) resolved = tuple( Site(s["slug"], s.get("name", s["slug"]), tuple(s.get("domains", ())), deep_merge(merged["brand"], s.get("brand", {})), s.get("status", "active")) for s in sites) return cls(slug=slug, name=name, status=status, data=merged, sites=resolved)