diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index c00209dc5..d509bf132 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -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. diff --git a/benchmarks/timeseries/periodic_feature_extractor.py b/benchmarks/timeseries/periodic_feature_extractor.py index 9a5bc765e..2b6c947e8 100644 --- a/benchmarks/timeseries/periodic_feature_extractor.py +++ b/benchmarks/timeseries/periodic_feature_extractor.py @@ -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)) diff --git a/pyproject.toml b/pyproject.toml index d5e1e0577..4678f17d0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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]", diff --git a/src/frequenz/sdk/_internal/_channels.py b/src/frequenz/sdk/_internal/_channels.py index eabd26892..74b2f6929 100644 --- a/src/frequenz/sdk/_internal/_channels.py +++ b/src/frequenz/sdk/_internal/_channels.py @@ -153,7 +153,7 @@ 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}." @@ -161,12 +161,12 @@ def get_or_create(self, message_type: type[T], key: str) -> Broadcast[T]: 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) diff --git a/src/frequenz/sdk/actor/_background_service.py b/src/frequenz/sdk/actor/_background_service.py index 6e6587094..2d44d2afe 100644 --- a/src/frequenz/sdk/actor/_background_service.py +++ b/src/frequenz/sdk/actor/_background_service.py @@ -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 diff --git a/src/frequenz/sdk/microgrid/_power_distributing/_component_pool_status_tracker.py b/src/frequenz/sdk/microgrid/_power_distributing/_component_pool_status_tracker.py index 09b18226e..c0aed7e2d 100644 --- a/src/frequenz/sdk/microgrid/_power_distributing/_component_pool_status_tracker.py +++ b/src/frequenz/sdk/microgrid/_power_distributing/_component_pool_status_tracker.py @@ -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, diff --git a/src/frequenz/sdk/microgrid/_power_distributing/_component_status/_battery_status_tracker.py b/src/frequenz/sdk/microgrid/_power_distributing/_component_status/_battery_status_tracker.py index bbc5161f0..dd181827c 100644 --- a/src/frequenz/sdk/microgrid/_power_distributing/_component_status/_battery_status_tracker.py +++ b/src/frequenz/sdk/microgrid/_power_distributing/_component_status/_battery_status_tracker.py @@ -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, diff --git a/src/frequenz/sdk/microgrid/_power_distributing/_component_status/_component_status.py b/src/frequenz/sdk/microgrid/_power_distributing/_component_status/_component_status.py index 34d5c5078..ce34befc5 100644 --- a/src/frequenz/sdk/microgrid/_power_distributing/_component_status/_component_status.py +++ b/src/frequenz/sdk/microgrid/_power_distributing/_component_status/_component_status.py @@ -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, diff --git a/src/frequenz/sdk/microgrid/_power_distributing/_component_status/_ev_charger_status_tracker.py b/src/frequenz/sdk/microgrid/_power_distributing/_component_status/_ev_charger_status_tracker.py index 5285239dd..48fe2478b 100644 --- a/src/frequenz/sdk/microgrid/_power_distributing/_component_status/_ev_charger_status_tracker.py +++ b/src/frequenz/sdk/microgrid/_power_distributing/_component_status/_ev_charger_status_tracker.py @@ -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, diff --git a/src/frequenz/sdk/microgrid/_power_distributing/_component_status/_pv_inverter_status_tracker.py b/src/frequenz/sdk/microgrid/_power_distributing/_component_status/_pv_inverter_status_tracker.py index f3ad7ebe5..39e38ba3c 100644 --- a/src/frequenz/sdk/microgrid/_power_distributing/_component_status/_pv_inverter_status_tracker.py +++ b/src/frequenz/sdk/microgrid/_power_distributing/_component_status/_pv_inverter_status_tracker.py @@ -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, diff --git a/src/frequenz/sdk/microgrid/_power_distributing/_distribution_algorithm/_battery_distribution_algorithm.py b/src/frequenz/sdk/microgrid/_power_distributing/_distribution_algorithm/_battery_distribution_algorithm.py index fccdb828b..b4e390e1d 100644 --- a/src/frequenz/sdk/microgrid/_power_distributing/_distribution_algorithm/_battery_distribution_algorithm.py +++ b/src/frequenz/sdk/microgrid/_power_distributing/_distribution_algorithm/_battery_distribution_algorithm.py @@ -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], @@ -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( @@ -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(): diff --git a/src/frequenz/sdk/microgrid/_power_managing/_power_managing_actor.py b/src/frequenz/sdk/microgrid/_power_managing/_power_managing_actor.py index 2211f1a3a..590ec2b6c 100644 --- a/src/frequenz/sdk/microgrid/_power_managing/_power_managing_actor.py +++ b/src/frequenz/sdk/microgrid/_power_managing/_power_managing_actor.py @@ -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 diff --git a/src/frequenz/sdk/timeseries/_moving_window.py b/src/frequenz/sdk/timeseries/_moving_window.py index c1cefb9de..4ed8e52ee 100644 --- a/src/frequenz/sdk/timeseries/_moving_window.py +++ b/src/frequenz/sdk/timeseries/_moving_window.py @@ -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 import numpy as np from frequenz.channels import Broadcast, Receiver, Sender @@ -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: """ @@ -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, @@ -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`. @@ -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. @@ -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 diff --git a/src/frequenz/sdk/timeseries/_resampling.py b/src/frequenz/sdk/timeseries/_resampling.py index c23dc0888..07c06361f 100644 --- a/src/frequenz/sdk/timeseries/_resampling.py +++ b/src/frequenz/sdk/timeseries/_resampling.py @@ -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 diff --git a/src/frequenz/sdk/timeseries/battery_pool/_battery_pool_reference_store.py b/src/frequenz/sdk/timeseries/battery_pool/_battery_pool_reference_store.py index cffbde00a..4e7e99fa4 100644 --- a/src/frequenz/sdk/timeseries/battery_pool/_battery_pool_reference_store.py +++ b/src/frequenz/sdk/timeseries/battery_pool/_battery_pool_reference_store.py @@ -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], diff --git a/src/frequenz/sdk/timeseries/ev_charger_pool/_ev_charger_pool_reference_store.py b/src/frequenz/sdk/timeseries/ev_charger_pool/_ev_charger_pool_reference_store.py index 1c42f6eb3..7bc7cc291 100644 --- a/src/frequenz/sdk/timeseries/ev_charger_pool/_ev_charger_pool_reference_store.py +++ b/src/frequenz/sdk/timeseries/ev_charger_pool/_ev_charger_pool_reference_store.py @@ -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], diff --git a/src/frequenz/sdk/timeseries/formula_engine/_formula_engine_pool.py b/src/frequenz/sdk/timeseries/formula_engine/_formula_engine_pool.py index 95ed22c59..dfd7b62c8 100644 --- a/src/frequenz/sdk/timeseries/formula_engine/_formula_engine_pool.py +++ b/src/frequenz/sdk/timeseries/formula_engine/_formula_engine_pool.py @@ -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 diff --git a/src/frequenz/sdk/timeseries/formula_engine/_formula_generators/_battery_power_formula.py b/src/frequenz/sdk/timeseries/formula_engine/_formula_generators/_battery_power_formula.py index 6080d9932..f011570c0 100644 --- a/src/frequenz/sdk/timeseries/formula_engine/_formula_generators/_battery_power_formula.py +++ b/src/frequenz/sdk/timeseries/formula_engine/_formula_generators/_battery_power_formula.py @@ -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 diff --git a/src/frequenz/sdk/timeseries/formula_engine/_formula_generators/_consumer_power_formula.py b/src/frequenz/sdk/timeseries/formula_engine/_formula_generators/_consumer_power_formula.py index 618b0f928..8e302ece2 100644 --- a/src/frequenz/sdk/timeseries/formula_engine/_formula_generators/_consumer_power_formula.py +++ b/src/frequenz/sdk/timeseries/formula_engine/_formula_generators/_consumer_power_formula.py @@ -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: diff --git a/src/frequenz/sdk/timeseries/formula_engine/_formula_generators/_formula_generator.py b/src/frequenz/sdk/timeseries/formula_engine/_formula_generators/_formula_generator.py index 2fe011c45..4a538b655 100644 --- a/src/frequenz/sdk/timeseries/formula_engine/_formula_generators/_formula_generator.py +++ b/src/frequenz/sdk/timeseries/formula_engine/_formula_generators/_formula_generator.py @@ -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 diff --git a/src/frequenz/sdk/timeseries/formula_engine/_resampled_formula_builder.py b/src/frequenz/sdk/timeseries/formula_engine/_resampled_formula_builder.py index 39b2fc76a..56001a129 100644 --- a/src/frequenz/sdk/timeseries/formula_engine/_resampled_formula_builder.py +++ b/src/frequenz/sdk/timeseries/formula_engine/_resampled_formula_builder.py @@ -24,6 +24,7 @@ class ResampledFormulaBuilder(FormulaBuilder[QuantityT]): def __init__( # pylint: disable=too-many-arguments self, + *, namespace: str, formula_name: str, channel_registry: ChannelRegistry, diff --git a/src/frequenz/sdk/timeseries/pv_pool/_pv_pool_reference_store.py b/src/frequenz/sdk/timeseries/pv_pool/_pv_pool_reference_store.py index caf9e42ce..57a05af4c 100644 --- a/src/frequenz/sdk/timeseries/pv_pool/_pv_pool_reference_store.py +++ b/src/frequenz/sdk/timeseries/pv_pool/_pv_pool_reference_store.py @@ -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], diff --git a/tests/actor/_power_managing/test_matryoshka.py b/tests/actor/_power_managing/test_matryoshka.py index 771783f18..6828f98a4 100644 --- a/tests/actor/_power_managing/test_matryoshka.py +++ b/tests/actor/_power_managing/test_matryoshka.py @@ -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, diff --git a/tests/actor/power_distributing/_component_status/test_battery_status.py b/tests/actor/power_distributing/_component_status/test_battery_status.py index 8daab8c4f..9f5d5ce02 100644 --- a/tests/actor/power_distributing/_component_status/test_battery_status.py +++ b/tests/actor/power_distributing/_component_status/test_battery_status.py @@ -41,7 +41,7 @@ from ....utils.receive_timeout import Timeout, receive_timeout -def battery_data( # pylint: disable=too-many-arguments +def battery_data( # pylint: disable=too-many-arguments,too-many-positional-arguments component_id: int, timestamp: datetime | None = None, relay_state: BatteryRelayState = BatteryRelayState.CLOSED, @@ -149,7 +149,7 @@ async def test_sync_update_status_with_messages( async with ( mock_microgrid, BatteryStatusTracker( - BATTERY_ID, + component_id=BATTERY_ID, max_data_age=timedelta(seconds=5), max_blocking_duration=timedelta(seconds=30), status_sender=status_channel.new_sender(), @@ -322,7 +322,7 @@ async def test_sync_blocking_feature(self, mocker: MockerFixture) -> None: BatteryStatusTracker( # increase max_data_age_sec for blocking tests. # Otherwise it will block blocking. - BATTERY_ID, + component_id=BATTERY_ID, max_data_age=timedelta(seconds=500), max_blocking_duration=timedelta(seconds=30), status_sender=status_channel.new_sender(), @@ -461,7 +461,7 @@ async def test_sync_blocking_interrupted_with_with_max_data( async with ( mock_microgrid, BatteryStatusTracker( - BATTERY_ID, + component_id=BATTERY_ID, max_data_age=timedelta(seconds=5), max_blocking_duration=timedelta(seconds=30), status_sender=status_channel.new_sender(), @@ -512,7 +512,7 @@ async def test_sync_blocking_interrupted_with_invalid_message( async with ( mock_microgrid, BatteryStatusTracker( - BATTERY_ID, + component_id=BATTERY_ID, max_data_age=timedelta(seconds=5), max_blocking_duration=timedelta(seconds=30), status_sender=status_channel.new_sender(), @@ -576,7 +576,7 @@ async def test_timers( async with ( mock_microgrid, BatteryStatusTracker( - BATTERY_ID, + component_id=BATTERY_ID, max_data_age=timedelta(seconds=5), max_blocking_duration=timedelta(seconds=30), status_sender=status_channel.new_sender(), @@ -645,7 +645,7 @@ async def test_async_battery_status(self, mocker: MockerFixture) -> None: async with ( mock_microgrid, BatteryStatusTracker( - BATTERY_ID, + component_id=BATTERY_ID, max_data_age=timedelta(seconds=5), max_blocking_duration=timedelta(seconds=30), status_sender=status_channel.new_sender(), @@ -733,7 +733,7 @@ async def setup_tracker( async with ( mock_microgrid, BatteryStatusTracker( - BATTERY_ID, + component_id=BATTERY_ID, max_data_age=timedelta(seconds=0.2), max_blocking_duration=timedelta(seconds=1), status_sender=status_channel.new_sender(), diff --git a/tests/actor/power_distributing/test_battery_distribution_algorithm.py b/tests/actor/power_distributing/test_battery_distribution_algorithm.py index efea3cf02..0ecf37a81 100644 --- a/tests/actor/power_distributing/test_battery_distribution_algorithm.py +++ b/tests/actor/power_distributing/test_battery_distribution_algorithm.py @@ -189,7 +189,11 @@ def test_distribute_power_one_battery(self) -> None: algorithm = BatteryDistributionAlgorithm(distributor_exponent=1) result = algorithm._distribute_power( # pylint: disable=protected-access - components, 650, available_soc, incl_bounds, excl_bounds + components=components, + power_w=650, + available_soc=available_soc, + incl_bounds=incl_bounds, + excl_bounds=excl_bounds, ) assert result.distribution == approx({1: 500}) @@ -210,7 +214,11 @@ def test_distribute_power_two_batteries_1(self) -> None: algorithm = BatteryDistributionAlgorithm(distributor_exponent=1) result = algorithm._distribute_power( # pylint: disable=protected-access - components, 600, available_soc, incl_bounds, excl_bounds + components=components, + power_w=600, + available_soc=available_soc, + incl_bounds=incl_bounds, + excl_bounds=excl_bounds, ) assert result.distribution == approx({1: 400, 3: 200}) @@ -231,7 +239,11 @@ def test_distribute_power_two_batteries_2(self) -> None: algorithm = BatteryDistributionAlgorithm(distributor_exponent=1) result = algorithm._distribute_power( # pylint: disable=protected-access - components, 600, available_soc, incl_bounds, excl_bounds + components=components, + power_w=600, + available_soc=available_soc, + incl_bounds=incl_bounds, + excl_bounds=excl_bounds, ) assert result.distribution == approx({1: 200, 3: 400}) @@ -254,7 +266,11 @@ def test_distribute_power_two_batteries_one_inverter(self) -> None: algorithm = BatteryDistributionAlgorithm(distributor_exponent=1) result = algorithm._distribute_power( # pylint: disable=protected-access - components, 600, available_soc, incl_bounds, excl_bounds + components=components, + power_w=600, + available_soc=available_soc, + incl_bounds=incl_bounds, + excl_bounds=excl_bounds, ) assert result.distribution == approx({1: 500}) @@ -276,7 +292,11 @@ def test_distribute_power_two_batteries_bounds(self) -> None: algorithm = BatteryDistributionAlgorithm(distributor_exponent=1) result = algorithm._distribute_power( # pylint: disable=protected-access - components, 600, available_soc, incl_bounds, excl_bounds + components=components, + power_w=600, + available_soc=available_soc, + incl_bounds=incl_bounds, + excl_bounds=excl_bounds, ) assert result.distribution == approx({1: 250, 3: 330}) @@ -300,7 +320,11 @@ def test_distribute_power_three_batteries(self) -> None: algorithm = BatteryDistributionAlgorithm(distributor_exponent=1) result = algorithm._distribute_power( # pylint: disable=protected-access - components, 1000, available_soc, incl_bounds, excl_bounds + components=components, + power_w=1000, + available_soc=available_soc, + incl_bounds=incl_bounds, + excl_bounds=excl_bounds, ) assert result.distribution == approx({1: 400, 3: 400, 5: 200}) @@ -324,7 +348,11 @@ def test_distribute_power_three_batteries_2(self) -> None: algorithm = BatteryDistributionAlgorithm(distributor_exponent=1) result = algorithm._distribute_power( # pylint: disable=protected-access - components, 1000, available_soc, incl_bounds, excl_bounds + components=components, + power_w=1000, + available_soc=available_soc, + incl_bounds=incl_bounds, + excl_bounds=excl_bounds, ) assert result.distribution == approx({1: 400, 3: 300, 5: 300}) @@ -348,7 +376,11 @@ def test_distribute_power_three_batteries_3(self) -> None: algorithm = BatteryDistributionAlgorithm(distributor_exponent=1) result = algorithm._distribute_power( # pylint: disable=protected-access - components, 1000, available_soc, incl_bounds, excl_bounds + components=components, + power_w=1000, + available_soc=available_soc, + incl_bounds=incl_bounds, + excl_bounds=excl_bounds, ) assert result.distribution == approx({1: 0, 3: 300, 5: 0}) diff --git a/tests/actor/test_run_utils.py b/tests/actor/test_run_utils.py index f56a380f9..38cc8be48 100644 --- a/tests/actor/test_run_utils.py +++ b/tests/actor/test_run_utils.py @@ -35,7 +35,7 @@ async def _run(self) -> None: """Run the faulty actor. Raises: - CancelledError: the exception causes the actor to be cancelled + asyncio.CancelledError: the exception causes the actor to be cancelled """ self.is_cancelled = True raise asyncio.CancelledError(f"Faulty Actor {self.name} failed") diff --git a/tests/timeseries/_battery_pool/test_battery_pool.py b/tests/timeseries/_battery_pool/test_battery_pool.py index df5191e49..cdb4d3aa6 100644 --- a/tests/timeseries/_battery_pool/test_battery_pool.py +++ b/tests/timeseries/_battery_pool/test_battery_pool.py @@ -263,7 +263,7 @@ async def run_scenarios( Raises: TimeoutError: If metric update was not received. - AssertError: If received metric is not as expected. + AssertionError: If received metric is not as expected. """ for idx, scenario in enumerate(scenarios): # Update data stream @@ -278,9 +278,9 @@ async def run_scenarios( # Wait for result and check if received expected message try: msg = await asyncio.wait_for(receiver.receive(), timeout=waiting_time_sec) - except TimeoutError as err: + except TimeoutError: _logger.error("Test scenario %d failed with timeout error.", idx) - raise err + raise if scenario.expected_result is None: assert msg is None @@ -288,9 +288,9 @@ async def run_scenarios( try: compare_messages(msg, scenario.expected_result) - except AssertionError as err: + except AssertionError: _logger.error("Test scenario: %d failed.", idx) - raise err + raise async def test_all_batteries_capacity( @@ -404,7 +404,8 @@ def compare_messages(msg: Any, expected_msg: Any) -> None: assert msg_dict == expected_dict -async def run_test_battery_status_channel( # pylint: disable=too-many-arguments +# pylint: disable-next=too-many-arguments,too-many-positional-arguments +async def run_test_battery_status_channel( battery_status_sender: Sender[ComponentPoolStatus], battery_pool_metric_receiver: Receiver[T], all_batteries: set[int], diff --git a/tests/timeseries/_formula_engine/utils.py b/tests/timeseries/_formula_engine/utils.py index b1e8f8230..2a745d547 100644 --- a/tests/timeseries/_formula_engine/utils.py +++ b/tests/timeseries/_formula_engine/utils.py @@ -29,12 +29,12 @@ def get_resampled_stream( # pylint: disable=protected-access builder = ResampledFormulaBuilder( - namespace, - "", - _data_pipeline._get()._channel_registry, - _data_pipeline._get()._resampling_request_sender(), - metric_id, - create_method, + namespace=namespace, + formula_name="", + channel_registry=_data_pipeline._get()._channel_registry, + resampler_subscription_sender=_data_pipeline._get()._resampling_request_sender(), + metric_id=metric_id, + create_method=create_method, ) # Resampled data is always `Quantity` type, so we need to convert it to the desired # output type. diff --git a/tests/timeseries/mock_microgrid.py b/tests/timeseries/mock_microgrid.py index 2fb5cca12..818bc9120 100644 --- a/tests/timeseries/mock_microgrid.py +++ b/tests/timeseries/mock_microgrid.py @@ -54,7 +54,7 @@ class MockMicrogrid: # pylint: disable=too-many-instance-attributes mock_client: MockMicrogridClient mock_resampler: MockResampler - def __init__( # pylint: disable=too-many-arguments + def __init__( # pylint: disable=too-many-arguments,too-many-positional-arguments self, grid_meter: bool | None = None, api_client_streaming: bool = False, diff --git a/tests/timeseries/mock_resampler.py b/tests/timeseries/mock_resampler.py index 68e9cada3..976c2fbe2 100644 --- a/tests/timeseries/mock_resampler.py +++ b/tests/timeseries/mock_resampler.py @@ -27,7 +27,7 @@ class MockResampler: """Mock resampler.""" - def __init__( # pylint: disable=too-many-arguments + def __init__( # pylint: disable=too-many-arguments,too-many-positional-arguments self, mocker: MockerFixture, resampler_config: ResamplerConfig, diff --git a/tests/timeseries/test_moving_window.py b/tests/timeseries/test_moving_window.py index f1ae49673..3f0e4cd56 100644 --- a/tests/timeseries/test_moving_window.py +++ b/tests/timeseries/test_moving_window.py @@ -59,7 +59,11 @@ def init_moving_window( """ lm_chan = Broadcast[Sample[Quantity]](name="lm_net_power") lm_tx = lm_chan.new_sender() - window = MovingWindow(size, lm_chan.new_receiver(), timedelta(seconds=1)) + window = MovingWindow( + size=size, + resampled_data_recv=lm_chan.new_receiver(), + input_sampling_period=timedelta(seconds=1), + ) return window, lm_tx diff --git a/tests/timeseries/test_periodic_feature_extractor.py b/tests/timeseries/test_periodic_feature_extractor.py index a298b0324..a61062ebb 100644 --- a/tests/timeseries/test_periodic_feature_extractor.py +++ b/tests/timeseries/test_periodic_feature_extractor.py @@ -60,7 +60,9 @@ async def init_feature_extractor_no_data( # We only need the moving window to initialize the PeriodicFeatureExtractor class. lm_chan = Broadcast[Sample[Quantity]](name="lm_net_power") moving_window = 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), ) async with moving_window: await lm_chan.new_sender().send( @@ -126,7 +128,7 @@ async def test_feature_extractor() -> None: # pylint: disable=too-many-statemen async with init_feature_extractor(data, timedelta(seconds=5)) as feature_extractor: assert np.allclose(feature_extractor.avg(start, end), [1.5, 1.5]) - async def _test_fun( # pylint: disable=too-many-arguments + async def _test_fun( # pylint: disable=too-many-arguments,too-many-positional-arguments data: list[float], period: int, start: int, diff --git a/tests/timeseries/test_resampling.py b/tests/timeseries/test_resampling.py index 81598fa30..9d57ee458 100644 --- a/tests/timeseries/test_resampling.py +++ b/tests/timeseries/test_resampling.py @@ -64,7 +64,8 @@ async def _advance_time(fake_time: time_machine.Coordinates, seconds: float) -> fake_time.shift(seconds) -async def _assert_no_more_samples( # pylint: disable=too-many-arguments +# pylint: disable-next=too-many-arguments,too-many-positional-arguments +async def _assert_no_more_samples( resampler: Resampler, initial_time: datetime, sink_mock: AsyncMock, diff --git a/tests/utils/component_data_wrapper.py b/tests/utils/component_data_wrapper.py index d4071d533..c76612caa 100644 --- a/tests/utils/component_data_wrapper.py +++ b/tests/utils/component_data_wrapper.py @@ -30,11 +30,14 @@ MeterData, ) +# Disable these checks for the file as we need to pass a lot of data +# pylint: disable=too-many-arguments,too-many-positional-arguments + class BatteryDataWrapper(BatteryData): """Wrapper for the BatteryData with default arguments.""" - def __init__( # pylint: disable=too-many-arguments + def __init__( self, component_id: int, timestamp: datetime, @@ -92,7 +95,7 @@ def copy_with_new_timestamp(self, new_timestamp: datetime) -> BatteryDataWrapper class InverterDataWrapper(InverterData): """Wrapper for the InverterData with default arguments.""" - def __init__( # pylint: disable=too-many-arguments,too-many-locals + def __init__( # pylint: disable=too-many-locals self, component_id: int, timestamp: datetime, @@ -160,7 +163,7 @@ def copy_with_new_timestamp(self, new_timestamp: datetime) -> InverterDataWrappe class EvChargerDataWrapper(EVChargerData): """Wrapper for the EvChargerData with default arguments.""" - def __init__( # pylint: disable=too-many-arguments,too-many-locals + def __init__( # pylint: disable=too-many-locals self, component_id: int, timestamp: datetime, @@ -228,7 +231,7 @@ def copy_with_new_timestamp(self, new_timestamp: datetime) -> EvChargerDataWrapp class MeterDataWrapper(MeterData): """Wrapper for the MeterData with default arguments.""" - def __init__( # pylint: disable=too-many-arguments + def __init__( self, component_id: int, timestamp: datetime, diff --git a/tests/utils/mock_microgrid_client.py b/tests/utils/mock_microgrid_client.py index d72973b80..902bcdd0c 100644 --- a/tests/utils/mock_microgrid_client.py +++ b/tests/utils/mock_microgrid_client.py @@ -126,7 +126,8 @@ def component_graph(self) -> ComponentGraph: """ return self._component_graph - async def send(self, data: ComponentData) -> None: + # We need the noqa because the `SenderError` is raised indirectly by `send()`. + async def send(self, data: ComponentData) -> None: # noqa: DOC503 """Send component data using channel. This simulates component sending data. Right now only battery and inverter @@ -136,6 +137,7 @@ async def send(self, data: ComponentData) -> None: data: Data to be send Raises: + RuntimeError: if the type of data is not supported. SenderError: if the underlying channel was closed. A [ChannelClosedError][frequenz.channels.ChannelClosedError] is set as the cause.