Skip to content

Commit 5889f3a

Browse files
fix(ha): drop, not raise, a not-an-outage error at runtime
Re-raising HA_NOT_OUTAGE_ERRORS from every call put back main's behaviour of one bad runtime call (a NaN attribute in set_state, a proxy redirect loop) unwinding the whole update cycle. Only initialize() needs to fail fast, so re-raise only for its untracked calls; at runtime log the error and return None without touching the outage timer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 31648ea commit 5889f3a

2 files changed

Lines changed: 33 additions & 19 deletions

File tree

‎apps/predbat/ha.py‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1311,9 +1311,14 @@ def api_call(self, endpoint, data_in=None, post=False, core=True, silent=False,
13111311
if track:
13121312
self.ha_unavailable("connection error from {}".format(endpoint))
13131313
data = None
1314-
except HA_NOT_OUTAGE_ERRORS:
1315-
# Not an outage and waiting will not fix it, so let it fail loudly as before
1316-
raise
1314+
except HA_NOT_OUTAGE_ERRORS as e:
1315+
# Not an outage and waiting will not fix it: initialize() lets it fail at once naming the real problem,
1316+
# while at runtime one bad call is logged and dropped rather than aborting the whole update cycle
1317+
if not track_outage:
1318+
raise
1319+
if not silent:
1320+
self.log("Warn: Request error from {}: {}".format(url, e))
1321+
data = None
13171322
except requests.exceptions.RequestException as e:
13181323
# Anything else requests raises while Home Assistant is going down, e.g. a connection reset part way through the body
13191324
if not silent:

‎apps/predbat/tests/test_hainterface_api.py‎

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -691,7 +691,7 @@ def test_hainterface_api_call_auth_rejected_repeatedly_is_fatal(my_predbat=None)
691691

692692

693693
def test_hainterface_api_call_config_error_not_outage(my_predbat=None):
694-
"""Test api_call() lets errors that are not Home Assistant being down (bad ha_url/ha_key, redirect loop, unencodable body) escape rather than recording an outage"""
694+
"""Test errors that are not Home Assistant being down (bad ha_url/ha_key, redirect loop, unencodable body) are never an outage: dropped at runtime, raised for initialize()"""
695695
print("\n=== Testing HAInterface api_call() errors that are not an outage ===")
696696
failed = 0
697697

@@ -701,21 +701,30 @@ def test_hainterface_api_call_config_error_not_outage(my_predbat=None):
701701
("post", requests.exceptions.InvalidJSONError("Out of range float values are not JSON compliant")),
702702
]
703703
for method, error in cases:
704-
mock_base = MockBase()
705-
ha_interface = create_ha_interface(mock_base, ha_key="test_key", db_enable=False, db_mirror_ha=False, db_primary=False)
706-
with patch("ha.requests." + method) as mock_call:
707-
mock_call.side_effect = error
708-
raised = False
709-
try:
710-
ha_interface.api_call("/api/states/sensor.test", data_in={"state": 1} if method == "post" else None, post=method == "post")
711-
except type(error):
712-
raised = True
713-
714-
if not raised or ha_interface.outage.since is not None:
715-
print(f"ERROR: expected {type(error).__name__} to escape without an outage, got raised={raised} / {ha_interface.outage.since}")
716-
failed += 1
717-
else:
718-
print(f"✓ {type(error).__name__} escaped and was not an outage")
704+
name = type(error).__name__
705+
for track_outage in (True, False):
706+
mock_base = MockBase()
707+
ha_interface = create_ha_interface(mock_base, ha_key="test_key", db_enable=False, db_mirror_ha=False, db_primary=False)
708+
with patch("ha.requests." + method) as mock_call:
709+
mock_call.side_effect = error
710+
raised = False
711+
result = "not set"
712+
try:
713+
result = ha_interface.api_call("/api/states/sensor.test", data_in={"state": 1} if method == "post" else None, post=method == "post", track_outage=track_outage)
714+
except type(error):
715+
raised = True
716+
717+
if ha_interface.outage.since is not None:
718+
print(f"ERROR: {name} should not be recorded as an outage")
719+
failed += 1
720+
elif track_outage and (raised or result is not None):
721+
print(f"ERROR: at runtime {name} should be logged and dropped, got raised={raised} result={result}")
722+
failed += 1
723+
elif not track_outage and not raised:
724+
print(f"ERROR: an untracked call (initialize) should let {name} escape")
725+
failed += 1
726+
else:
727+
print(f"✓ {name} with track_outage={track_outage} handled and not an outage")
719728

720729
return failed
721730

0 commit comments

Comments
 (0)