Task 1: Simplify Capability; extend BoundEntity

- Make href Optional[str] = None for pattern capabilities
- Remove observe field (v2 architecture observes all hrefs)
- Add rt_filter, match_fn, key_fn fields to Capability
- Add entities default value tuple[()] to Capability
- Add key_override field to BoundEntity
- Update _key() to use key_override when present
- Update _make_observe_paths() to observe all unique hrefs (OBSERVE as accelerator)
- Guard _build() to skip pattern capabilities (href is None)
- Add test_bound_entity_key_override_takes_precedence test
- Update test_capability_defaults to remove observe assertion

All 61 tests passing (60 existing + 1 new).
This commit is contained in:
Marc Billow
2026-06-30 16:32:42 -05:00
parent 1a76bf6d47
commit 81d97e2fb5
6 changed files with 42 additions and 7 deletions
+2 -2
View File
@@ -27,7 +27,7 @@ def _segs(href: str) -> list[str]:
def _key(b: BoundEntity) -> str:
return f"{b.desc.key}{b.instance}"
return f"{b.key_override or b.desc.key}{b.instance}"
def _encode(cfg: dict) -> bytes:
@@ -70,7 +70,7 @@ def _make_flatten(bound: list[BoundEntity]) -> Callable[[dict], dict]:
def _make_observe_paths(bound: list[BoundEntity]) -> list[list[str]]:
seen, out = set(), []
for b in bound:
if not b.capability.observe or b.href in seen:
if b.href in seen:
continue
seen.add(b.href)
out.append(_segs(b.href))
+5 -3
View File
@@ -9,10 +9,12 @@ from .entities import SamsungEntityDescription
@dataclass(frozen=True, kw_only=True)
class Capability:
href: str
entities: tuple[SamsungEntityDescription, ...]
href: Optional[str] = None
entities: tuple[SamsungEntityDescription, ...] = ()
poll_tier: str = 'warm' # 'hot' | 'warm' | 'cold'
observe: bool = True
rt_filter: Optional[str] = None # bind only if rt_filter in rep.get('rt', ())
match_fn: Optional[Callable[[dict, dict], bool]] = None # match_fn(rep, resources) -> bool
key_fn: Optional[Callable[[str], str]] = None # href -> entity key (pattern caps)
# Rare optional hooks — only operational-state-style resources use these.
active_when: Optional[Callable[[dict], bool]] = None
on_observation: Optional[Callable[[dict, dict], None]] = None
+1
View File
@@ -19,6 +19,7 @@ class BoundEntity:
capability: Capability
desc: SamsungEntityDescription
instance: str = ''
key_override: Optional[str] = None
def instance_suffix(href: str) -> str:
+6 -1
View File
@@ -10,9 +10,14 @@ from .capability import Capability
def _build() -> dict[str, Capability]:
"""Build the registry, raising ValueError if any href is duplicated."""
"""Build the registry, raising ValueError if any href is duplicated.
Skips capabilities with href=None (pattern capabilities, handled elsewhere).
"""
out: dict[str, Capability] = {}
for cap in ALL:
if cap.href is None:
continue
if cap.href in out:
raise ValueError(f"duplicate capability href: {cap.href}")
out[cap.href] = cap
+28
View File
@@ -57,3 +57,31 @@ def test_switch_discovery_config_has_correct_payloads(dishwasher_resources):
assert cfg['state_off'] == 'Off'
assert 'On' in cfg['value_template']
assert 'Off' in cfg['value_template']
def test_bound_entity_key_override_takes_precedence():
from samsung_appliance.registry.adapter import _key
from samsung_appliance.registry.discovery import BoundEntity
from samsung_appliance.registry.entities import SensorDesc
cap = common.ENERGY_METER
desc = SensorDesc(
key='power_watts',
field='x.com.samsung.da.power',
unit='W',
device_class='power',
state_class='measurement',
value_fn=lambda x: x,
)
# Without key_override, should use desc.key
b1 = BoundEntity(href='/power/vs/0', capability=cap, desc=desc, instance='')
assert _key(b1) == 'power_watts'
# With key_override, should use key_override
b2 = BoundEntity(href='/power/vs/0', capability=cap, desc=desc, instance='', key_override='foo')
assert _key(b2) == 'foo'
# key_override with instance suffix should preserve instance suffix
b3 = BoundEntity(href='/power/vs/0', capability=cap, desc=desc, instance='_1', key_override='bar')
assert _key(b3) == 'bar_1'
-1
View File
@@ -6,7 +6,6 @@ def test_capability_defaults():
c = Capability(href='/kidslock/vs/0',
entities=(BinarySensorDesc(key='child_lock', field='x.com.samsung.da.kidsLock'),))
assert c.poll_tier == 'warm'
assert c.observe is True
assert c.active_when is None
assert len(c.entities) == 1