| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172 |
- """The zEPD model registry: loads drivers/*.json and merges each named model
- over default.json.
- Pure Python, no ESPHome dependency - see claude/DESCRIPTOR_REGISTRY_PLAN.md.
- Both display.py (at ESPHome compile time) and standalone host tooling
- (check_descriptors.py and friends) import this module directly.
- """
- from __future__ import annotations
- import json
- from pathlib import Path
- from typing import Any
- DRIVERS_DIR = Path(__file__).parent / "drivers"
- def _merge(base: Any, overlay: Any) -> Any:
- """Recursive dict merge. Named dict keys merge recursively; anything
- anonymous/positional (list elements) replaces whole. A key present in
- `overlay` but not in `base` is dropped silently - the accumulated
- `base` (built up from default.json first) is the implicit schema."""
- if isinstance(base, dict) and isinstance(overlay, dict):
- out = dict(base)
- for key, value in overlay.items():
- if key in out:
- out[key] = _merge(out[key], value)
- # else: unknown key, dropped - see unknown_keys() for a warning pass.
- return out
- return overlay
- def unknown_keys(base: dict, overlay: dict, prefix: str = "") -> list[str]:
- """Dotted-path keys present in `overlay` that don't exist anywhere in
- `base` - a likely typo, surfaced as a warning (not an error) by callers."""
- found: list[str] = []
- for key, value in overlay.items():
- path = f"{prefix}.{key}" if prefix else key
- if key not in base:
- found.append(path)
- elif isinstance(base.get(key), dict) and isinstance(value, dict):
- found.extend(unknown_keys(base[key], value, path))
- return found
- def load_default(drivers_dir: Path = DRIVERS_DIR) -> dict:
- with open(drivers_dir / "default.json") as f:
- return json.load(f)
- def load_model(name: str, drivers_dir: Path = DRIVERS_DIR, overlay: dict | None = None) -> dict:
- """Load one model: default.json, then drivers/<name>.json (skipped when
- name == "default"), then an optional YAML-provided overlay dict."""
- merged = load_default(drivers_dir)
- if name != "default":
- path = drivers_dir / f"{name}.json"
- with open(path) as f:
- model_json = json.load(f)
- unk = unknown_keys(merged, model_json)
- if unk:
- print(f"warning: {path}: unknown key(s), possible typo: {', '.join(unk)}")
- merged = _merge(merged, model_json)
- if overlay:
- unk = unknown_keys(merged, overlay)
- if unk:
- print(f"warning: model_overlay for {name!r}: unknown key(s), possible typo: {', '.join(unk)}")
- merged = _merge(merged, overlay)
- return merged
- def list_models(drivers_dir: Path = DRIVERS_DIR) -> list[str]:
- return sorted(p.stem for p in drivers_dir.glob("*.json") if p.stem != "default")
|