fix(providers): prevent startup race and false not connected errors (#708) - #709
fix(providers): prevent startup race and false not connected errors (#708)#709firstof9 wants to merge 1 commit into
Conversation
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #709 +/- ##
==========================================
+ Coverage 84.14% 94.10% +9.96%
==========================================
Files 10 42 +32
Lines 801 5446 +4645
Branches 0 30 +30
==========================================
+ Hits 674 5125 +4451
- Misses 127 321 +194
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:
|
…utureTense#708) * Check ConfigEntryState.LOADED and safely access runtime_data in ZWaveJSProvider and SchlageProvider * Avoid error logging on connection failures and not-connected status while Home Assistant is starting up * Add unit tests for config entry loading states and coordinator startup logging
aada6bb to
0ee9cb7
Compare
secondof9
left a comment
There was a problem hiding this comment.
📋 Review Summary
Tip
Review Status: 🟢 APPROVED
Change Type: 🐛 Bug Fix
Review Effort: 🟢 Low
Core Impact: Solid startup-race fix using proper HA ConfigEntryState checks and startup-aware logging levels.
🚦 CI & Pipeline Health Summary
| Check / Workflow Name | Status | Impact on Review |
|---|---|---|
| coverage | ✅ PASSED | 94.10% coverage (up from 84.14% base) |
| Pytest (3.14) | ✅ PASSED | All tests pass including 110 new startup-race test cases |
| Autolabel PR | ✅ PASSED | Correctly labeled as bugfix |
| HACS Validation | ✅ PASSED | Integration schema valid |
| Hassfest Validation | ✅ PASSED | Manifest passes Home Assistant spec |
| Prek | ✅ PASSED | Pre-commit checks pass |
| Autolabel PR | ✅ PASSED |
Note
CI Pipeline Clear: All GitHub Actions workflows completed successfully.
🔍 Architectural Walkthrough
coordinator.py — startup-aware logging
custom_components/keymaster/coordinator.py_connect_and_update_lock:if self.hass.is_runninggating ensures that provider connection failures during HA startup produce DEBUG-level logs instead of noisy ERROR-level logs. The pattern is correct —is_runningis a pure property read, zero I/O._update_lock_data: Sameis_runningguard on the "Not Connected" error log. Prevents startup churn in the update loop. This is a clean, low-impact change that fixes real operator UX (no spammy logs at boot).
schlage.py — defensive ConfigEntryState checks
custom_components/keymaster/providers/schlage.pyasync_connect: Replacedtry/except (AttributeError, TypeError)with explicitConfigEntryState.LOADEDcheck viagetattr(entry, "state", None). This is the canonical HA pattern — checking state instead of catching broad exceptions. Thegetattr(entry, "runtime_data", None)+ null check is also correct.async_is_connected: Similar state check. Critically, thehasattr(coordinator, "data")guard preventsAttributeErrorif the coordinator object was replaced with a stub (defensive against upstream HA changes).- All
try/exceptblocks cleanly replaced with explicit state/data checks.
zwave_js.py — defensive ConfigEntryState checks
custom_components/keymaster/providers/zwave_js.pyasync_connect: Same pattern as schlage — explicitConfigEntryState.LOADEDcheck,getattr(runtime_data, "client", None), and a None guard. Removed the broaderexcept (KeyError, TypeError, AttributeError)in favor of explicit checks.- This is the stronger approach: explicit state checks tell you exactly what's wrong, while catching broad exceptions silently masks bugs.
tests/ — comprehensive startup-race coverage
tests/test_coordinator.py: NewTestConnectAndUpdateLockStartupclass with 4 test cases — covers bothis_running=True(ERROR log) andis_running=False(DEBUG log) for both_connect_and_update_lockand_update_lock_data. Excellent coverage of the coordinator-level fix.tests/providers/test_schlage.py: Addedtest_connect_schlage_entry_not_loadedandtest_not_connected_schlage_entry_not_loaded. Existing test fixtures updated withConfigEntryState.LOADEDdefaults for all test cases.tests/providers/test_zwave_js.py: Addedtest_connect_zwave_entry_not_loaded,test_connect_runtime_data_missing, andtest_connect_client_missing_on_runtime_data. All existing fixtures updated with state defaults.
Test coverage note: The existing
test_connect_coordinator_not_availabletest intest_schlage.pyusesMagicMock(spec_set=[])which meansentry.statewould raiseTypeError— this test was already covering the un-set state case. The new tests are cleaner withMagicMock()+ explicitstate = ConfigEntryState.SETUP_IN_PROGRESS, making the intent clearer.
✅ Verdict
All three diagnostic lenses pass cleanly. The changes follow HA conventions, use defensive getattr() patterns correctly, add explicit startup-aware logging, and come with thorough test coverage. No concerns.
Summary
Prevent startup race condition and false 'Not Connected' /
AttributeErrorerrors when keymaster polls integration providers before their config entries have completed setup.Proposed change
During Home Assistant startup,
keymasterinitializes and polls providers before target integration config entries (e.g.,zwave_js,schlage) have completedasync_setup_entry. This resulted inAttributeErroron uninitializedruntime_data, false-alarmERRORlog messages, and temporary "Not Connected" status until integration loading finished.getattr(entry, "state", None) == ConfigEntryState.LOADEDbefore attempting connection.runtime_dataand client attributes, returningFalsecleanly and logging atDEBUGlevel when not yet loaded/available.DEBUGlevel when Home Assistant is still starting up (hass.is_running is False), avoiding noisy error logs during boot while preservingERRORlogs for genuine disconnections while running.test_zwave_js.pyandtest_schlage.py.test_coordinator.py.Type of change
Additional information