chore: full code review — bugs, error handling, tech debt, docs, tests, features#5
Merged
Merged
Conversation
…s, features Phase 1 — Critical Bug Fixes: - Fix systemic or-None/truthiness patterns dropping valid 0/0.0 values across models (temperature, signal strength, humidity, setpoints) - Fix AUTH bare except: narrow to QuiltAuthError|ClientError in auth.py - Fix AUTO mode setpoint: apply deadband clamp before setpoint routing - Fix settings bool coercion: string 'false' no longer coerces to True - Fix login() to clear _system_id/_snapshot_cache on re-authentication - Fix stream reconnect: add subscription lock + logging for safety Phase 2 — Error Handling: - Add grpc_call() context manager with QuiltConnectionError for UNAVAILABLE - Add _run() error handling: QuiltError/QuiltAuthError → friendly CLI messages - Add graceful invalid HVAC mode input handling in CLI set-space command - Re-export QuiltStreamError from quilt_hp.__init__ Phase 3 — Documentation Sync: - Fix stale dict-based SystemSnapshot examples → list + apply_*() pattern - Document 15+ missing public types (enums, energy, sensors, comfort) - Document all NotifierStream callback and lifecycle methods - Document QuiltClient.close() and get_current_token() Phase 4 — Technical Debt: - Replace assert guards with explicit QuiltError raises in client.py - Deduplicate snapshot resolution with _resolve_system_id() helper - Centralize CLI login/snapshot boilerplate - Extract shared model helpers to models/_helpers.py - Use atomic file writes (temp + os.replace) in cli/store.py - Replace raw int hvac_mode with HVACMode enum in schedule.py - Add MetricBucketStatus enum; use in energy.py and services/system.py - Close() now clears _hds/_sysinfo/_user_svc service refs Phase 5 — Performance & Features: - Add structured logging (DEBUG/INFO/WARNING) across all core modules - Add NotifierStream health properties: is_connected, last_event_at, stream_state - Add stream event debouncing: debounce_s parameter on NotifierStream - Add grpc_call retry: max_retries, retry_delay, retry_backoff with backoff - Cache inspect.signature() result in transport interceptor - Verify async context manager closes channel cleanly Phase 6 — Test Coverage (+44 tests, 182→226, 82.7%→84.5%): - Add tests/test_models_extra.py: SoftwareUpdateInfo, EnergyBucket, ComfortSetting, Controller, OutdoorUnit, RemoteSensor conversions - Add tests/test_hds_payloads.py: gRPC payload contract tests for HEAT/COOL/AUTO/STANDBY modes and deadband clamping - Add tests/test_streaming_health.py: connection health properties - Add tests/test_grpc_retry.py: transient error retry logic - Add tests/test_streaming_debounce.py: event coalescing - Add tests/test_streaming_concurrency.py: start/stop/subscribe races - Populate tests/conftest.py with shared _ns, fake_space, fake_snapshot Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR is a broad correctness + resiliency pass across the async Quilt client library, focused on fixing proto “zero value” handling, improving gRPC error translation/retry behavior, hardening streaming reconnect/concurrency, expanding structured logging, and syncing docs/tests to the updated public API.
Changes:
- Introduces a shared
grpc_call()context manager with optional transient-error retries; applies it toSystemInformationService. - Refactors
NotifierStreamfor safer reconnect/subscription concurrency and adds stream health + debouncing features. - Fixes multiple model conversion edge cases (especially “0” preservation), expands docs, and adds extensive new test coverage.
Reviewed changes
Copilot reviewed 46 out of 46 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/test_transport_interceptor_extra.py | Adds coverage for refresh-callback signature caching behavior. |
| tests/test_streaming.py | Adds regression test for resubscription behavior after queue reset. |
| tests/test_streaming_reconnect_dispatch_extra.py | Validates reconnect queue reset logging + behavior. |
| tests/test_streaming_health.py | New tests for stream health properties and lifecycle state exposure. |
| tests/test_streaming_debounce.py | New tests for stream update debouncing/coalescing. |
| tests/test_streaming_concurrency.py | New tests for start/stop/subscribe race handling. |
| tests/test_models_from_proto.py | Expands model conversion tests, especially for “zero value preserved” cases. |
| tests/test_models_extra.py | New model conversion coverage for additional entities and edge cases. |
| tests/test_hds_payloads.py | Validates HDS UpdateSpace payload shaping (including AUTO deadband clamp). |
| tests/test_grpc_retry.py | Tests grpc_call() translation + retry/backoff behavior. |
| tests/test_client_service_error_paths.py | Tests client “requires login” guards and close() cleanup. |
| tests/test_cli_surfaces_extra.py | Verifies CLI surfaces friendly auth/general error handling. |
| tests/test_cli_login.py | Aligns CLI login test to raise QuiltAuthError for OTP-required path. |
| tests/test_auth_store_settings_edges.py | Adjusts settings-store expectation for boolean coercion. |
| tests/conftest.py | Adds shared fixtures/helpers (_ns, _make_header, fake snapshot/space). |
| src/quilt_hp/transport.py | Adds logging + refresh callback signature caching. |
| src/quilt_hp/services/user.py | Adds debug logging around user RPCs. |
| src/quilt_hp/services/system.py | Uses grpc_call() for error translation and wraps energy status in enum. |
| src/quilt_hp/services/streaming.py | Major streaming refactor: locks, reconnect behavior, debouncing, health properties. |
| src/quilt_hp/services/hds.py | Adds structured RPC logging and fixes AUTO deadband clamp ordering. |
| src/quilt_hp/services/init.py | Adds grpc_call() helper with exception translation and optional retries. |
| src/quilt_hp/models/space.py | Fixes setpoint display logic to avoid dropping valid 0 values. |
| src/quilt_hp/models/sensor.py | Preserves explicit zero values when parsing sensor state. |
| src/quilt_hp/models/schedule.py | Changes ScheduleEvent.hvac_mode to HVACMode enum. |
| src/quilt_hp/models/qsm.py | Uses shared WiFi parsing helper to preserve zeros. |
| src/quilt_hp/models/outdoor_unit.py | Uses shared hardware lookup; improves performance_data presence detection. |
| src/quilt_hp/models/indoor_unit.py | Fixes louver-mode parsing to avoid truthiness dropping valid zeros. |
| src/quilt_hp/models/enums.py | Introduces MetricBucketStatus enum. |
| src/quilt_hp/models/energy.py | Switches bucket status to enum; adds validity helpers. |
| src/quilt_hp/models/controller.py | Uses shared hardware lookup + WiFi parsing that preserves zero signal. |
| src/quilt_hp/models/_helpers.py | New shared helpers for hardware lookup and WiFi parsing. |
| src/quilt_hp/models/init.py | Re-exports MetricBucketStatus. |
| src/quilt_hp/client.py | Adds logging; replaces asserts with guards; refactors snapshot/system resolution; adds debounce option. |
| src/quilt_hp/cli/store.py | Adds logging and atomic token writes. |
| src/quilt_hp/cli/settings.py | Fixes boolean coercion for use_fahrenheit. |
| src/quilt_hp/cli/main.py | Centralizes login/snapshot boilerplate and adds friendly error handling. |
| src/quilt_hp/auth.py | Narrows exception handling; adds structured logging and safer ExpiresIn handling. |
| src/quilt_hp/init.py | Re-exports QuiltStreamError. |
| docs/reference/models.md | Updates model and streaming docs to match list-based snapshot + new callbacks. |
| docs/reference/client.md | Updates lifecycle + documents close() and get_current_token(). |
| docs/how-to/tui-app.md | Updates TUI example to merge stream diffs and handle fatal stream error. |
| docs/how-to/stream-updates.md | Updates streaming how-to with additional callbacks and lifecycle patterns. |
| docs/how-to/control-spaces.md | Fixes snapshot list indexing in examples. |
| docs/how-to/configure-schedules.md | Updates schedule examples for new model structure. |
| docs/how-to/configure-comfort-settings.md | Updates comfort-setting examples for list-based snapshot. |
| docs/how-to/automation-daemon.md | Updates daemon example to apply stream diffs and align fields with new models. |
- energy.py: has_missing_energy_value now treats None (non-float) as missing, not just NaN; total_kwh safely skips None values - transport.py: replace id()-keyed signature cache with WeakKeyDictionary to prevent unbounded growth and id-reuse bugs after GC - cli/store.py: create temp token file with O_CREAT|0o600 permissions upfront so tokens are never transiently world-readable - services/__init__.py: avoid self-chaining (raise wrapped from exc) when wrapped is exc (QuiltError passthrough case) - docs/how-to/configure-schedules.md: pass hvac_mode enum instance not .value in ScheduleEvent examples - docs/reference/models.md: update ScheduleEvent.hvac_mode int->HVACMode and EnergyBucket.status int->MetricBucketStatus in reference signatures - tests: update test_energy_bucket_none_does_not_raise to assert correct behavior (None is now correctly treated as missing) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- transport.py: wrap WeakKeyDictionary.get() in try/except TypeError (non-weakrefable callables raise on .get() too, not just .set()) - services/system.py: guard MetricBucketStatus() conversion against unknown/future server enum values — falls back to UNSPECIFIED instead of raising ValueError and failing the entire get_energy_metrics call - docs/reference/models.md: make NotifierStream.create() example self-contained by defining get_metadata() inline instead of referencing the undefined metadata_provider variable Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Full code review pass covering correctness, error handling, performance, technical debt, documentation sync, and new features. All 31 identified issues addressed.
Before → After: 182 tests → 226 tests (+44), 82.7% → 84.5% coverage, ruff/mypy/docs all green.
Phase 1 — Critical Bug Fixes
or None/if valuepatterns were silently dropping legitimate0/0.0values (temperatures, signal strength, humidity, setpoints) across 8+ model files. Replaced with explicitis not Nonechecks and proto-sentinel-aware guards.cool - heat >= 2.5°C) was applied after setpoint routing, so the clamped value was never used. Fixed ordering.bool("false")isTrue. Replaced withisinstance(uf, bool)guard so only actual booleans are accepted.login()didn't clear_system_id,_system_name, or_snapshot_cache, causing stale data to be returned for a new session.except Exception— Narrowed toQuiltAuthError | ClientErrorso programming errors inside refresh hooks are no longer silently swallowed._topicsis the source of truth).Phase 2 — Error Handling
grpc_call()context manager (services/__init__.py) — Shared translator that mapsAioRpcErrortoQuiltConnectionError(transient) orQuiltError(other), and wraps unexpected non-gRPC exceptions. Applied tosystem.pyas demonstration; other services can adopt incrementally._run()now catchesQuiltAuthError/QuiltErrorand prints formatted messages instead of stack traces.QuiltAuthErrorspecifically (not all exceptions) on silent-login attempt.KeyError.QuiltStreamErrorre-exported fromquilt_hp.__init__.Phase 3 — Documentation Sync
SystemSnapshotexamples in 4 doc files → list iteration +apply_*()methods.NotifierStreamcallback and lifecycle methods (on_outdoor_unit_update,subscribe,run_forever, etc.).QuiltClient.close()andget_current_token().Phase 4 — Technical Debt
assert self._hds is not Noneinclient.pyreplaced with explicitQuiltErrorraises (asserts are disabled with-O)._resolve_system_id()and_resolve_snapshot_item()helpers inclient.py.models/_helpers.py— Extracted sharedlookup_hardware()and WiFi-info parsing fromcontroller.py,outdoor_unit.py,qsm.py.cli/store.pytoken writes now use temp file +os.replace()to prevent corruption on crash.HVACModeenum —ScheduleEvent.hvac_modechanged from rawinttoHVACMode.MetricBucketStatusenum — Added toenums.py; used inenergy.pyandservices/system.py.close()— Now clears_hds,_sysinfo,_user_svcto prevent accidental reuse.Phase 5 — Performance & New Features
logging.getLogger(__name__)added to auth, transport, client, all services, and store. DEBUG/INFO/WARNING at appropriate callsites. No secrets logged.NotifierStream.is_connected,last_event_at,stream_stateproperties for building health dashboards.debounce_sparameter onNotifierStream(and exposed viaQuiltClient.stream()) coalesces rapid events to reduce redundant processing.grpc_call(max_retries=N, retry_delay=1.0, retry_backoff=2.0)adds opt-in exponential backoff for transient errors. Defaultmax_retries=0preserves current behavior.inspect.signature()result in transport interceptor is cached after first call.Phase 6 — Test Coverage (+44 tests)
test_models_extra.pyfrom_proto()test_hds_payloads.pytest_streaming_health.pyis_connected,last_event_at,stream_statetest_grpc_retry.pytest_streaming_debounce.pytest_streaming_concurrency.pyconftest.py_ns,fake_space,fake_snapshotfixturesChecklist
ruff check src/ tests/— cleanruff format --check src/ tests/— cleanmypy src/quilt_hp/— clean (43 files)pytest— 226 passed, 84.46% coveragemkdocs build --strict— clean