Skip to content

Fix: re-raise exceptions instead of silently swallowing them - #27

Open
luizbon wants to merge 1 commit into
CharlesGillanders:mainfrom
luizbon:fix/propagate-api-exceptions
Open

Fix: re-raise exceptions instead of silently swallowing them#27
luizbon wants to merge 1 commit into
CharlesGillanders:mainfrom
luizbon:fix/propagate-api-exceptions

Conversation

@luizbon

@luizbon luizbon commented Jul 28, 2026

Copy link
Copy Markdown

Fixes #26.

Problem

17 wrapper methods (getESSList, getSumDataForCustomer, getOneDateEnergyBySn, getLastPowerData, getChargeConfigInfo, getDisChargeConfigInfo, getEvChargerConfigList, setEvChargerCurrentsBySn, getEvChargerCurrentsBySn, getEvChargerStatusBySn, remoteControlEvCharger, bindSn, getVerificationCode, unBindSn, updateChargeConfigInfo, updateDisChargeConfigInfo) catch exceptions from api_get()/api_post(), log them, and return None without re-raising — even though api_get/api_post themselves already correctly raise after logging. Full detail and reproduction in #26.

Net effect: any real failure (DNS blip, connection reset, etc.) reaching one of these methods comes back to the caller as None. homeassistant-alphaESS's coordinator.py calls these methods directly and has except (aiohttp.ClientConnectorError, ...) specifically to let Home Assistant's DataUpdateCoordinator mark a failed update and retry with backoff — but that path can't be reached, because the exception never leaves this library. Observed effect on my own instance: entities stuck unavailable for 5+ hours after a 3-minute network outage, on a 60-second poll interval, until a manual config-entry reload.

Fix

Add raise after the existing logger.error(f"Error: {e} when calling {resource}") in each of the 17 methods — one line each, no other behavioural change. This matches the pattern already used correctly elsewhere in the same file: api_get, api_post, getdata, authenticate, setbatterycharge, setbatterydischarge all already do logger.error(...); raise.

Also bumped setup.py to 0.0.20 following the convention from #25.

Testing

I don't have aiohttp/voluptuous installable in my current environment (no pip/venv available) to run this live against a real or mocked API failure, so I can't attach a test run. What I did verify:

  • python3 -m py_compile alphaess/alphaess.py passes
  • Grepped for the exact swallow pattern (logger.error(f"Error: {e} when calling {resource}")) — confirmed exactly 17 occurrences, all now followed by raise
  • Manually traced every one of the 17 methods to confirm the try/except structure and indentation are unchanged aside from the added line — this is a purely additive change (raise re-raising the currently-caught exception), so there's no new control-flow path introduced, only the existing one becoming reachable
  • Confirmed getdata() and authenticate()'s existing if units is None: guards (from Guard against getESSList() returning None ('NoneType' object is not iterable) #25) are untouched and still meaningful — api_get() can still legitimately return None on a successful-but-empty API response (not just on exceptions), so those guards aren't made redundant by this change

Happy to add a proper mocked test if there's an existing test setup I should target — didn't see one in the repo.

Every wrapper method that calls api_get()/api_post() (getESSList,
getSumDataForCustomer, getOneDateEnergyBySn, getLastPowerData,
getChargeConfigInfo, getDisChargeConfigInfo, getEvChargerConfigList,
and 10 others) caught all exceptions, logged them, and returned None
- without re-raising. api_get()/api_post() themselves already do this
correctly (log then `raise`), but every caller one level up undid
that by catching-and-swallowing again.

Consequence: a transient failure (DNS blip, timeout, connection
reset) anywhere in these calls comes back to the caller as a plain
None instead of an exception. Home Assistant's alphaess integration
(coordinator.py) wraps its calls in try/except for
aiohttp.ClientConnectorError etc. specifically so HA's
DataUpdateCoordinator can mark the update as failed and retry with
backoff - but that except block can never fire, because the
exception never reaches it. The coordinator treats the None as a
normal (if empty) result, so entities can get stuck without ever
triggering HA's retry path, sometimes for hours after the underlying
network issue has already cleared.

This matches CharlesGillanders/homeassistant-alphaESS#110
("happens at least once a day, a quick reload sorts it") and the
traceback in CharlesGillanders/homeassistant-alphaESS#114
('NoneType' object is not iterable, from getESSList() returning None).
PR CharlesGillanders#25 added `is None` guards around the two call sites inside
getdata()/authenticate() to stop that traceback, but
homeassistant-alphaESS's coordinator.py doesn't call getdata()/
authenticate() - it calls these per-field methods directly, so the
guard doesn't apply there, and the underlying swallow is still
present for every other caller of these methods.

Fix: add `raise` after the existing `logger.error(...)` call in each
of the 17 affected methods, matching the pattern already used
correctly by api_get, api_post, getdata, authenticate,
setbatterycharge, and setbatterydischarge in this same file. Logging
behaviour is unchanged; the only difference is the exception now also
propagates to the caller.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Wrapper methods swallow exceptions instead of re-raising, defeating callers' retry/error handling

1 participant