Skip to content

Commit 910bd9c

Browse files
Refactor power distribution Result (#659)
Since Frequenz-SDK is already using Python 3.11 as a minimum version, Result can now be defined as a union type of the different results instead of using inheritance.
2 parents 5fe0adf + 202a1e5 commit 910bd9c

File tree

2 files changed

+89
-20
lines changed

2 files changed

+89
-20
lines changed

RELEASE_NOTES.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,24 @@
1010

1111
- The battery pool metric methods no longer return `None` when no batteries are available. Instead, the value of the `Sample` or `PowerMetric` is set to `None`.
1212

13+
- The power distribution `Result` is now a union of all different types of results rather than a base class. This means we can now also use `match` to check for result types instead of only using `isinstance()`. The following example shows how `Result` can be used for matching power distribution results:
14+
15+
```python
16+
from typing import assert_never
17+
result: Result = some_operation()
18+
match result:
19+
case Success() as success:
20+
print(f"Power request was successful: {success}")
21+
case PartialFailure() as partial_failure:
22+
print(f"Power request was partially successful: {partial_failure}")
23+
case OutOfBounds() as out_of_bounds:
24+
print(f"Power request was out of bounds: {out_of_bounds}")
25+
case Error() as error:
26+
print(f"Power request failed: {error}")
27+
case _ as unreachable:
28+
assert_never(unreachable)
29+
```
30+
1331
## New Features
1432

1533
<!-- Here goes the main new features and examples or instructions on how to use them -->

src/frequenz/sdk/actor/power_distributing/result.py

Lines changed: 71 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,6 @@ class _BaseResultMixin:
1919
"""The user's request to which this message responds."""
2020

2121

22-
# When moving to Python 3.10+ we should replace this with an union type:
23-
# Result = Success | PartialFailure | Error | OutOfBound | Ignored
24-
# For now it can't be done because before 3.10 isinstance(result, Success)
25-
# doesn't work, so it is hard to figure out what type of result you got in
26-
# a forward compatible way.
27-
# When moving we should use the _BaseResultMixin as a base class for all
28-
# results.
29-
@dataclasses.dataclass
30-
class Result(_BaseResultMixin):
31-
"""Power distribution result."""
32-
33-
3422
@dataclasses.dataclass
3523
class _BaseSuccessMixin:
3624
"""Result returned when setting the power succeed for all batteries."""
@@ -48,19 +36,19 @@ class _BaseSuccessMixin:
4836
"""
4937

5038

51-
# We need to put the _BaseSuccessMixin before Result in the inheritance list to
52-
# make sure that the Result attributes appear before the _BaseSuccessMixin,
53-
# otherwise the request attribute will be last in the dataclass constructor
54-
# because of how MRO works.
39+
# We need to put the _BaseSuccessMixin before _BaseResultMixin in the
40+
# inheritance list to make sure that the _BaseResultMixin attributes appear
41+
# before the _BaseSuccessMixin, otherwise the request attribute will be last
42+
# in the dataclass constructor because of how MRO works.
5543

5644

5745
@dataclasses.dataclass
58-
class Success(_BaseSuccessMixin, Result): # Order matters here. See above.
46+
class Success(_BaseSuccessMixin, _BaseResultMixin): # Order matters here. See above.
5947
"""Result returned when setting the power succeeded for all batteries."""
6048

6149

6250
@dataclasses.dataclass
63-
class PartialFailure(_BaseSuccessMixin, Result):
51+
class PartialFailure(_BaseSuccessMixin, _BaseResultMixin):
6452
"""Result returned when any battery failed to perform the request."""
6553

6654
failed_power: Power
@@ -71,7 +59,7 @@ class PartialFailure(_BaseSuccessMixin, Result):
7159

7260

7361
@dataclasses.dataclass
74-
class Error(Result):
62+
class Error(_BaseResultMixin):
7563
"""Result returned when an error occurred and power was not set at all."""
7664

7765
msg: str
@@ -96,7 +84,7 @@ class PowerBounds:
9684

9785

9886
@dataclasses.dataclass
99-
class OutOfBounds(Result):
87+
class OutOfBounds(_BaseResultMixin):
10088
"""Result returned when the power was not set because it was out of bounds.
10189
10290
This result happens when the originating request was done with
@@ -109,3 +97,66 @@ class OutOfBounds(Result):
10997
If the requested power negative, then this value is the lower bound.
11098
Otherwise it is upper bound.
11199
"""
100+
101+
102+
Result = Success | PartialFailure | Error | OutOfBounds
103+
"""Power distribution result.
104+
105+
Example: Handling power distribution results
106+
107+
```python
108+
from typing import assert_never
109+
110+
from frequenz.sdk.actor.power_distributing import (
111+
Error,
112+
OutOfBounds,
113+
PartialFailure,
114+
Result,
115+
Success,
116+
)
117+
from frequenz.sdk.actor.power_distributing.request import Request
118+
from frequenz.sdk.actor.power_distributing.result import PowerBounds
119+
from frequenz.sdk.timeseries._quantities import Power
120+
121+
def handle_power_request_result(result: Result) -> None:
122+
match result:
123+
case Success() as success:
124+
print(f"Power request was successful: {success}")
125+
case PartialFailure() as partial_failure:
126+
print(f"Power request was partially successful: {partial_failure}")
127+
case OutOfBounds() as out_of_bounds:
128+
print(f"Power request was out of bounds: {out_of_bounds}")
129+
case Error() as error:
130+
print(f"Power request failed: {error}")
131+
case _ as unreachable:
132+
assert_never(unreachable)
133+
134+
request = Request(
135+
namespace="TestChannel",
136+
power=Power.from_watts(123.4),
137+
batteries={8, 18},
138+
)
139+
140+
results: list[Result] = [
141+
Success(
142+
request,
143+
succeeded_power=Power.from_watts(123.4),
144+
succeeded_batteries={8, 18},
145+
excess_power=Power.zero(),
146+
),
147+
PartialFailure(
148+
request,
149+
succeeded_power=Power.from_watts(103.4),
150+
succeeded_batteries={8},
151+
excess_power=Power.zero(),
152+
failed_batteries={18},
153+
failed_power=Power.from_watts(20.0),
154+
),
155+
OutOfBounds(request, bounds=PowerBounds(0, 0, 0, 800)),
156+
Error(request, msg="The batteries are not available"),
157+
]
158+
159+
for r in results:
160+
handle_power_request_result(r)
161+
```
162+
"""

0 commit comments

Comments
 (0)