Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
actor = ConfigManagingActor(config_files=["config.toml"])
```

* The `MovingWindow` now take all arguments as keyword-only to avoid mistakes.

## New Features

- The `ConfigManagingActor` can now take multiple configuration files as input, allowing to override default configurations with custom configurations.
Expand Down
4 changes: 3 additions & 1 deletion benchmarks/timeseries/periodic_feature_extractor.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ async def init_feature_extractor(
# We only need the moving window to initialize the PeriodicFeatureExtractor class.
lm_chan = Broadcast[Sample[Quantity]](name="lm_net_power")
async with MovingWindow(
timedelta(seconds=1), lm_chan.new_receiver(), timedelta(seconds=1)
size=timedelta(seconds=1),
resampled_data_recv=lm_chan.new_receiver(),
input_sampling_period=timedelta(seconds=1),
) as moving_window:
await lm_chan.new_sender().send(
Sample(datetime.now(tz=timezone.utc), Quantity(0))
Expand Down
26 changes: 13 additions & 13 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,44 +47,44 @@ dev-flake8 = [
"flake8 == 7.1.1",
"flake8-docstrings == 1.7.0",
"flake8-pyproject == 1.2.3", # For reading the flake8 config from pyproject.toml
"pydoclint == 0.5.6",
"pydoclint == 0.5.9",
"pydocstyle == 6.3.0",
]
dev-formatting = ["black == 24.8.0", "isort == 5.13.2"]
dev-formatting = ["black == 24.10.0", "isort == 5.13.2"]
dev-mkdocs = [
"black == 24.8.0",
"black == 24.10.0",
"Markdown==3.7",
"mike == 2.1.3",
"mkdocs-gen-files == 0.5.0",
"mkdocs-literate-nav == 0.6.1",
"mkdocs-macros-plugin == 1.0.5",
"mkdocs-material == 9.5.34",
"mkdocstrings[python] == 0.25.2",
"mkdocstrings-python == 1.10.9",
"mkdocs-macros-plugin == 1.3.7",
"mkdocs-material == 9.5.43",
"mkdocstrings[python] == 0.26.2",
"mkdocstrings-python == 1.12.2",
"frequenz-repo-config[lib] == 0.10.0",
]
dev-mypy = [
"mypy == 1.11.2",
"mypy == 1.13.0",
"types-Markdown == 3.7.0.20240822",
"types-protobuf == 5.27.0.20240626",
"types-protobuf == 5.28.3.20241030",
"types-setuptools == 74.0.0.20240831",
# For checking the noxfile, docs/ script, and tests
"frequenz-sdk[dev-mkdocs,dev-noxfile,dev-pytest]",
]
dev-noxfile = ["nox == 2024.4.15", "frequenz-repo-config[lib] == 0.10.0"]
dev-noxfile = ["nox == 2024.10.9", "frequenz-repo-config[lib] == 0.10.0"]
dev-pylint = [
"pylint == 3.2.7",
"pylint == 3.3.1",
# For checking the noxfile, docs/ script, and tests
"frequenz-sdk[dev-mkdocs,dev-noxfile,dev-pytest]",
]
dev-pytest = [
"pytest == 8.3.2",
"pytest == 8.3.3",
"frequenz-repo-config[extra-lint-examples] == 0.10.0",
"pytest-mock == 3.14.0",
"pytest-asyncio == 0.24.0",
"time-machine == 2.12.0",
"async-solipsism == 0.7",
"hypothesis == 6.111.2",
"hypothesis == 6.116.0",
]
dev = [
"frequenz-sdk[dev-mkdocs,dev-flake8,dev-formatting,dev-mkdocs,dev-mypy,dev-noxfile,dev-pylint,dev-pytest]",
Expand Down
6 changes: 3 additions & 3 deletions src/frequenz/sdk/_internal/_channels.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,20 +153,20 @@ def get_or_create(self, message_type: type[T], key: str) -> Broadcast[T]:

entry = self._channels[key]
if entry.message_type is not message_type:
exception = ValueError(
error_message = (
f"Type mismatch, a channel for key {key!r} exists and the requested "
f"message type {message_type} is not the same as the existing "
f"message type {entry.message_type}."
)
if _logger.isEnabledFor(logging.DEBUG):
_logger.debug(
"%s at:\n%s",
str(exception),
error_message,
# We skip the last frame because it's this method, and limit the
# stack to 9 frames to avoid adding too much noise.
"".join(traceback.format_stack(limit=10)[:9]),
)
raise exception
raise ValueError(error_message)

return typing.cast(Broadcast[T], entry.channel)

Expand Down
4 changes: 3 additions & 1 deletion src/frequenz/sdk/actor/_background_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,9 @@ def cancel(self, msg: str | None = None) -> None:
for task in self._tasks:
task.cancel(msg)

async def stop(self, msg: str | None = None) -> None:
# We need the noqa because pydoclint can't figure out `rest` is
# a `BaseExceptionGroup` instance.
async def stop(self, msg: str | None = None) -> None: # noqa: DOC503
"""Stop this background service.

This method cancels all running tasks spawned by this service and waits for them
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class ComponentPoolStatusTracker:

def __init__( # pylint: disable=too-many-arguments
self,
*,
component_ids: abc.Set[int],
component_status_sender: Sender[ComponentPoolStatus],
max_data_age: timedelta,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ class BatteryStatusTracker(ComponentStatusTracker, BackgroundService):
@override
def __init__( # pylint: disable=too-many-arguments
self,
*,
component_id: int,
max_data_age: timedelta,
max_blocking_duration: timedelta,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ class ComponentStatusTracker(BackgroundService, ABC):
@abstractmethod
def __init__( # pylint: disable=too-many-arguments,super-init-not-called
self,
*,
component_id: int,
max_data_age: timedelta,
max_blocking_duration: timedelta,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ class EVChargerStatusTracker(ComponentStatusTracker, BackgroundService):
@override
def __init__( # pylint: disable=too-many-arguments
self,
*,
component_id: int,
max_data_age: timedelta,
max_blocking_duration: timedelta,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class PVInverterStatusTracker(ComponentStatusTracker, BackgroundService):
@override
def __init__( # pylint: disable=too-many-arguments
self,
*,
component_id: int,
max_data_age: timedelta,
max_blocking_duration: timedelta,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,7 @@ def _compute_battery_availability_ratio(

def _distribute_power( # pylint: disable=too-many-arguments
self,
*,
components: list[InvBatPair],
power_w: float,
available_soc: dict[int, float],
Expand Down Expand Up @@ -730,7 +731,11 @@ def _distribute_consume_power(
)

return self._distribute_power(
components, power_w, available_soc, incl_bounds, excl_bounds
components=components,
power_w=power_w,
available_soc=available_soc,
incl_bounds=incl_bounds,
excl_bounds=excl_bounds,
)

def _distribute_supply_power(
Expand Down Expand Up @@ -761,7 +766,11 @@ def _distribute_supply_power(
)

result: DistributionResult = self._distribute_power(
components, -1 * power_w, available_soc, incl_bounds, excl_bounds
components=components,
power_w=-1 * power_w,
available_soc=available_soc,
incl_bounds=incl_bounds,
excl_bounds=excl_bounds,
)

for inverter_id in result.distribution.keys():
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ class PowerManagingActor(Actor): # pylint: disable=too-many-instance-attributes

def __init__( # pylint: disable=too-many-arguments
self,
*,
proposals_receiver: Receiver[Proposal],
bounds_subscription_receiver: Receiver[ReportRequest],
power_distributing_requests_sender: Sender[_power_distributing.Request],
power_distributing_results_receiver: Receiver[_power_distributing.Result],
channel_registry: ChannelRegistry,
*,
component_category: ComponentCategory,
component_type: ComponentType | None = None,
# arguments to actors need to serializable, so we pass an enum for the algorithm
Expand Down
21 changes: 12 additions & 9 deletions src/frequenz/sdk/timeseries/_moving_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import math
from collections.abc import Sequence
from datetime import datetime, timedelta
from typing import SupportsIndex, overload
from typing import SupportsIndex, assert_never, overload
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assert never doesn't seem to be used? at least not in the commit where it is added

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


import numpy as np
from frequenz.channels import Broadcast, Receiver, Sender
Expand Down Expand Up @@ -130,12 +130,12 @@ async def run() -> None:

def __init__( # pylint: disable=too-many-arguments
self,
*,
size: timedelta,
resampled_data_recv: Receiver[Sample[Quantity]],
input_sampling_period: timedelta,
resampler_config: ResamplerConfig | None = None,
align_to: datetime = UNIX_EPOCH,
*,
name: str | None = None,
) -> None:
"""
Expand Down Expand Up @@ -282,7 +282,7 @@ def at(self, key: int | datetime) -> float: # pylint: disable=invalid-name
assert timestamp is not None
return self._buffer[self._buffer.to_internal_index(timestamp)]

raise TypeError("Key has to be either a timestamp or an integer.")
assert_never(key)

def window(
self,
Expand Down Expand Up @@ -385,7 +385,10 @@ def __getitem__(self, key: datetime) -> float:
def __getitem__(self, key: slice) -> ArrayLike:
"""See the main __getitem__ method."""

def __getitem__(self, key: SupportsIndex | datetime | slice) -> float | ArrayLike:
# We need the noqa because `IndexError` is raised indirectly by `at()` and `window()`
def __getitem__( # noqa: DOC503
self, key: SupportsIndex | datetime | slice
) -> float | ArrayLike:
"""
Return a sub window of the `MovingWindow`.

Expand All @@ -400,12 +403,15 @@ def __getitem__(self, key: SupportsIndex | datetime | slice) -> float | ArrayLik
where the bounds correspond to the slice bounds.
Note that a half open interval, which is open at the end, is returned.

Note:
Slicing with a step other than 1 is not supported.

Args:
key: Either an integer or a timestamp or a slice of timestamps or integers.

Raises:
IndexError: when requesting an out of range timestamp or index
TypeError: when the key is not a datetime or slice object.
ValueError: when requesting a slice with a step other than 1

Returns:
A float if the key is a number or a timestamp.
Expand All @@ -422,10 +428,7 @@ def __getitem__(self, key: SupportsIndex | datetime | slice) -> float | ArrayLik
if isinstance(key, SupportsIndex):
return self.at(key.__index__())

raise TypeError(
"Key has to be either a timestamp or an integer "
"or a slice of timestamps or integers"
)
assert_never(key)


# We need to register the class as a subclass of Sequence like this because
Expand Down
4 changes: 3 additions & 1 deletion src/frequenz/sdk/timeseries/_resampling.py
Original file line number Diff line number Diff line change
Expand Up @@ -801,7 +801,9 @@ async def _receive_samples(self) -> None:
if sample.value is not None and not sample.value.isnan():
self._helper.add_sample(sample)

async def resample(self, timestamp: datetime) -> None:
# We need the noqa because pydoclint can't figure out that `recv_exception` is an
# `Exception` instance.
async def resample(self, timestamp: datetime) -> None: # noqa: DOC503
"""Calculate a new sample for the passed `timestamp` and send it.

The helper is used to calculate the new sample and the sender is used
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class BatteryPoolReferenceStore: # pylint: disable=too-many-instance-attributes

def __init__( # pylint: disable=too-many-arguments
self,
*,
channel_registry: ChannelRegistry,
resampler_subscription_sender: Sender[ComponentMetricRequest],
batteries_status_receiver: Receiver[ComponentPoolStatus],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class EVChargerPoolReferenceStore:

def __init__( # pylint: disable=too-many-arguments
self,
*,
channel_registry: ChannelRegistry,
resampler_subscription_sender: Sender[ComponentMetricRequest],
status_receiver: Receiver[ComponentPoolStatus],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,12 @@ def from_string(
return self._string_engines[channel_key]

builder = ResampledFormulaBuilder(
self._namespace,
formula,
self._channel_registry,
self._resampler_subscription_sender,
component_metric_id,
Quantity,
namespace=self._namespace,
formula_name=formula,
channel_registry=self._channel_registry,
resampler_subscription_sender=self._resampler_subscription_sender,
metric_id=component_metric_id,
create_method=Quantity,
)
formula_engine = builder.from_string(formula, nones_are_zeros=nones_are_zeros)
self._string_engines[channel_key] = formula_engine
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,8 @@ def generate(
ComponentNotFound: if there are no batteries in the component graph, or if
they don't have an inverter as a predecessor.
FormulaGenerationError: If a battery has a non-inverter predecessor
in the component graph.
FormulaGenerationError: If not all batteries behind a set of inverters
have been requested.
in the component graph, or if not all batteries behind a set of
inverters have been requested.
"""
builder = self._get_builder(
"battery-power", ComponentMetricId.ACTIVE_POWER, Power.from_watts
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ def _are_grid_meters(self, grid_successors: set[Component]) -> bool:
for successor in grid_successors
)

def generate(self) -> FormulaEngine[Power]:
# We need the noqa here because `RuntimeError` is raised indirectly
def generate(self) -> FormulaEngine[Power]: # noqa: DOC503
"""Generate formula for calculating consumer power from the component graph.

Returns:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,12 +91,12 @@ def _get_builder(
create_method: Callable[[float], QuantityT],
) -> ResampledFormulaBuilder[QuantityT]:
builder = ResampledFormulaBuilder(
self._namespace,
name,
self._channel_registry,
self._resampler_subscription_sender,
component_metric_id,
create_method,
namespace=self._namespace,
formula_name=name,
channel_registry=self._channel_registry,
resampler_subscription_sender=self._resampler_subscription_sender,
metric_id=component_metric_id,
create_method=create_method,
)
return builder

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class ResampledFormulaBuilder(FormulaBuilder[QuantityT]):

def __init__( # pylint: disable=too-many-arguments
self,
*,
namespace: str,
formula_name: str,
channel_registry: ChannelRegistry,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class PVPoolReferenceStore:

def __init__( # pylint: disable=too-many-arguments
self,
*,
channel_registry: ChannelRegistry,
resampler_subscription_sender: Sender[ComponentMetricRequest],
status_receiver: Receiver[ComponentPoolStatus],
Expand Down
2 changes: 1 addition & 1 deletion tests/actor/_power_managing/test_matryoshka.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def __init__(
self._system_bounds = system_bounds
self.algorithm = Matryoshka(max_proposal_age=timedelta(seconds=60.0))

def tgt_power( # pylint: disable=too-many-arguments
def tgt_power( # pylint: disable=too-many-arguments,too-many-positional-arguments
self,
priority: int,
power: float | None,
Expand Down
Loading
Loading