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:
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -19,6 +19,7 @@ class BoundEntity:
|
||||
capability: Capability
|
||||
desc: SamsungEntityDescription
|
||||
instance: str = ''
|
||||
key_override: Optional[str] = None
|
||||
|
||||
|
||||
def instance_suffix(href: str) -> str:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user