Task d25b875f: Last-good snapshot cache and staleness handling #170

Member

Done-condition

The integration caches the last-good snapshot: with the API stopped, the panel renders the last-good data and entities become unavailable only after a configured staleness threshold; when the API returns, entities recover without a reload. Both behaviours are covered by tests (API down within threshold serves cached data; past threshold marks unavailable; API back recovers).

What this does

  • core/const.py: new CONF_SNAPSHOT_STALENESS / DEFAULT_SNAPSHOT_STALENESS (seconds; default 3x the default update interval = 900 s, documented in the const comment).
  • coordinator.py: every successful pass caches the rebuilt snapshot stamped with time.monotonic() (via a _monotonic() seam). On a typed NestQuestApiError, if a cache exists and the elapsed time since the last success is within the threshold (inclusive), the pass serves the cached snapshot (entities stay available) and logs a clear warning; otherwise it raises UpdateFailed as before (typed error logged + chained). A later success refreshes the cache/timestamp and entities recover automatically — no reload. Typed-error chaining and refresh serialization unchanged; the token is never logged (error messages carry only method/path/status).
  • __init__.py: setup passes staleness_seconds from entry options.
  • options_flow.py: new required field, validated (positive int, >= the effective update interval — submit value, else stored/default; dedicated invalid_staleness error), raw submits omitting it keep the stored value.
  • strings.json + translations/en.json: field label + error string.
  • Coordinator constructor additionally floors an invalid/too-short threshold at max(DEFAULT, interval) so the cache can never be dead on arrival.

Tests

  • tests/test_coordinator.py: within threshold serves the cached snapshot (same data, success stays True, warning logged); past threshold raises UpdateFailed (existing test updated to advance the fake clock past the default threshold — all its original assertions kept); API-back recovery without reload, cache re-arms with the fresh stamp; setup wiring + constructor fallbacks. Clock driven deterministically by monkeypatching the _monotonic seam (FakeMonotonic).
  • tests/test_options_flow.py: staleness validation (below interval refused incl. stored-interval path, positive-int, boundary-inclusive save, defaults, round-trip, strings coverage); FULL_INPUT/schema-defaults tests extended for the new key.

Checks (TZ=UTC):

  • uv run pytest -q → 1827 passed (baseline 1815 + 12 new)
  • uv run pytest -q tests/test_coordinator.py → 18 passed

Maestro task: d25b875f-611b-418a-be2f-3026ef69b26e — Last-good snapshot cache and staleness handling (Feature 18: HA Integration as API Client).

## Done-condition The integration caches the last-good snapshot: with the API stopped, the panel renders the last-good data and entities become unavailable only after a configured staleness threshold; when the API returns, entities recover without a reload. Both behaviours are covered by tests (API down within threshold serves cached data; past threshold marks unavailable; API back recovers). ## What this does - `core/const.py`: new `CONF_SNAPSHOT_STALENESS` / `DEFAULT_SNAPSHOT_STALENESS` (seconds; default 3x the default update interval = 900 s, documented in the const comment). - `coordinator.py`: every successful pass caches the rebuilt snapshot stamped with `time.monotonic()` (via a `_monotonic()` seam). On a typed `NestQuestApiError`, if a cache exists and the elapsed time since the last success is within the threshold (inclusive), the pass serves the cached snapshot (entities stay available) and logs a clear warning; otherwise it raises `UpdateFailed` as before (typed error logged + chained). A later success refreshes the cache/timestamp and entities recover automatically — no reload. Typed-error chaining and refresh serialization unchanged; the token is never logged (error messages carry only method/path/status). - `__init__.py`: setup passes `staleness_seconds` from entry options. - `options_flow.py`: new required field, validated (positive int, >= the effective update interval — submit value, else stored/default; dedicated `invalid_staleness` error), raw submits omitting it keep the stored value. - `strings.json` + `translations/en.json`: field label + error string. - Coordinator constructor additionally floors an invalid/too-short threshold at `max(DEFAULT, interval)` so the cache can never be dead on arrival. ## Tests - `tests/test_coordinator.py`: within threshold serves the cached snapshot (same data, success stays True, warning logged); past threshold raises `UpdateFailed` (existing test updated to advance the fake clock past the default threshold — all its original assertions kept); API-back recovery without reload, cache re-arms with the fresh stamp; setup wiring + constructor fallbacks. Clock driven deterministically by monkeypatching the `_monotonic` seam (`FakeMonotonic`). - `tests/test_options_flow.py`: staleness validation (below interval refused incl. stored-interval path, positive-int, boundary-inclusive save, defaults, round-trip, strings coverage); `FULL_INPUT`/schema-defaults tests extended for the new key. Checks (TZ=UTC): - `uv run pytest -q` → 1827 passed (baseline 1815 + 12 new) - `uv run pytest -q tests/test_coordinator.py` → 18 passed Maestro task: d25b875f-611b-418a-be2f-3026ef69b26e — Last-good snapshot cache and staleness handling (Feature 18: HA Integration as API Client).
dev_agent added 1 commit 2026-09-23 08:04:39 -04:00
WHY: any single typed fetch failure was a failed pass, so the whole
board flipped unavailable on one dropped poll and stayed dark until the
API answered again — even though HA's coordinator machinery keeps the
last good data on a failed pass and the panel could safely keep
rendering it for a while.

The coordinator now caches every successful snapshot with its
time.monotonic stamp; while the elapsed time since the last success is
within the configured staleness threshold (CONF_SNAPSHOT_STALENESS,
seconds, default 3x the update interval = 900 s) a typed failure serves
that cache instead: the panel keeps rendering last-good data, entities
stay available, and the outage is logged as a clear warning.  Past the
threshold — or before the first success — the pass fails as before
(typed error logged and chained into UpdateFailed); a later successful
fetch refreshes the cache and the entities recover with no config-entry
reload.  The threshold rides the options flow (validated as a positive
int at least the update interval, with a dedicated error string) and
the strings files; the clock is read through a _monotonic seam so the
staleness tests drive it deterministically.
review_agent left a comment
Member

Independent Claude Sonnet 5 review completed for this exact commit.

Independent Claude Sonnet 5 review completed for this exact commit.
dev_agent merged commit 684f52ece8 into feature/430f7f98-7650-43e9-aa98-4b340303602a-ha-integration-as-api-client 2026-09-23 08:08:07 -04:00
Sign in to join this conversation.
No Reviewers
No labels
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Maniacs_Mansion/NestQuest#170