🐛 fix: skip corrupt persisted energy state instead of failing setup #113

Merged
mat merged 1 commit from issue-56-storage-load into main 2026-09-04 21:23:33 +00:00
Owner

Follow-up to the fix attempt on GitHub issue MateoGreil/homeassistant-comwatt#56.

Why

Two independent bugs chain on affected accounts:

  1. Original error — some Comwatt accounts return time-series samples as JSON strings ("12.34"), so the v0.8.x site-energy folding (value < 0) crashes with TypeError: '<' not supported between instances of 'str' and 'int'. Fixed client-side by the fix-string-series-values branch of python-comwatt-client (normalize samples to float — to be released as 0.4.2).
  2. Follow-up error — with the patched client, setup still failed with:
    AttributeError: 'NoneType' object has no attribute 'get'
    coordinator.py, line 266, in async_load_energy_state
        state.totals = dict(site_dict.get("totals") or {})
    
    The user's .storage/comwatt.energy_state contains a __sites__ entry mapped to JSON null. No released version can write that shape (the save only writes dicts; stock v0.8.x failed its fetch before saving), so the file was corrupted externally — but the loader trusted the stored shape blindly, turning one malformed entry into a permanent, unrecoverable setup failure (a reinstall does not clear .storage).

What

async_load_energy_state becomes defensive:

  • non-dict site/device entries are skipped with a warning — affected counters re-seed from the server on the next fetch (sites: 8-day official history; devices: forward from the stream)
  • non-dict totals / folded_buckets are tolerated
  • timestamps parse through _parse_bucket_ts instead of datetime.fromisoformat, so unparseable values restore as None instead of raising

This also removes the need for the "delete .storage file" workaround — with this fix the corrupt entry is simply ignored and intact entries keep their continuity.

Tests

3 regression tests added (corrupt site entry, corrupt device entry, garbage timestamps); full suite 132 passed, ruff + mypy clean.

Follow-up to the fix attempt on GitHub issue MateoGreil/homeassistant-comwatt#56. ## Why Two independent bugs chain on affected accounts: 1. **Original error** — some Comwatt accounts return time-series samples as JSON strings (`"12.34"`), so the v0.8.x site-energy folding (`value < 0`) crashes with `TypeError: '<' not supported between instances of 'str' and 'int'`. Fixed client-side by the `fix-string-series-values` branch of python-comwatt-client (normalize samples to float — to be released as 0.4.2). 2. **Follow-up error** — with the patched client, setup still failed with: ``` AttributeError: 'NoneType' object has no attribute 'get' coordinator.py, line 266, in async_load_energy_state state.totals = dict(site_dict.get("totals") or {}) ``` The user's `.storage/comwatt.energy_state` contains a `__sites__` entry mapped to JSON `null`. No released version can write that shape (the save only writes dicts; stock v0.8.x failed its fetch before saving), so the file was corrupted externally — but the loader trusted the stored shape blindly, turning one malformed entry into a permanent, unrecoverable setup failure (a reinstall does not clear `.storage`). ## What `async_load_energy_state` becomes defensive: - non-dict site/device entries are skipped with a warning — affected counters re-seed from the server on the next fetch (sites: 8-day official history; devices: forward from the stream) - non-dict `totals` / `folded_buckets` are tolerated - timestamps parse through `_parse_bucket_ts` instead of `datetime.fromisoformat`, so unparseable values restore as `None` instead of raising This also removes the need for the "delete .storage file" workaround — with this fix the corrupt entry is simply ignored and intact entries keep their continuity. ## Tests 3 regression tests added (corrupt site entry, corrupt device entry, garbage timestamps); full suite 132 passed, ruff + mypy clean.
🐛 fix: skip corrupt persisted energy state instead of failing setup
All checks were successful
Validate / lint-ruff (push) Successful in 8s
Validate / test-pytest (push) Successful in 3m18s
Validate / type-check-mypy (push) Successful in 3m16s
Validate / lint-ruff (pull_request) Successful in 8s
Validate / test-pytest (pull_request) Successful in 3m22s
Validate / type-check-mypy (pull_request) Successful in 3m25s
149993ab77
Reported in #56: after the client-side string normalization fixed the
original TypeError, setup still failed with

    AttributeError: 'NoneType' object has no attribute 'get'
    coordinator.py, line 266, in async_load_energy_state
        state.totals = dict(site_dict.get("totals") or {})

because the persisted .storage/comwatt.energy_state contained a __sites__
entry mapped to JSON null. The loader trusted the stored shape blindly, so
one malformed entry permanently bricked the whole integration — a reinstall
does not clear .storage.

async_load_energy_state now skips non-dict site/device entries with a
warning (totals re-seed from the server's 8-day official history on the
next fetch), tolerates a non-dict totals/folded_buckets, and parses
timestamps through _parse_bucket_ts instead of datetime.fromisoformat so
unparseable values restore as None instead of raising.
mat merged commit 9bae8066d7 into main 2026-09-04 21:23:33 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
mat/homeassistant-comwatt!113
No description provided.