Refactor: Bind Keymaster entities to per-lock coordinators#693
Conversation
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #693 +/- ##
==========================================
+ Coverage 84.14% 93.28% +9.14%
==========================================
Files 10 42 +32
Lines 801 5258 +4457
Branches 0 30 +30
==========================================
+ Hits 674 4905 +4231
- Misses 127 353 +226
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
cf46de8 to
bdc6135
Compare
Bind every Keymaster entity platform to its per-entry KeymasterLockCoordinator instead of the global manager, so entity listeners live on the per-lock coordinator. The manager remains the storage owner and sole periodic refresher; a keepalive listener keeps its 60s refresh loop scheduled now that entities no longer listen on it. Notifications reach rebound entities through a conservative fan-out from the manager's deferred flush to all per-lock coordinators, while the scoped dual-target bridge is retained for optimistic updates and future dirty-lock scoping. Entity unique IDs, names, device identifiers, and the persisted storage format are unchanged. Closes FutureTense#681 Part of FutureTense#670 Assisted-by: GitHub Copilot CLI 1.0.68 (Claude Opus 4.8, model claude-opus-4.8) Signed-off-by: Andrew Grimberg <tykeal@bardicgrove.org>
bdc6135 to
7ef4063
Compare
This comment was marked as outdated.
This comment was marked as outdated.
|
Both flagged points reference code that isn't in the current PR head (
class KeymasterLockCoordinator(DataUpdateCoordinator[KeymasterLock | None]):
def __init__(self, manager: KeymasterCoordinator, config_entry_id: str) -> None:
self.manager = manager
self.config_entry_id = config_entry_id
super().__init__(
manager.hass,
_LOGGER,
name=f"{DOMAIN}_{config_entry_id}",
update_interval=None,
config_entry=manager.hass.config_entries.async_get_entry(config_entry_id),
always_update=False,
)
self.data = manager.sync_get_lock_by_config_entry_id(config_entry_id)There is no Double data_update, preserve_failed_refresh = self._flush_global_leg()
targets = list(self._lock_coordinators) if all_fanout else list(entry_ids)
for entry_id in targets:
self._push_lock_coordinator_update(
entry_id, data_update=data_update, preserve_failed_refresh=preserve_failed_refresh
)
|
Summary
Phase B of issue #670: move entity listeners off the global manager onto the per-entry
KeymasterLockCoordinatorintroduced in #680. Entity registry stability (unique IDs, names, device identifiers) and the persisted storage format are unchanged.Changes
entity.py+ all 9 platforms):KeymasterEntityis nowCoordinatorEntity[KeymasterLockCoordinator]; each platform constructs entities withmanager.async_get_lock_coordinator(entry_id)while setup-time lock lookups use the manager._kmlock, unique IDs, names, and device info are unchanged.coordinator.py): the manager's deferred global-notification flush now also pushes fresh lock data to all per-lock coordinators (via shared_flush_global_leg+_push_lock_coordinator_updatehelpers), so mutation and periodic-refresh notifications reach the rebound entities. Per-dirty-lock scoping is deferred to Refactor: Pipeline Keymaster refreshes through dirty lock scopes #682/Fix: Scope quick and debounced refresh to Keymaster lock entries #684.switch.pyoptimistic updates route through the newKeymasterLockCoordinator.async_schedule_notification().Testing
{entry_id}_{slugify(property)}; conservative fan-out and refresh-completion reach per-lock listeners; both flush orderings notify every coordinator exactly once; keepalive install/remove.ruff check,ruff format,mypy, targeted platform suites, and the full suite pass; 100% patch coverage on new lines.Closes #681
Part of #670