Skip to content

Commit 69aad56

Browse files
authored
Cleanup *Report classes as simple protocols with only methods (#981)
This was required to remove the explicit casting of the `_Report` type to the `*PoolReport` protocol types. Closes #823
2 parents 1976fc1 + c8ab45d commit 69aad56

File tree

7 files changed

+30
-85
lines changed

7 files changed

+30
-85
lines changed

src/frequenz/sdk/actor/_power_managing/_base_classes.py

Lines changed: 1 addition & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -46,60 +46,8 @@ def get_channel_name(self) -> str:
4646
return f"power_manager.report.{self.component_ids=}.{self.priority=}"
4747

4848

49-
class Report(typing.Protocol):
50-
"""Current PowerManager report for a set of components.
51-
52-
This protocol can be specialized by different component pools to provide more
53-
specific details and documentation for the reports.
54-
"""
55-
56-
@property
57-
def bounds(self) -> timeseries.Bounds[Power] | None:
58-
"""The bounds for the components.
59-
60-
These bounds are adjusted to any restrictions placed by actors with higher
61-
priorities.
62-
63-
There might be exclusion zones within these bounds. If necessary, the
64-
`adjust_to_bounds` method may be used to check if a desired power value fits the
65-
bounds, or to get the closest possible power values that do fit the bounds.
66-
"""
67-
68-
@abc.abstractmethod
69-
def adjust_to_bounds(self, power: Power) -> tuple[Power | None, Power | None]:
70-
"""Adjust a power value to the bounds.
71-
72-
This method can be used to adjust a desired power value to the power bounds
73-
available to the actor.
74-
75-
If the given power value falls within the usable bounds, it will be returned
76-
unchanged.
77-
78-
If it falls outside the usable bounds, the closest possible value on the
79-
corresponding side will be returned. For example, if the given power is lower
80-
than the lowest usable power, only the lowest usable power will be returned, and
81-
similarly for the highest usable power.
82-
83-
If the given power falls within an exclusion zone that's contained within the
84-
usable bounds, the closest possible power values on both sides will be returned.
85-
86-
!!! note
87-
It is completely optional to use this method to adjust power values before
88-
proposing them, because the PowerManager will do this automatically. This
89-
method is provided for convenience, and for granular control when there are
90-
two possible power values, both of which fall within the available bounds.
91-
92-
Args:
93-
power: The power value to adjust.
94-
95-
Returns:
96-
A tuple of the closest power values to the desired power that fall within
97-
the available bounds for the actor.
98-
"""
99-
100-
10149
@dataclasses.dataclass(frozen=True, kw_only=True)
102-
class _Report(Report):
50+
class _Report:
10351
"""Current PowerManager report for a set of components."""
10452

10553
target_power: Power | None

src/frequenz/sdk/timeseries/battery_pool/_battery_pool.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
import uuid
1313
from collections import abc
1414
from datetime import timedelta
15-
from typing import cast
1615

1716
from ... import timeseries
1817
from ..._internal._channels import ReceiverFetcher
@@ -401,9 +400,7 @@ def power_status(self) -> ReceiverFetcher[BatteryPoolReport]:
401400
)
402401
channel.resend_latest = True
403402

404-
# More details on why the cast is needed here:
405-
# https://github.com/frequenz-floss/frequenz-sdk-python/issues/823
406-
return cast(ReceiverFetcher[BatteryPoolReport], channel)
403+
return channel
407404

408405
@property
409406
def _system_power_bounds(self) -> ReceiverFetcher[SystemBounds]:

src/frequenz/sdk/timeseries/battery_pool/_result_types.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,28 @@
44
"""Types for exposing battery pool reports."""
55

66
import abc
7+
import typing
78

89
from ...actor import power_distributing
9-
from ...actor._power_managing._base_classes import Report
1010
from .._base_types import Bounds
1111
from .._quantities import Power
1212

1313

1414
# This class is used to expose the generic reports from the PowerManager with specific
1515
# documentation for the battery pool.
16-
class BatteryPoolReport(Report):
16+
class BatteryPoolReport(typing.Protocol):
1717
"""A status report for a battery pool."""
1818

19-
target_power: Power | None
20-
"""The currently set power for the batteries."""
19+
@property
20+
def target_power(self) -> Power | None:
21+
"""The currently set power for the batteries."""
2122

22-
distribution_result: power_distributing.Result | None
23-
"""The result of the last power distribution.
23+
@property
24+
def distribution_result(self) -> power_distributing.Result | None:
25+
"""The result of the last power distribution.
2426
25-
This is `None` if no power distribution has been performed yet.
26-
"""
27+
This is `None` if no power distribution has been performed yet.
28+
"""
2729

2830
@property
2931
def bounds(self) -> Bounds[Power] | None:

src/frequenz/sdk/timeseries/ev_charger_pool/_ev_charger_pool.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66

77
import asyncio
8-
import typing
98
import uuid
109
from collections import abc
1110
from datetime import timedelta
@@ -230,9 +229,7 @@ def power_status(self) -> ReceiverFetcher[EVChargerPoolReport]:
230229
)
231230
channel.resend_latest = True
232231

233-
# More details on why the cast is needed here:
234-
# https://github.com/frequenz-floss/frequenz-sdk-python/issues/823
235-
return typing.cast(ReceiverFetcher[EVChargerPoolReport], channel)
232+
return channel
236233

237234
async def stop(self) -> None:
238235
"""Stop all tasks and channels owned by the EVChargerPool."""

src/frequenz/sdk/timeseries/ev_charger_pool/_result_types.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,16 @@
1313
class EVChargerPoolReport(typing.Protocol):
1414
"""A status report for an EV chargers pool."""
1515

16-
target_power: Power | None
17-
"""The currently set power for the EV chargers."""
16+
@property
17+
def target_power(self) -> Power | None:
18+
"""The currently set power for the EV chargers."""
1819

19-
distribution_result: power_distributing.Result | None
20-
"""The result of the last power distribution.
20+
@property
21+
def distribution_result(self) -> power_distributing.Result | None:
22+
"""The result of the last power distribution.
2123
22-
This is `None` if no power distribution has been performed yet.
23-
"""
24+
This is `None` if no power distribution has been performed yet.
25+
"""
2426

2527
@property
2628
def bounds(self) -> Bounds[Power] | None:

src/frequenz/sdk/timeseries/pv_pool/_pv_pool.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
"""Interactions with pools of PV inverters."""
55

66
import asyncio
7-
import typing
87
import uuid
98
from collections import abc
109
from datetime import timedelta
@@ -189,9 +188,7 @@ def power_status(self) -> ReceiverFetcher[PVPoolReport]:
189188
)
190189
channel.resend_latest = True
191190

192-
# More details on why the cast is needed here:
193-
# https://github.com/frequenz-floss/frequenz-sdk-python/issues/823
194-
return typing.cast(ReceiverFetcher[PVPoolReport], channel)
191+
return channel
195192

196193
async def stop(self) -> None:
197194
"""Stop all tasks and channels owned by the PVPool."""

src/frequenz/sdk/timeseries/pv_pool/_result_types.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,16 @@
1313
class PVPoolReport(typing.Protocol):
1414
"""A status report for a PV pool."""
1515

16-
target_power: Power | None
17-
"""The currently set power for the PV inverters."""
16+
@property
17+
def target_power(self) -> Power | None:
18+
"""The currently set power for the PV inverters."""
1819

19-
distribution_result: power_distributing.Result | None
20-
"""The result of the last power distribution.
20+
@property
21+
def distribution_result(self) -> power_distributing.Result | None:
22+
"""The result of the last power distribution.
2123
22-
This is `None` if no power distribution has been performed yet.
23-
"""
24+
This is `None` if no power distribution has been performed yet.
25+
"""
2426

2527
@property
2628
def bounds(self) -> Bounds[Power] | None:

0 commit comments

Comments
 (0)