Skip to content

Commit 7250a5d

Browse files
committed
Improve BatteryManager error reporting
A `KeyError` was raised to indicate errors while getting inverters for batteries, but this have a couple of issues. First, the `KeyError` was converted to a string to get an error message, but `KeyError` is a very special error, and the argument is interpreted as a *key*, not as an error message, so when converting the error to a `str`, the result is the `repr()` of the *key*, so the strings are quoted and escaped, which is not what we want to report errors downstream. Second, one of the reported errors isn't really a `KeyError`, it is just a mismatch between what is requested and the current microgrid topology, so using a `KeyError` was completely wrong. Since these errors were only used to report errors from a direct call, we change `_get_components_data()` to return `list[InvBatPair] | str`, where if there is an error, a `str` with the error description is returned directly. As an extra, we sort the IDs when used in error messages, so they are easier to read for users, and more deterministic for tests. Tests are also simplified to remove the use of regex, which were probably only used because string were quoted. Signed-off-by: Leandro Lucarella <[email protected]>
1 parent 69b79a9 commit 7250a5d

File tree

2 files changed

+28
-29
lines changed

2 files changed

+28
-29
lines changed

src/frequenz/sdk/microgrid/_power_distributing/_component_managers/_battery_manager.py

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -224,17 +224,18 @@ async def _get_distribution(self, request: Request) -> DistributionResult | Resu
224224
Returns:
225225
Distribution of the batteries.
226226
"""
227-
try:
228-
pairs_data: list[InvBatPair] = self._get_components_data(
229-
request.component_ids
230-
)
231-
except KeyError as err:
232-
return Error(request=request, msg=str(err))
227+
match self._get_components_data(request.component_ids):
228+
case str() as err:
229+
return Error(request=request, msg=err)
230+
case list() as pairs_data:
231+
pass
232+
case unexpected:
233+
typing.assert_never(unexpected)
233234

234235
if not pairs_data:
235236
error_msg = (
236-
"No data for at least one of the given "
237-
f"batteries {str(request.component_ids)}"
237+
"No data for at least one of the given batteries: "
238+
+ self._str_ids(request.component_ids)
238239
)
239240
return Error(request=request, msg=str(error_msg))
240241

@@ -510,18 +511,17 @@ def nan_metric_in_list(data: list[DataType], metrics: list[str]) -> bool:
510511

511512
def _get_components_data(
512513
self, batteries: collections.abc.Set[int]
513-
) -> list[InvBatPair]:
514+
) -> list[InvBatPair] | str:
514515
"""Get data for the given batteries and adjacent inverters.
515516
516517
Args:
517518
batteries: Batteries that needs data.
518519
519-
Raises:
520-
KeyError: If any battery in the given list doesn't exists in microgrid.
521-
522520
Returns:
523-
Pairs of battery and adjacent inverter data.
521+
Pairs of battery and adjacent inverter data or an error message if there was
522+
an error while getting the data.
524523
"""
524+
inverter_ids: collections.abc.Set[int]
525525
pairs_data: list[InvBatPair] = []
526526

527527
working_batteries = self._component_pool_status_tracker.get_working_components(
@@ -530,9 +530,9 @@ def _get_components_data(
530530

531531
for battery_id in working_batteries:
532532
if battery_id not in self._battery_caches:
533-
raise KeyError(
533+
return (
534534
f"No battery {battery_id}, "
535-
f"available batteries: {list(self._battery_caches.keys())}"
535+
f"available batteries: {self._str_ids(self._battery_caches.keys())}"
536536
)
537537

538538
connected_inverters = _get_all_from_map(self._bat_invs_map, batteries)
@@ -545,9 +545,10 @@ def _get_components_data(
545545

546546
if batteries_from_inverters != batteries:
547547
extra_batteries = batteries_from_inverters - batteries
548-
raise KeyError(
549-
f"Inverters {_get_all_from_map(self._bat_invs_map, extra_batteries)} "
550-
f"are connected to batteries that were not requested: {extra_batteries}"
548+
inverter_ids = _get_all_from_map(self._bat_invs_map, extra_batteries)
549+
return (
550+
f"Inverter(s) ({self._str_ids(inverter_ids)}) are connected to "
551+
f"battery(ies) ({self._str_ids(extra_batteries)}) that were not requested"
551552
)
552553

553554
# set of set of batteries one for each working_battery
@@ -556,7 +557,7 @@ def _get_components_data(
556557
)
557558

558559
for battery_ids in battery_sets:
559-
inverter_ids: frozenset[int] = self._bat_invs_map[next(iter(battery_ids))]
560+
inverter_ids = self._bat_invs_map[next(iter(battery_ids))]
560561

561562
data = self._get_battery_inverter_data(battery_ids, inverter_ids)
562563
if data is None:
@@ -570,6 +571,9 @@ def _get_components_data(
570571
pairs_data.append(data)
571572
return pairs_data
572573

574+
def _str_ids(self, ids: collections.abc.Set[int]) -> str:
575+
return ", ".join(str(cid) for cid in sorted(ids))
576+
573577
def _get_power_distribution(
574578
self, request: Request, inv_bat_pairs: list[InvBatPair]
575579
) -> DistributionResult:

tests/microgrid/power_distributing/test_power_distributing.py

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77

88
import asyncio
99
import math
10-
import re
1110
from collections import abc
1211
from typing import TypeVar
1312
from unittest.mock import MagicMock
@@ -474,7 +473,7 @@ async def test_two_batteries_one_broken_one_inverters(
474473
assert result.request == request
475474
assert (
476475
result.msg
477-
== "No data for at least one of the given batteries {9, 19}"
476+
== "No data for at least one of the given batteries: 9, 19"
478477
)
479478

480479
async def test_battery_two_inverters(self, mocker: MockerFixture) -> None:
@@ -821,13 +820,10 @@ async def test_connected_but_not_requested_batteries(
821820
result: Result = done.pop().result()
822821
assert isinstance(result, Error)
823822
assert result.request == request
824-
825-
err_msg = re.search(
826-
r"'Inverters \{48\} are connected to batteries that were not "
827-
r"requested: \{19\}'",
828-
result.msg,
823+
assert (
824+
result.msg
825+
== "Inverter(s) (48) are connected to battery(ies) (19) that were not requested"
829826
)
830-
assert err_msg is not None
831827

832828
async def test_battery_soc_nan(self, mocker: MockerFixture) -> None:
833829
"""Test if battery with SoC==NaN is not used."""
@@ -1054,8 +1050,7 @@ async def test_power_distributor_invalid_battery_id(
10541050
result: Result = done.pop().result()
10551051
assert isinstance(result, Error)
10561052
assert result.request == request
1057-
err_msg = re.search(r"No battery 100, available batteries:", result.msg)
1058-
assert err_msg is not None
1053+
assert result.msg == "No battery 100, available batteries: 9, 19, 29"
10591054

10601055
async def test_power_distributor_one_user_adjust_power_consume(
10611056
self, mocker: MockerFixture

0 commit comments

Comments
 (0)