Opower: fall back to usage-only reads when cost endpoint fails#169224
Opower: fall back to usage-only reads when cost endpoint fails#169224loganrosen wants to merge 3 commits into
Conversation
When the Opower cost API returns an error (e.g., HTTP 500) for daily or hourly cost reads, fall back to the coarser-resolution data that was already fetched successfully, rather than re-raising the exception and crashing the coordinator. Previously, any cost read failure at the daily or hourly level would propagate up and crash the DataUpdateCoordinator. On retry, the coordinator would call async_login() again within the same 30-second TOTP window, causing the reused code to be rejected. This created a permanent reauth repair loop with no user-recoverable path. Now, if daily reads fail, the coordinator returns the monthly (BILL) data. If hourly reads fail, it returns the monthly+daily data. The monthly (BILL) handler still raises on failure since there is no coarser fallback. CostRead objects contain both consumption and cost, so returning the coarser reads preserves consumption statistics at reduced granularity rather than losing all data. Fixes home-assistant#169223 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Hey there @tronikos, mind taking a look at this pull request as it has been labeled with an integration ( Code owner commandsCode owners of
|
There was a problem hiding this comment.
Pull request overview
This PR updates the Opower integration’s coordinator to avoid crashing the DataUpdateCoordinator when finer-grained cost read endpoints (daily/hourly) error, falling back to coarser data that was already fetched successfully.
Changes:
- In
_async_get_cost_reads(), return already-fetched coarser cost reads when daily/hourly requests raiseApiException, instead of re-raising. - Adjust coordinator API-exception tests to no longer expect DAY/HOUR failures to raise.
- Add a new test ensuring daily/hourly API errors don’t crash the coordinator (fallback behavior).
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
homeassistant/components/opower/coordinator.py |
Implements fallback to coarser cost reads on DAY/HOUR API exceptions via early return + warning logs. |
tests/components/opower/test_coordinator.py |
Updates exception expectations and adds a new test for fallback behavior on DAY/HOUR failures. |
- Fix hourly fallback log message to say 'using coarser reads' since cost_reads may be monthly-only if the daily call also failed - Add async_wait_recording_done after _async_update_data in fallback test to avoid flaky pending recorder writes Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
tronikos
left a comment
There was a problem hiding this comment.
What read_resolution does the API return?
Weird that it advertises it supports granular reads but it only supports reads at the bill level.
Is that also the case in your utility website energy dashboard?
Does the GraphQL endpoint (sorry I haven't had time to review your PR) return granular data?
When the cost endpoint fails for daily/hourly reads, retry with usage_only=True to hit the usage-only endpoint instead. This preserves consumption data at the original granularity (daily/hourly) rather than falling back to monthly data. Some utilities like ConEd return HTTP 500 from the cost endpoint for daily/hourly aggregation but the usage-only endpoint works fine. If the usage-only endpoint also fails, fall back to the coarser data that was already fetched.
|
From the diagnostics, the account reports
The usage data exists at all granularities — it's specifically the REST cost endpoint that 500s for DAY/HOUR. Two things worth noting:
On the GraphQL side, I pushed a fix to opower#177 for a ConEd-specific issue: the |
| except ApiException: | ||
| _LOGGER.warning("Usage-only daily reads also failed, using monthly") |
There was a problem hiding this comment.
Log the usage-only daily ApiException details (e.g., include the exception in the warning) so operators can see why both the primary and fallback calls failed.
| except ApiException: | |
| _LOGGER.warning("Usage-only daily reads also failed, using monthly") | |
| except ApiException as err: | |
| _LOGGER.warning( | |
| "Usage-only daily reads also failed, using monthly: %s", err | |
| ) |
| except ApiException: | ||
| _LOGGER.warning( | ||
| "Usage-only hourly reads also failed, using coarser reads" |
There was a problem hiding this comment.
Log the usage-only hourly ApiException details (e.g., include the exception in the warning) so failures don’t become silent when both the primary and fallback calls error.
| except ApiException: | |
| _LOGGER.warning( | |
| "Usage-only hourly reads also failed, using coarser reads" | |
| except ApiException as err: | |
| _LOGGER.warning( | |
| "Usage-only hourly reads also failed, using coarser reads: %s", | |
| err, |
|
|
||
|
|
There was a problem hiding this comment.
Add assertions that the fallback path was actually exercised (e.g., that async_get_cost_reads was awaited with usage_only=True for the failing aggregate type) so this test fails if the coordinator regresses back to raising or stops invoking the fallback.
| await_calls = mock_opower_api.async_get_cost_reads.await_args_list | |
| assert any( | |
| call.args[1] == failing_aggregate_type | |
| and not call.kwargs.get("usage_only", False) | |
| for call in await_calls | |
| ) | |
| assert any( | |
| call.args[1] == failing_aggregate_type | |
| and call.kwargs.get("usage_only") is True | |
| for call in await_calls | |
| ) |
|
Change the library instead. There is already related code here: |
|
Makes sense. The exact response from the cost endpoint is: So it's throwing an |
Proposed change
When the Opower cost API returns an error (e.g., HTTP 500) for daily or hourly cost reads, retry with the usage-only endpoint before falling back to coarser data. This preserves consumption statistics at the original granularity (daily/hourly) rather than degrading to monthly.
Some utilities like ConEd return HTTP 500 from the REST cost endpoint for daily/hourly aggregation, while the usage-only endpoint works fine at those granularities. The account reports
read_resolution: QUARTER_HOUR, and granular data does exist — it's specifically the cost endpoint that fails.The fallback chain is now:
Previously, any cost read failure at the daily or hourly level would crash the
DataUpdateCoordinator. On retry,async_login()would be called again within the same TOTP window, causing the reused code to be rejected — creating a permanent reauth repair loop with no user-recoverable path.Type of change
Additional information
Checklist
Fixes #169223