diff --git a/.github/dependabot.yml b/.github/dependabot.yml index db6cfbd3d..7ddc4b9a7 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -3,7 +3,7 @@ updates: - package-ecosystem: "pip" directory: "/" schedule: - interval: "daily" + interval: "weekly" time: "07:00" labels: - "part:tooling" @@ -13,11 +13,28 @@ updates: versioning-strategy: auto # Allow up to 10 open pull requests for updates to dependency versions open-pull-requests-limit: 10 + # We group production and development ("optional" in the context of + # pyproject.toml) dependency updates when they are patch and minor updates, + # so we end up with less PRs being generated. + # Major updates are still managed, but they'll create one PR per + # dependency, as major updates are expected to be breaking, it is better to + # manage them individually. + groups: + required: + dependency-type: "production" + update-types: + - "minor" + - "patch" + optional: + dependency-type: "development" + update-types: + - "minor" + - "patch" - package-ecosystem: "github-actions" directory: "/" schedule: - interval: "daily" + interval: "weekly" time: "06:00" labels: - "part:tooling" diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 1cb82cd57..335c54280 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -32,9 +32,17 @@ jobs: - ubuntu-20.04 python: - "3.11" + nox-session: + # To speed things up a bit we use the special ci_checks_max session + # that uses the same venv to run multiple linting sessions + - "ci_checks_max" + - "pytest_min" runs-on: ${{ matrix.os }} steps: + - name: Print environment (debug) + run: env + - name: Fetch sources uses: actions/checkout@v3 with: @@ -50,11 +58,25 @@ jobs: run: | python -m pip install --upgrade pip python -m pip install -e .[dev-noxfile] + pip freeze + + - name: Create nox venv + env: + NOX_SESSION: ${{ matrix.nox-session }} + run: nox --install-only -e "$NOX_SESSION" + + - name: Print pip freeze for nox venv (debug) + env: + NOX_SESSION: ${{ matrix.nox-session }} + run: | + . ".nox/$NOX_SESSION/bin/activate" + pip freeze + deactivate - name: Run nox - # To speed things up a bit we use the special ci_checks_max session - # that uses the same venv to run multiple linting sessions - run: nox -e ci_checks_max pytest_min + env: + NOX_SESSION: ${{ matrix.nox-session }} + run: nox -R -e "$NOX_SESSION" timeout-minutes: 10 build: @@ -76,6 +98,7 @@ jobs: run: | python -m pip install -U pip python -m pip install -U build + pip freeze - name: Build the source and binary distribution run: python -m build @@ -135,6 +158,7 @@ jobs: run: | python -m pip install -U pip python -m pip install .[dev-mkdocs] + pip freeze - name: Generate the documentation env: @@ -217,6 +241,7 @@ jobs: run: | python -m pip install -U pip python -m pip install .[dev-mkdocs] + pip freeze - name: Fetch the gh-pages branch if: steps.mike-metadata.outputs.version diff --git a/MANIFEST.in b/MANIFEST.in index 1dc8c9d55..fc40ba9c5 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,4 @@ exclude .cookiecutter-replay.json -exclude .darglint exclude .editorconfig exclude .gitignore exclude CODEOWNERS diff --git a/benchmarks/power_distribution/power_distributor.py b/benchmarks/power_distribution/power_distributor.py index bd4a147d3..6f16ccbb4 100644 --- a/benchmarks/power_distribution/power_distributor.py +++ b/benchmarks/power_distribution/power_distributor.py @@ -36,7 +36,6 @@ async def send_requests(batteries: Set[int], request_num: int) -> List[Result]: """Send requests to the PowerDistributingActor and wait for the response. Args: - user: user that should send request batteries: set of batteries where the power should be set request_num: number of requests that should be send diff --git a/benchmarks/timeseries/benchmark_ringbuffer.py b/benchmarks/timeseries/benchmark_ringbuffer.py index 4615f009f..23839b940 100644 --- a/benchmarks/timeseries/benchmark_ringbuffer.py +++ b/benchmarks/timeseries/benchmark_ringbuffer.py @@ -39,8 +39,7 @@ def test_days(days: int, buffer: OrderedRingBuffer[Any]) -> None: basetime = datetime(2022, 1, 1, tzinfo=timezone.utc) for day in range(days): - # pylint: disable=unused-variable - minutes = buffer.window( + _ = buffer.window( basetime + timedelta(days=day), basetime + timedelta(days=day + 1) ) diff --git a/benchmarks/timeseries/periodic_feature_extractor.py b/benchmarks/timeseries/periodic_feature_extractor.py index 9be937e23..04791fc01 100644 --- a/benchmarks/timeseries/periodic_feature_extractor.py +++ b/benchmarks/timeseries/periodic_feature_extractor.py @@ -64,7 +64,6 @@ def _calculate_avg_window( feature_extractor: The instance of the PeriodicFeatureExtractor to use. window: The window to calculate the average over. window_size: The size of the window to calculate the average over. - weights: The weights to use for the average calculation. Returns: The averaged window. @@ -113,6 +112,7 @@ def _num_windows( Args: window: The buffer that is used for the average calculation. window_size: The size of the window in samples. + period: The distance between two succeeding intervals in samples. Returns: The number of windows that are fully contained in the MovingWindow. @@ -168,7 +168,7 @@ def run_avg_np( The return value is discarded such that it can be used by timit. Args: - a: The array containing all data. + array: The array containing all data. window_size: The size of the window. feature_extractor: An instance of the PeriodicFeatureExtractor. """ @@ -185,7 +185,7 @@ def run_avg_py( The return value is discarded such that it can be used by timit. Args: - a: The array containing all data. + array: The array containing all data. window_size: The size of the window. feature_extractor: An instance of the PeriodicFeatureExtractor. """ diff --git a/benchmarks/timeseries/ringbuffer_serialization.py b/benchmarks/timeseries/ringbuffer_serialization.py index 101387a7f..02edac2ff 100644 --- a/benchmarks/timeseries/ringbuffer_serialization.py +++ b/benchmarks/timeseries/ringbuffer_serialization.py @@ -45,6 +45,9 @@ def benchmark_serialization( Args: ringbuffer: Ringbuffer to benchmark to serialize. iterations: amount of iterations to run. + + Returns: + Average time to dump and load the ringbuffer. """ total = 0.0 for _ in range(iterations): diff --git a/pyproject.toml b/pyproject.toml index ff9157c64..6362431f4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ requires = [ "setuptools == 68.1.0", "setuptools_scm[toml] == 7.1.0", - "frequenz-repo-config[lib] == 0.5.2", + "frequenz-repo-config[lib] == 0.6.1", ] build-backend = "setuptools.build_meta" @@ -49,10 +49,12 @@ name = "Frequenz Energy-as-a-Service GmbH" email = "floss@frequenz.com" [project.optional-dependencies] -dev-docstrings = [ +dev-flake8 = [ + "flake8 == 6.1.0", + "flake8-docstrings == 1.7.0", + "flake8-pyproject == 1.2.3", # For reading the flake8 config from pyproject.toml + "pydoclint == 0.3.1", "pydocstyle == 6.3.0", - "darglint == 1.8.1", - "tomli == 2.0.1", # Needed by pydocstyle to read pyproject.toml ] dev-examples = ["polars == 0.18.15"] dev-formatting = ["black == 23.7.0", "isort == 5.12.0"] @@ -63,16 +65,23 @@ dev-mkdocs = [ "mkdocs-material == 9.2.5", "mkdocs-section-index == 0.3.5", "mkdocstrings[python] == 0.22.0", - "frequenz-repo-config[lib] == 0.5.2", + "frequenz-repo-config[lib] == 0.6.1", ] dev-mypy = [ "mypy == 1.5.1", - "grpc-stubs == 1.24.12", # This dependency introduces breaking changes in patch releases + "grpc-stubs == 1.24.12", # This dependency introduces breaking changes in patch releases + "types-Markdown == 3.4.2.10", + "types-PyYAML == 6.0.12.11", + "types-Pygments == 2.16.0.0", + "types-colorama == 0.4.15.12", "types-protobuf == 4.24.0.1", + "types-python-dateutil == 2.8.19.14", + "types-pytz == 2023.3.0.1", + "types-setuptools == 68.1.0.0", # For checking the noxfile, docs/ script, and tests "frequenz-sdk[dev-mkdocs,dev-noxfile,dev-pytest]", ] -dev-noxfile = ["nox == 2023.4.22", "frequenz-repo-config[lib] == 0.5.2"] +dev-noxfile = ["nox == 2023.4.22", "frequenz-repo-config[lib] == 0.6.1"] dev-pylint = [ "pylint == 2.17.5", # For checking the noxfile, docs/ script, and tests @@ -80,18 +89,16 @@ dev-pylint = [ ] dev-pytest = [ "pytest == 7.4.0", - "frequenz-repo-config[extra-lint-examples] == 0.5.2", + "frequenz-repo-config[extra-lint-examples] == 0.6.1", "pytest-mock == 3.11.1", "pytest-asyncio == 0.21.1", "time-machine == 2.12.0", "async-solipsism == 0.5", # For checking docstring code examples - "sybil == 5.0.3", - "pylint == 2.17.5", "frequenz-sdk[dev-examples]", ] dev = [ - "frequenz-sdk[dev-mkdocs,dev-docstrings,dev-formatting,dev-mkdocs,dev-mypy,dev-noxfile,dev-pylint,dev-pytest]", + "frequenz-sdk[dev-mkdocs,dev-flake8,dev-formatting,dev-mkdocs,dev-mypy,dev-noxfile,dev-pylint,dev-pytest]", ] [project.urls] @@ -110,6 +117,23 @@ profile = "black" line_length = 88 src_paths = ["benchmarks", "examples", "src", "tests"] +[tool.flake8] +# We give some flexibility to go over 88, there are cases like long URLs or +# code in documenation that have extra indentation. Black will still take care +# of making everything that can be 88 wide, 88 wide. +max-line-length = 100 +extend-ignore = [ + "E203", # Whitespace before ':' (conflicts with black) + "W503", # Line break before binary operator (conflicts with black) +] +# pydoclint options +style = "google" +allow-init-docstring = true +arg-type-hints-in-docstring = false +arg-type-hints-in-signature = true +check-return-types = false +check-yield-types = false + [tool.pylint.similarities] ignore-comments = ['yes'] ignore-docstrings = ['yes'] @@ -125,6 +149,10 @@ disable = [ # pylint's unsubscriptable check is buggy and is not needed because # it is a type-check, for which we already have mypy. "unsubscriptable-object", + # Checked by flake8 + "line-too-long", + "unused-variable", + "unnecessary-lambda-assignment", ] [tool.pylint.design] diff --git a/src/frequenz/sdk/actor/_run_utils.py b/src/frequenz/sdk/actor/_run_utils.py index 5cc547c8d..27a9c50bc 100644 --- a/src/frequenz/sdk/actor/_run_utils.py +++ b/src/frequenz/sdk/actor/_run_utils.py @@ -16,7 +16,7 @@ async def run(*actors: Actor) -> None: """Await the completion of all actors. Args: - actors: the actors to be awaited. + *actors: the actors to be awaited. """ _logger.info("Starting %s actor(s)...", len(actors)) diff --git a/src/frequenz/sdk/actor/power_distributing/_battery_pool_status.py b/src/frequenz/sdk/actor/power_distributing/_battery_pool_status.py index 669e41180..57a627ab5 100644 --- a/src/frequenz/sdk/actor/power_distributing/_battery_pool_status.py +++ b/src/frequenz/sdk/actor/power_distributing/_battery_pool_status.py @@ -70,7 +70,7 @@ class BatteryPoolStatus: Send set of working and uncertain batteries, when the any battery change status. """ - def __init__( + def __init__( # noqa: DOC502 (RuntimeError is raised indirectly by BatteryStatus) self, battery_ids: Set[int], battery_status_sender: Sender[BatteryStatus], diff --git a/src/frequenz/sdk/actor/power_distributing/power_distributing.py b/src/frequenz/sdk/actor/power_distributing/power_distributing.py index 492f96c35..670551b29 100644 --- a/src/frequenz/sdk/actor/power_distributing/power_distributing.py +++ b/src/frequenz/sdk/actor/power_distributing/power_distributing.py @@ -217,7 +217,7 @@ async def _send_result(self, namespace: str, result: Result) -> None: namespace: namespace of the sender, to identify the result channel with. result: Result to send out. """ - if not namespace in self._result_senders: + if namespace not in self._result_senders: self._result_senders[namespace] = self._channel_registry.new_sender( namespace ) diff --git a/src/frequenz/sdk/microgrid/_data_pipeline.py b/src/frequenz/sdk/microgrid/_data_pipeline.py index fb0c6a5a4..325fea6d7 100644 --- a/src/frequenz/sdk/microgrid/_data_pipeline.py +++ b/src/frequenz/sdk/microgrid/_data_pipeline.py @@ -33,7 +33,7 @@ DataSourcingActor, ResamplerConfig, ) - from ..actor.power_distributing import ( + from ..actor.power_distributing import ( # noqa: F401 (imports used by string type hints) BatteryStatus, PowerDistributingActor, Request, diff --git a/src/frequenz/sdk/microgrid/client/_client.py b/src/frequenz/sdk/microgrid/client/_client.py index 210c46b92..742f4d98b 100644 --- a/src/frequenz/sdk/microgrid/client/_client.py +++ b/src/frequenz/sdk/microgrid/client/_client.py @@ -335,9 +335,6 @@ async def _component_data_task( transform: A method for transforming raw component data into the desired output type. sender: A channel sender, to send the component data to. - - Raises: - AioRpcError: if connection to Microgrid API cannot be established """ retry_spec: RetryStrategy = self._retry_spec.copy() while True: @@ -446,7 +443,7 @@ async def _expect_category( f", not a {expected_category}." ) - async def meter_data( + async def meter_data( # noqa: DOC502 (ValueError is raised indirectly by _expect_category) self, component_id: int, maxsize: int = RECEIVER_MAX_SIZE, @@ -476,7 +473,7 @@ async def meter_data( MeterData.from_proto, ).new_receiver(maxsize=maxsize) - async def battery_data( + async def battery_data( # noqa: DOC502 (ValueError is raised indirectly by _expect_category) self, component_id: int, maxsize: int = RECEIVER_MAX_SIZE, @@ -506,7 +503,7 @@ async def battery_data( BatteryData.from_proto, ).new_receiver(maxsize=maxsize) - async def inverter_data( + async def inverter_data( # noqa: DOC502 (ValueError is raised indirectly by _expect_category) self, component_id: int, maxsize: int = RECEIVER_MAX_SIZE, @@ -536,7 +533,7 @@ async def inverter_data( InverterData.from_proto, ).new_receiver(maxsize=maxsize) - async def ev_charger_data( + async def ev_charger_data( # noqa: DOC502 (ValueError is raised indirectly by _expect_category) self, component_id: int, maxsize: int = RECEIVER_MAX_SIZE, @@ -624,12 +621,14 @@ async def set_bounds( if lower > 0: raise ValueError(f"Lower bound {upper} must be less than or equal to 0.") + target_metric = ( + microgrid_pb.SetBoundsParam.TargetMetric.TARGET_METRIC_POWER_ACTIVE + ) try: self.api.AddInclusionBounds( microgrid_pb.SetBoundsParam( component_id=component_id, - # pylint: disable=no-member,line-too-long - target_metric=microgrid_pb.SetBoundsParam.TargetMetric.TARGET_METRIC_POWER_ACTIVE, + target_metric=target_metric, bounds=metrics_pb.Bounds(lower=lower, upper=upper), ), ) diff --git a/src/frequenz/sdk/microgrid/component/_component_data.py b/src/frequenz/sdk/microgrid/component/_component_data.py index a3e29c3dc..2a44207d7 100644 --- a/src/frequenz/sdk/microgrid/component/_component_data.py +++ b/src/frequenz/sdk/microgrid/component/_component_data.py @@ -53,6 +53,9 @@ def from_proto(cls, raw: microgrid_pb.ComponentData) -> ComponentData: Args: raw: raw component data as decoded from the wire. + + Returns: + The instance created from the protobuf message. """ diff --git a/src/frequenz/sdk/timeseries/_formula_engine/_formula_engine.py b/src/frequenz/sdk/timeseries/_formula_engine/_formula_engine.py index 62321fa74..b0e3aff09 100644 --- a/src/frequenz/sdk/timeseries/_formula_engine/_formula_engine.py +++ b/src/frequenz/sdk/timeseries/_formula_engine/_formula_engine.py @@ -281,8 +281,8 @@ async def run() -> None: ``` Args: - receiver: A receiver that streams `Sample`s. name: A name for the formula engine. + receiver: A receiver that streams `Sample`s. create_method: A method to generate the output `Sample` value with, e.g. `Power.from_watts`. nones_are_zeros: If `True`, `None` values in the receiver are treated as 0. diff --git a/src/frequenz/sdk/timeseries/_formula_engine/_formula_generators/_chp_power_formula.py b/src/frequenz/sdk/timeseries/_formula_engine/_formula_generators/_chp_power_formula.py index d202a88c3..948d77640 100644 --- a/src/frequenz/sdk/timeseries/_formula_engine/_formula_generators/_chp_power_formula.py +++ b/src/frequenz/sdk/timeseries/_formula_engine/_formula_generators/_chp_power_formula.py @@ -25,7 +25,9 @@ class CHPPowerFormula(FormulaGenerator[Power]): """Formula generator for CHP Power.""" - def generate(self) -> FormulaEngine[Power]: + def generate( # noqa: DOC502 (FormulaGenerationError is raised indirectly by _get_chp_meters) + self, + ) -> FormulaEngine[Power]: """Make a formula for the cumulative CHP power of a microgrid. The calculation is performed by adding the active power measurements from diff --git a/src/frequenz/sdk/timeseries/_formula_engine/_formula_generators/_grid_current_formula.py b/src/frequenz/sdk/timeseries/_formula_engine/_formula_generators/_grid_current_formula.py index 63e2ba98d..b6f7e1b54 100644 --- a/src/frequenz/sdk/timeseries/_formula_engine/_formula_generators/_grid_current_formula.py +++ b/src/frequenz/sdk/timeseries/_formula_engine/_formula_generators/_grid_current_formula.py @@ -14,7 +14,10 @@ class GridCurrentFormula(FormulaGenerator[Current]): """Create a formula engine from the component graph for calculating grid current.""" - def generate(self) -> FormulaEngine3Phase[Current]: + def generate( # noqa: DOC502 + # ComponentNotFound is raised indirectly by _get_grid_component_successors + self, + ) -> FormulaEngine3Phase[Current]: """Generate a formula for calculating grid current from the component graph. Returns: diff --git a/src/frequenz/sdk/timeseries/_formula_engine/_formula_generators/_grid_power_formula.py b/src/frequenz/sdk/timeseries/_formula_engine/_formula_generators/_grid_power_formula.py index 489bf8bdb..5cbc4a8e4 100644 --- a/src/frequenz/sdk/timeseries/_formula_engine/_formula_generators/_grid_power_formula.py +++ b/src/frequenz/sdk/timeseries/_formula_engine/_formula_generators/_grid_power_formula.py @@ -12,7 +12,8 @@ class GridPowerFormula(FormulaGenerator[Power]): """Creates a formula engine from the component graph for calculating grid power.""" - def generate( + def generate( # noqa: DOC502 + # * ComponentNotFound is raised indirectly by _get_grid_component_successors self, ) -> FormulaEngine[Power]: """Generate a formula for calculating grid power from the component graph. diff --git a/src/frequenz/sdk/timeseries/_formula_engine/_formula_generators/_producer_power_formula.py b/src/frequenz/sdk/timeseries/_formula_engine/_formula_generators/_producer_power_formula.py index bd51d969d..84bbca7c4 100644 --- a/src/frequenz/sdk/timeseries/_formula_engine/_formula_generators/_producer_power_formula.py +++ b/src/frequenz/sdk/timeseries/_formula_engine/_formula_generators/_producer_power_formula.py @@ -21,7 +21,11 @@ class ProducerPowerFormula(FormulaGenerator[Power]): which are CHP and PV. """ - def generate(self) -> FormulaEngine[Power]: + def generate( # noqa: DOC502 + # * ComponentNotFound is raised indirectly by _get_grid_component() + # * RuntimeError is raised indirectly by connection_manager.get() + self, + ) -> FormulaEngine[Power]: """Generate formula for calculating producer power from the component graph. Returns: diff --git a/src/frequenz/sdk/timeseries/_formula_engine/_formula_generators/_pv_power_formula.py b/src/frequenz/sdk/timeseries/_formula_engine/_formula_generators/_pv_power_formula.py index cce41f00d..50a6e7e1a 100644 --- a/src/frequenz/sdk/timeseries/_formula_engine/_formula_generators/_pv_power_formula.py +++ b/src/frequenz/sdk/timeseries/_formula_engine/_formula_generators/_pv_power_formula.py @@ -17,14 +17,20 @@ class PVPowerFormula(FormulaGenerator[Power]): """Creates a formula engine for calculating the PV power production.""" - def generate(self) -> FormulaEngine[Power]: + def generate( # noqa: DOC502 + # * ComponentNotFound is raised indirectly by _get_pv_power_components + # * RuntimeError is also raised indirectly by _get_pv_power_components + self, + ) -> FormulaEngine[Power]: """Make a formula for the PV power production of a microgrid. Returns: A formula engine that will calculate PV power production values. Raises: - ComponentNotFound: if there are no PV inverters in the component graph. + ComponentNotFound: if there is a problem finding the needed components. + RuntimeError: if the grid component has no PV inverters or meters as + successors. """ builder = self._get_builder( "pv-power", ComponentMetricId.ACTIVE_POWER, Power.from_watts diff --git a/src/frequenz/sdk/timeseries/_periodic_feature_extractor.py b/src/frequenz/sdk/timeseries/_periodic_feature_extractor.py index 66a954f57..fea3073c5 100644 --- a/src/frequenz/sdk/timeseries/_periodic_feature_extractor.py +++ b/src/frequenz/sdk/timeseries/_periodic_feature_extractor.py @@ -250,9 +250,6 @@ def _get_relative_positions( Returns: The relative positions of the start, end and next samples. - - Raises: - ValueError: If the start timestamp is after the end timestamp. """ # The number of usable windows can change, when the current position of # the ringbuffer is inside one of the windows inside the MovingWindow. diff --git a/src/frequenz/sdk/timeseries/_quantities.py b/src/frequenz/sdk/timeseries/_quantities.py index d5dd0147b..9dac8e6ac 100644 --- a/src/frequenz/sdk/timeseries/_quantities.py +++ b/src/frequenz/sdk/timeseries/_quantities.py @@ -61,7 +61,7 @@ def __init_subclass__(cls, exponent_unit_map: dict[int, str]) -> None: ValueError: If the given exponent_unit_map does not contain a base unit (exponent 0). """ - if not 0 in exponent_unit_map: + if 0 not in exponent_unit_map: raise ValueError("Expected a base unit for the type (for exponent 0)") cls._exponent_unit_map = exponent_unit_map super().__init_subclass__() @@ -262,13 +262,13 @@ def __sub__(self, other: Self) -> Self: return difference def __mul__(self, percent: Percentage) -> Self: - """Return the product of this quantity and a percentage. + """Scale this quantity by a percentage. Args: - percent: The percentage. + percent: The percentage by which to scale this quantity. Returns: - The product of this quantity and a percentage. + The scaled quantity. """ if not isinstance(percent, Percentage): return NotImplemented @@ -374,8 +374,8 @@ def __call__(cls, *_args: Any, **_kwargs: Any) -> NoReturn: """Raise a TypeError when the default constructor is called. Args: - _args: ignored positional arguments. - _kwargs: ignored keyword arguments. + *_args: ignored positional arguments. + **_kwargs: ignored keyword arguments. Raises: TypeError: Always. @@ -522,10 +522,13 @@ def as_megawatts(self) -> float: @overload # type: ignore def __mul__(self, other: Percentage) -> Self: - """Return a power from multiplying this power by the given percentage. + """Scale this power by a percentage. Args: - other: The percentage to multiply by. + other: The percentage by which to scale this power. + + Returns: + The scaled power. """ @overload @@ -534,6 +537,9 @@ def __mul__(self, other: timedelta) -> Energy: Args: other: The duration to multiply by. + + Returns: + The calculated energy. """ def __mul__(self, other: Percentage | timedelta) -> Self | Energy: @@ -544,9 +550,6 @@ def __mul__(self, other: Percentage | timedelta) -> Self | Energy: Returns: A power or energy. - - Raises: - TypeError: If the given value is not a percentage or duration. """ if isinstance(other, Percentage): return super().__mul__(other) @@ -563,6 +566,9 @@ def __truediv__(self, other: Current) -> Voltage: Args: other: The current to divide by. + + Returns: + A voltage from dividing this power by the a current. """ @overload @@ -571,6 +577,9 @@ def __truediv__(self, other: Voltage) -> Current: Args: other: The voltage to divide by. + + Returns: + A current from dividing this power by a voltage. """ def __truediv__(self, other: Current | Voltage) -> Voltage | Current: @@ -659,10 +668,13 @@ def as_milliamperes(self) -> float: @overload # type: ignore def __mul__(self, other: Percentage) -> Self: - """Return a power from multiplying this power by the given percentage. + """Scale this current by a percentage. Args: - other: The percentage to multiply by. + other: The percentage by which to scale this current. + + Returns: + The scaled current. """ @overload @@ -671,6 +683,9 @@ def __mul__(self, other: Voltage) -> Power: Args: other: The voltage. + + Returns: + The calculated power. """ def __mul__(self, other: Percentage | Voltage) -> Self | Power: @@ -681,9 +696,6 @@ def __mul__(self, other: Percentage | Voltage) -> Self | Power: Returns: A current or power. - - Raises: - TypeError: If the given value is not a percentage or voltage. """ if isinstance(other, Percentage): return super().__mul__(other) @@ -777,10 +789,13 @@ def as_kilovolts(self) -> float: @overload # type: ignore def __mul__(self, other: Percentage) -> Self: - """Return a power from multiplying this power by the given percentage. + """Scale this voltage by a percentage. Args: - other: The percentage to multiply by. + other: The percentage by which to scale this voltage. + + Returns: + The scaled voltage. """ @overload @@ -789,6 +804,9 @@ def __mul__(self, other: Current) -> Power: Args: other: The current to multiply the voltage with. + + Returns: + The calculated power. """ def __mul__(self, other: Percentage | Current) -> Self | Power: @@ -799,9 +817,6 @@ def __mul__(self, other: Percentage | Current) -> Self | Power: Returns: The calculated voltage or power. - - Raises: - TypeError: If the given value is not a percentage or current. """ if isinstance(other, Percentage): return super().__mul__(other) @@ -903,6 +918,9 @@ def __truediv__(self, other: timedelta) -> Power: Args: other: The duration to divide by. + + Returns: + A power from dividing this energy by the given duration. """ @overload @@ -911,6 +929,9 @@ def __truediv__(self, other: Power) -> timedelta: Args: other: The power to divide by. + + Returns: + A duration from dividing this energy by the given power. """ def __truediv__(self, other: timedelta | Power) -> Power | timedelta: diff --git a/src/frequenz/sdk/timeseries/_ringbuffer/buffer.py b/src/frequenz/sdk/timeseries/_ringbuffer/buffer.py index a1616d616..1a087022e 100644 --- a/src/frequenz/sdk/timeseries/_ringbuffer/buffer.py +++ b/src/frequenz/sdk/timeseries/_ringbuffer/buffer.py @@ -488,6 +488,9 @@ def __getitem__(self, index_or_slice: SupportsIndex) -> float: Args: index_or_slice: Index of the requested data. + + Returns: + The requested value. """ @overload @@ -499,6 +502,9 @@ def __getitem__(self, index_or_slice: slice) -> FloatArray: Args: index_or_slice: Slice specification of where the requested data is. + + Returns: + The requested slice. """ def __getitem__(self, index_or_slice: SupportsIndex | slice) -> float | FloatArray: diff --git a/src/frequenz/sdk/timeseries/_ringbuffer/serialization.py b/src/frequenz/sdk/timeseries/_ringbuffer/serialization.py index 9bfb7cd68..169e5fe2d 100644 --- a/src/frequenz/sdk/timeseries/_ringbuffer/serialization.py +++ b/src/frequenz/sdk/timeseries/_ringbuffer/serialization.py @@ -45,7 +45,7 @@ def load(path: str) -> OrderedRingBuffer[FloatArray] | None: return instance -def dump( +def dump( # noqa: DOC502 (OSError is raised indirectly by open and pickle.dump) ringbuffer: OrderedRingBuffer[FloatArray], path: str, file_format_version: int = FILE_FORMAT_VERSION, @@ -58,7 +58,7 @@ def dump( file_format_version: Version of the file format, optional. Raises: - I/O related exceptions when the file cannot be written. + OSError: When the file cannot be opened or written. """ with open(path, mode="wb+") as fileobj: pickle.dump((file_format_version, ringbuffer), fileobj) diff --git a/src/frequenz/sdk/timeseries/battery_pool/_component_metric_fetcher.py b/src/frequenz/sdk/timeseries/battery_pool/_component_metric_fetcher.py index a2d24dedd..ddf5a62d1 100644 --- a/src/frequenz/sdk/timeseries/battery_pool/_component_metric_fetcher.py +++ b/src/frequenz/sdk/timeseries/battery_pool/_component_metric_fetcher.py @@ -10,7 +10,7 @@ import math from abc import ABC, abstractmethod from datetime import datetime, timezone -from typing import Any, Generic, Iterable, Optional, Set, TypeVar +from typing import Any, Generic, Iterable, Optional, Self, Set, TypeVar from frequenz.channels import ChannelClosedError, Receiver @@ -44,7 +44,7 @@ class ComponentMetricFetcher(AsyncConstructible, ABC): @classmethod async def async_new( cls, component_id: int, metrics: Iterable[ComponentMetricId] - ) -> Self: # type: ignore[name-defined] # pylint: disable=undefined-variable + ) -> Self: """Create an instance of this class. Subscribe for the given component metrics and return them if method @@ -57,7 +57,7 @@ async def async_new( Returns: This class instance. """ - self: ComponentMetricFetcher = ComponentMetricFetcher.__new__(cls) + self: Self = cls.__new__(cls) self._component_id = component_id self._metrics = metrics return self @@ -78,7 +78,7 @@ async def async_new( cls, component_id: int, metrics: Iterable[ComponentMetricId], - ) -> Self: # type: ignore[name-defined] # pylint: disable=undefined-variable: + ) -> Self: """Create instance of this class. Subscribe for the requested component data and fetch only the latest component @@ -94,7 +94,7 @@ async def async_new( Returns: This class instance """ - self: LatestMetricsFetcher[T] = await super().async_new(component_id, metrics) + self: Self = await super().async_new(component_id, metrics) for metric in metrics: # pylint: disable=protected-access @@ -169,7 +169,7 @@ class LatestBatteryMetricsFetcher(LatestMetricsFetcher[BatteryData]): """Subscribe for the latest battery data using MicrogridApiClient.""" @classmethod - async def async_new( + async def async_new( # noqa: DOC502 (ValueError is raised indirectly super.async_new) cls, component_id: int, metrics: Iterable[ComponentMetricId], @@ -220,7 +220,7 @@ class LatestInverterMetricsFetcher(LatestMetricsFetcher[InverterData]): """Subscribe for the latest inverter data using MicrogridApiClient.""" @classmethod - async def async_new( + async def async_new( # noqa: DOC502 (ValueError is raised indirectly by super.async_new) cls, component_id: int, metrics: Iterable[ComponentMetricId], diff --git a/src/frequenz/sdk/timeseries/battery_pool/_component_metrics.py b/src/frequenz/sdk/timeseries/battery_pool/_component_metrics.py index afc6b2735..09781635b 100644 --- a/src/frequenz/sdk/timeseries/battery_pool/_component_metrics.py +++ b/src/frequenz/sdk/timeseries/battery_pool/_component_metrics.py @@ -55,9 +55,6 @@ def get(self, metric: ComponentMetricId) -> float | None: Args: metric: metric id - Raises: - KeyError: If given metric is not stored in the object. - Returns: Value of the metric. """ diff --git a/src/frequenz/sdk/timeseries/battery_pool/_metric_calculator.py b/src/frequenz/sdk/timeseries/battery_pool/_metric_calculator.py index ceb730e0d..67b880be0 100644 --- a/src/frequenz/sdk/timeseries/battery_pool/_metric_calculator.py +++ b/src/frequenz/sdk/timeseries/battery_pool/_metric_calculator.py @@ -457,9 +457,6 @@ def __init__( Args: batteries: What batteries should be used for calculation. - - Raises: - ValueError: If no battery has adjacent inverter. """ self._bat_inv_map = battery_inverter_mapping(batteries) used_batteries = set(self._bat_inv_map.keys()) diff --git a/tests/actor/power_distributing/test_distribution_algorithm.py b/tests/actor/power_distributing/test_distribution_algorithm.py index 87c2badf8..3229d4a32 100644 --- a/tests/actor/power_distributing/test_distribution_algorithm.py +++ b/tests/actor/power_distributing/test_distribution_algorithm.py @@ -23,7 +23,7 @@ @dataclass class Bound: - """Class to create protobuf Bound""" + """Class to create protobuf Bound.""" lower: float upper: float @@ -31,7 +31,7 @@ class Bound: @dataclass class Metric: - """Class to create protobuf Metric""" + """Class to create protobuf Metric.""" now: Optional[float] bound: Optional[Bound] = None @@ -50,8 +50,7 @@ def battery_msg( # pylint: disable=too-many-arguments component_id: id of that component capacity: capacity soc: soc - power_supply: supply bound - power_consumption: consumption bound + power: power bounds timestamp: timestamp of the message Returns: @@ -80,8 +79,7 @@ def inverter_msg( Args: component_id: id of that component - power_supply: Supply bound - power_consumption: Consumption bound inverter + power: Power bounds timestamp: Timestamp from the message Returns: @@ -109,9 +107,7 @@ def create_components( num: Number of components capacity: Capacity for each battery soc: SoC for each battery - soc_bounds: SoC bounds for each battery - supply_bounds: Supply bounds for each battery and inverter - consumption_bounds: Consumption bounds for each battery and inverter + power: Power bounds for each battery and inverter Returns: List of the components @@ -133,7 +129,6 @@ def create_components_with_capacity( self, num: int, capacity: List[float] ) -> List[InvBatPair]: """Create components with given capacity.""" - components: List[InvBatPair] = [] for i in range(0, num): battery_data = BatteryDataWrapper( @@ -281,7 +276,7 @@ def test_distribute_power_three_batteries_2(self) -> None: assert result.remaining_power == approx(0.0) def test_distribute_power_three_batteries_3(self) -> None: - """Test with batteries with no capacity""" + """Test with batteries with no capacity.""" capacity: List[float] = [0, 49000, 0] components = self.create_components_with_capacity(3, capacity) @@ -572,7 +567,7 @@ def test_consumption_three_batteries_4(self) -> None: assert result.remaining_power == approx(200.0) def test_consumption_three_batteries_5(self) -> None: - """Test what if some batteries has invalid SoC and capacity""" + """Test what if some batteries has invalid SoC and capacity.""" capacity: List[Metric] = [Metric(98000), Metric(49000), Metric(0.0)] soc: List[Metric] = [ Metric(80.0, Bound(0, 50)), diff --git a/tests/actor/power_distributing/test_power_distributing.py b/tests/actor/power_distributing/test_power_distributing.py index a1db2bcf0..09bf33451 100644 --- a/tests/actor/power_distributing/test_power_distributing.py +++ b/tests/actor/power_distributing/test_power_distributing.py @@ -1,7 +1,7 @@ # License: MIT # Copyright © 2023 Frequenz Energy-as-a-Service GmbH -"""Tests power distributor""" +"""Tests power distributor.""" from __future__ import annotations @@ -41,7 +41,7 @@ class TestPowerDistributingActor: # pylint: disable=protected-access - """Test tool to distribute power""" + """Test tool to distribute power.""" _namespace = "power_distributor" @@ -191,7 +191,7 @@ async def test_power_distributor_exclusion_bounds( channel_registry=channel_registry, battery_status_sender=battery_status_channel.new_sender(), ): - ## zero power requests should pass through despite the exclusion bounds. + # zero power requests should pass through despite the exclusion bounds. request = Request( namespace=self._namespace, power=Power.zero(), @@ -216,8 +216,8 @@ async def test_power_distributor_exclusion_bounds( assert result.excess_power.isclose(Power.zero(), abs_tol=1e-9) assert result.request == request - ## non-zero power requests that fall within the exclusion bounds should be - ## rejected. + # non-zero power requests that fall within the exclusion bounds should be + # rejected. request = Request( namespace=self._namespace, power=Power.from_watts(300.0), diff --git a/tests/actor/test_actor.py b/tests/actor/test_actor.py index 7d266cbbb..b60ccbffb 100644 --- a/tests/actor/test_actor.py +++ b/tests/actor/test_actor.py @@ -47,7 +47,7 @@ def __init__(self) -> None: super().__init__(name="test") async def _run(self) -> None: - """Start the actor and crash upon receiving a message""" + """Start the actor and crash upon receiving a message.""" print(f"{self} started") self.inc_restart_count() print(f"{self} done") @@ -69,7 +69,7 @@ def __init__( self._recv = recv async def _run(self) -> None: - """Start the actor and crash upon receiving a message""" + """Start the actor and crash upon receiving a message.""" print(f"{self} started") self.inc_restart_count() async for msg in self._recv: @@ -94,7 +94,7 @@ def __init__( self._recv = recv async def _run(self) -> None: - """Start the actor and crash upon receiving a message""" + """Start the actor and crash upon receiving a message.""" print(f"{self} started") self.inc_restart_count() async for _ in self._recv: @@ -122,9 +122,10 @@ def __init__( """Create an `EchoActor` instance. Args: - name (str): Name of the actor. - recv1 (Receiver[bool]): A channel receiver for test boolean data. - recv2 (Receiver[bool]): A channel receiver for test boolean data. + name: Name of the actor. + recv1: A channel receiver for test boolean data. + recv2: A channel receiver for test boolean data. + output: A channel sender for output test boolean data. """ super().__init__(name=name) self._recv1 = recv1 @@ -132,11 +133,7 @@ def __init__( self._output = output async def _run(self) -> None: - """Do computations depending on the selected input message. - - Args: - output (Sender[OT]): A channel sender, to send actor's results to. - """ + """Do computations depending on the selected input message.""" print(f"{self} started") self.inc_restart_count() @@ -207,6 +204,7 @@ async def test_restart_on_unhandled_exception( Args: restart_limit: The restart limit to use. + caplog: The log capture fixture. """ caplog.set_level("DEBUG", logger="frequenz.sdk.actor._actor") caplog.set_level("DEBUG", logger="frequenz.sdk.actor._run_utils") diff --git a/tests/actor/test_battery_pool_status.py b/tests/actor/test_battery_pool_status.py index 4e0f40475..e9fc6b544 100644 --- a/tests/actor/test_battery_pool_status.py +++ b/tests/actor/test_battery_pool_status.py @@ -20,7 +20,7 @@ # pylint: disable=protected-access class TestBatteryPoolStatus: - """Tests for BatteryPoolStatus""" + """Tests for BatteryPoolStatus.""" async def test_batteries_status(self, mocker: MockerFixture) -> None: """Basic tests for BatteryPoolStatus. @@ -28,7 +28,7 @@ async def test_batteries_status(self, mocker: MockerFixture) -> None: BatteryStatusTracker is more tested in its own unit tests. Args: - mock_microgrid: mock microgrid client + mocker: Pytest mocker fixture. """ mock_microgrid = MockMicrogrid(grid_meter=True) mock_microgrid.add_batteries(3) diff --git a/tests/actor/test_battery_status.py b/tests/actor/test_battery_status.py index 375ebf64b..4ddd25f13 100644 --- a/tests/actor/test_battery_status.py +++ b/tests/actor/test_battery_status.py @@ -59,11 +59,11 @@ def battery_data( # pylint: disable=too-many-arguments component_state: Component state. Defaults to BatteryState.COMPONENT_STATE_CHARGING. errors: List of the components error. By default empty list will be created. + capacity: Battery capacity. Returns: BatteryData with given arguments. """ - return BatteryDataWrapper( component_id=component_id, capacity=capacity, @@ -97,7 +97,6 @@ def inverter_data( Returns: InverterData with given arguments. """ - return InverterDataWrapper( component_id=component_id, timestamp=datetime.now(tz=timezone.utc) if timestamp is None else timestamp, @@ -148,13 +147,13 @@ class TestBatteryStatus: async def test_sync_update_status_with_messages( self, mocker: MockerFixture ) -> None: - """Test if messages changes battery status/ + """Test if messages changes battery status. Tests uses FakeSelect to test status in sync way. Otherwise we would have lots of async calls and waiting. Args: - mock_microgrid: mock_microgrid fixture + mocker: Pytest mocker fixture. """ mock_microgrid = MockMicrogrid(grid_meter=True) mock_microgrid.add_batteries(3) @@ -316,7 +315,7 @@ async def test_sync_blocking_feature(self, mocker: MockerFixture) -> None: Otherwise we would have lots of async calls and waiting. Args: - mock_microgrid: mock_microgrid fixture + mocker: Pytest mocker fixture. """ mock_microgrid = MockMicrogrid(grid_meter=True) mock_microgrid.add_batteries(3) @@ -433,7 +432,7 @@ async def test_sync_blocking_interrupted_with_with_max_data( Otherwise we would have lots of async calls and waiting. Args: - mock_microgrid: mock_microgrid fixture + mocker: Pytest mocker fixture. """ mock_microgrid = MockMicrogrid(grid_meter=True) mock_microgrid.add_batteries(3) @@ -484,7 +483,7 @@ async def test_sync_blocking_interrupted_with_invalid_message( Otherwise we would have lots of async calls and waiting. Args: - mock_microgrid: mock_microgrid fixture + mocker: Pytest mocker fixture. """ mock_microgrid = MockMicrogrid(grid_meter=True) mock_microgrid.add_batteries(3) @@ -538,14 +537,13 @@ async def test_sync_blocking_interrupted_with_invalid_message( @time_machine.travel("2022-01-01 00:00 UTC", tick=False) async def test_timers(self, mocker: MockerFixture) -> None: - """Test if messages changes battery status/ + """Test if messages changes battery status. Tests uses FakeSelect to test status in sync way. Otherwise we would have lots of async calls and waiting. Args: - mock_microgrid: mock_microgrid fixture - mocker: pytest mocker instance + mocker: Pytest mocker fixture. """ mock_microgrid = MockMicrogrid(grid_meter=True) mock_microgrid.add_batteries(3) @@ -607,7 +605,7 @@ async def test_async_battery_status(self, mocker: MockerFixture) -> None: """Test if status changes. Args: - mock_microgrid: mock_microgrid fixture + mocker: Pytest mocker fixture. """ mock_microgrid = MockMicrogrid(grid_meter=True) mock_microgrid.add_batteries(3) @@ -689,7 +687,7 @@ class TestBatteryStatusRecovery: async def setup_tracker( self, mocker: MockerFixture ) -> AsyncIterator[tuple[MockMicrogrid, Receiver[Status]]]: - """Setup a BatteryStatusTracker instance to run tests with.""" + """Set a BatteryStatusTracker instance up to run tests with.""" mock_microgrid = MockMicrogrid(grid_meter=True) mock_microgrid.add_batteries(1) await mock_microgrid.start(mocker) @@ -887,7 +885,6 @@ async def test_critical_error( setup_tracker: tuple[MockMicrogrid, Receiver[Status]], ) -> None: """Test recovery after critical error.""" - mock_microgrid, status_receiver = setup_tracker await self._send_healthy_inverter(mock_microgrid) diff --git a/tests/actor/test_config_manager.py b/tests/actor/test_config_manager.py index 3e70c4f09..7190a77a5 100644 --- a/tests/actor/test_config_manager.py +++ b/tests/actor/test_config_manager.py @@ -1,7 +1,7 @@ # License: MIT # Copyright © 2022 Frequenz Energy-as-a-Service GmbH -"""Test for ConfigManager""" +"""Test for ConfigManager.""" import pathlib import pytest @@ -15,14 +15,14 @@ class Item(BaseModel): - """Test item""" + """Test item.""" item_id: int name: str def create_content(number: int) -> str: - """Utility function to create content to be written to a config file.""" + """Create content to be written to a config file.""" return f""" logging_lvl = "ERROR" var1 = "0" @@ -31,7 +31,7 @@ def create_content(number: int) -> str: class TestActorConfigManager: - """Test for ConfigManager""" + """Test for ConfigManager.""" conf_path = "sdk/config.toml" conf_content = """ @@ -71,11 +71,12 @@ def real_config_file( return file_path async def test_update(self, config_file: pathlib.Path) -> None: - """ - Test ConfigManager by checking if: + """Test ConfigManager. + + Check if: + - the initial content of the content file is correct - - the config file modifications are picked up and the new content - is correct + - the config file modifications are picked up and the new content is correct """ config_channel: Broadcast[Config] = Broadcast( "Config Channel", resend_latest=True diff --git a/tests/actor/test_data_sourcing.py b/tests/actor/test_data_sourcing.py index 4d70fe5cb..377a5859a 100644 --- a/tests/actor/test_data_sourcing.py +++ b/tests/actor/test_data_sourcing.py @@ -1,9 +1,7 @@ # License: MIT # Copyright © 2022 Frequenz Energy-as-a-Service GmbH -""" -Tests for the DataSourcingActor. -""" +"""Tests for the DataSourcingActor.""" from frequenz.api.common import components_pb2 as components_pb from frequenz.channels import Broadcast diff --git a/tests/actor/test_resampling.py b/tests/actor/test_resampling.py index 752ce479b..7e1d39884 100644 --- a/tests/actor/test_resampling.py +++ b/tests/actor/test_resampling.py @@ -115,7 +115,6 @@ async def test_single_request( fake_time: time_machine.Coordinates, ) -> None: """Run main functions that initializes and creates everything.""" - channel_registry = ChannelRegistry(name="test") data_source_req_chan = Broadcast[ComponentMetricRequest]("data-source-req") data_source_req_recv = data_source_req_chan.new_receiver() @@ -159,7 +158,6 @@ async def test_duplicate_request( fake_time: time_machine.Coordinates, ) -> None: """Run main functions that initializes and creates everything.""" - channel_registry = ChannelRegistry(name="test") data_source_req_chan = Broadcast[ComponentMetricRequest]("data-source-req") data_source_req_recv = data_source_req_chan.new_receiver() diff --git a/tests/actor/test_run_utils.py b/tests/actor/test_run_utils.py index 3d93e2429..7324626dd 100644 --- a/tests/actor/test_run_utils.py +++ b/tests/actor/test_run_utils.py @@ -77,7 +77,6 @@ async def _run(self) -> None: # pylint: disable=redefined-outer-name async def test_all_actors_done(fake_time: time_machine.Coordinates) -> None: """Test the completion of all actors.""" - sleepy_actor_1 = SleepyActor("sleepy_actor_1", sleep_duration=1.0) sleepy_actor_2 = SleepyActor("sleepy_actor_2", sleep_duration=2.0) @@ -110,7 +109,6 @@ async def test_all_actors_done(fake_time: time_machine.Coordinates) -> None: async def test_actors_cancelled() -> None: """Test the completion of actors being cancelled.""" - faulty_actors = [FaultyActor(f"faulty_actor_{idx}") for idx in range(5)] await asyncio.wait_for(run(*faulty_actors), timeout=1.0) diff --git a/tests/api_client/test_api_client.py b/tests/api_client/test_api_client.py index 4cab18628..12a587477 100644 --- a/tests/api_client/test_api_client.py +++ b/tests/api_client/test_api_client.py @@ -14,6 +14,11 @@ class FakeApiClient(ApiClient): @classmethod def api_major_version(cls) -> int: + """Return the major version of the API supported by the client. + + Returns: + The major version of the API supported by the client. + """ # Specifying the targeted API version here. return 1 @@ -42,16 +47,20 @@ class FakeGrpcClient(FakeApiClient): @classmethod def api_type(cls) -> ApiProtocol: + """Return the API type.""" # Specifying the API protocol here as gRPC. return ApiProtocol.GRPC async def connect(self, connection_params: str) -> None: + """Connect to the API.""" self.is_connected = True async def disconnect(self) -> None: + """Disconnect from the API.""" self.is_connected = False def get_data(self) -> str: + """Get data from the API.""" return "grpc data" @@ -62,16 +71,20 @@ class FakeRestClient(FakeApiClient): @classmethod def api_type(cls) -> ApiProtocol: + """Return the API type.""" # Same as `FakeGrpcClient`, but targeting REST protocol here. return ApiProtocol.REST async def connect(self, connection_params: str) -> None: + """Connect to the API.""" self.is_connected = True async def disconnect(self) -> None: + """Disconnect from the API.""" self.is_connected = False def get_data(self) -> str: + """Get data from the API.""" return "rest data" diff --git a/tests/config/test_config.py b/tests/config/test_config.py index 2a890983b..786c432d6 100644 --- a/tests/config/test_config.py +++ b/tests/config/test_config.py @@ -1,7 +1,7 @@ # License: MIT # Copyright © 2022 Frequenz Energy-as-a-Service GmbH -"""Test for Config""" +"""Test for Config.""" import pathlib import re @@ -16,14 +16,14 @@ class Item(BaseModel): - """Test item""" + """Test item.""" item_id: int name: str class TestConfig: - """Test for Config""" + """Test for Config.""" conf_path = "sdk/config.toml" conf_content = """ @@ -61,7 +61,7 @@ def conf_vars(self, config_file: pathlib.Path) -> dict[str, Any]: return tomllib.load(file) def test_get(self, conf_vars: dict[str, Any]) -> None: - """Test get function""" + """Test get function.""" config = Config(conf_vars=conf_vars) assert config.get("logging_lvl") == "DEBUG" @@ -70,7 +70,7 @@ def test_get(self, conf_vars: dict[str, Any]) -> None: assert config.get("var2", default=0) == 0 def test_getitem(self, conf_vars: dict[str, Any]) -> None: - """Test getitem function""" + """Test getitem function.""" config = Config(conf_vars=conf_vars) assert config["logging_lvl"] == "DEBUG" @@ -79,7 +79,7 @@ def test_getitem(self, conf_vars: dict[str, Any]) -> None: assert config["var2"] def test_contains(self, conf_vars: dict[str, Any]) -> None: - """Test contains function""" + """Test contains function.""" config = Config(conf_vars=conf_vars) assert "logging_lvl" in config @@ -116,8 +116,7 @@ def test_contains(self, conf_vars: dict[str, Any]) -> None: def test_get_as_success( self, key: str, expected_type: Any, value: Any, conf_vars: dict[str, Any] ) -> None: - """Test get_as function with proper arguments""" - + """Test get_as function with proper arguments.""" config = Config(conf_vars=conf_vars) result = config.get_as(key, expected_type) assert result == value @@ -136,7 +135,7 @@ def test_get_as_success( def test_get_as_validation_error( self, key: str, expected_type: Any, conf_vars: dict[str, Any] ) -> None: - """Test get_as function which raise ValidationError""" + """Test get_as function which raise ValidationError.""" config = Config(conf_vars=conf_vars) err_msg = ( @@ -161,8 +160,7 @@ def test_get_dict_values_success( value: Any, conf_vars: dict[str, Any], ) -> None: - """Test get_as function with proper arguments""" - + """Test get_as function with proper arguments.""" config = Config(conf_vars=conf_vars) result = config.get_dict(key_prefix, expected_values_type) assert result == value @@ -178,8 +176,7 @@ def test_get_dict_values_success( def test_get_dict_success( self, key_prefix: str, expected_values_type: Any, conf_vars: dict[str, Any] ) -> None: - """Test get_as function with proper arguments""" - + """Test get_as function with proper arguments.""" config = Config(conf_vars=conf_vars) err_msg_re = ( diff --git a/tests/config/test_config_manager.py b/tests/config/test_config_manager.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/conftest.py b/tests/conftest.py index 348741f79..631360209 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,8 +2,8 @@ # Copyright © 2022 Frequenz Energy-as-a-Service GmbH """Setup for all the tests.""" -import collections.abc import contextlib +from collections.abc import Iterator from datetime import timedelta import pytest @@ -16,7 +16,7 @@ @contextlib.contextmanager -def actor_restart_limit(limit: int) -> collections.abc.Iterator[None]: +def actor_restart_limit(limit: int) -> Iterator[None]: """Temporarily set the actor restart limit to a given value. Example: @@ -41,14 +41,14 @@ def actor_restart_limit(limit: int) -> collections.abc.Iterator[None]: @pytest.fixture(scope="session", autouse=True) -def disable_actor_auto_restart() -> collections.abc.Iterator[None]: +def disable_actor_auto_restart() -> Iterator[None]: """Disable auto-restart of actors while running tests.""" with actor_restart_limit(0): yield @pytest.fixture -def actor_auto_restart_once() -> collections.abc.Iterator[None]: +def actor_auto_restart_once() -> Iterator[None]: """Make actors restart only once.""" with actor_restart_limit(1): yield diff --git a/tests/microgrid/mock_api.py b/tests/microgrid/mock_api.py index 3e07b3824..72fde7a6b 100644 --- a/tests/microgrid/mock_api.py +++ b/tests/microgrid/mock_api.py @@ -45,7 +45,6 @@ ConnectionFilter, ConnectionList, MicrogridMetadata, - PowerLevelParam, SetBoundsParam, SetPowerActiveParam, SetPowerReactiveParam, diff --git a/tests/microgrid/test_client.py b/tests/microgrid/test_client.py index 8b38e72bb..eb3c27a74 100644 --- a/tests/microgrid/test_client.py +++ b/tests/microgrid/test_client.py @@ -1,9 +1,7 @@ # License: MIT # Copyright © 2022 Frequenz Energy-as-a-Service GmbH -""" -Tests for the microgrid client thin wrapper. -""" +"""Tests for the microgrid client thin wrapper.""" import asyncio @@ -36,8 +34,11 @@ class TestMicrogridGrpcClient: + """Tests for the microgrid client thin wrapper.""" + @staticmethod def create_client(port: int) -> client.MicrogridApiClient: + """Create a client for the mock API server.""" return client.MicrogridGrpcClient( grpc.aio.insecure_channel(f"[::]:{port}"), f"[::]:{port}", @@ -45,6 +46,7 @@ def create_client(port: int) -> client.MicrogridApiClient: ) async def test_components(self) -> None: + """Test the components() method.""" servicer = mock_api.MockMicrogridServicer() server = mock_api.MockGrpcServer(servicer, port=57899) await server.start() @@ -167,6 +169,7 @@ async def test_components(self) -> None: assert await server.graceful_shutdown() async def test_connections(self) -> None: + """Test the connections() method.""" servicer = mock_api.MockMicrogridServicer() server = mock_api.MockGrpcServer(servicer, port=57898) await server.start() @@ -392,6 +395,7 @@ def ListAllComponents( assert await server.graceful_shutdown() async def test_meter_data(self) -> None: + """Test the meter_data() method.""" servicer = mock_api.MockMicrogridServicer() server = mock_api.MockGrpcServer(servicer, port=57899) await server.start() @@ -407,11 +411,11 @@ async def test_meter_data(self) -> None: ) with pytest.raises(ValueError): - ## should raise a ValueError for missing component_id + # should raise a ValueError for missing component_id await microgrid.meter_data(20) with pytest.raises(ValueError): - ## should raise a ValueError for wrong component category + # should raise a ValueError for wrong component category await microgrid.meter_data(38) peekable = (await microgrid.meter_data(83)).into_peekable() await asyncio.sleep(0.2) @@ -424,6 +428,7 @@ async def test_meter_data(self) -> None: assert latest.component_id == 83 async def test_battery_data(self) -> None: + """Test the battery_data() method.""" servicer = mock_api.MockMicrogridServicer() server = mock_api.MockGrpcServer(servicer, port=57899) await server.start() @@ -439,11 +444,11 @@ async def test_battery_data(self) -> None: ) with pytest.raises(ValueError): - ## should raise a ValueError for missing component_id + # should raise a ValueError for missing component_id await microgrid.meter_data(20) with pytest.raises(ValueError): - ## should raise a ValueError for wrong component category + # should raise a ValueError for wrong component category await microgrid.meter_data(38) peekable = (await microgrid.battery_data(83)).into_peekable() await asyncio.sleep(0.2) @@ -456,6 +461,7 @@ async def test_battery_data(self) -> None: assert latest.component_id == 83 async def test_inverter_data(self) -> None: + """Test the inverter_data() method.""" servicer = mock_api.MockMicrogridServicer() server = mock_api.MockGrpcServer(servicer, port=57899) await server.start() @@ -471,11 +477,11 @@ async def test_inverter_data(self) -> None: ) with pytest.raises(ValueError): - ## should raise a ValueError for missing component_id + # should raise a ValueError for missing component_id await microgrid.meter_data(20) with pytest.raises(ValueError): - ## should raise a ValueError for wrong component category + # should raise a ValueError for wrong component category await microgrid.meter_data(38) peekable = (await microgrid.inverter_data(83)).into_peekable() await asyncio.sleep(0.2) @@ -488,6 +494,7 @@ async def test_inverter_data(self) -> None: assert latest.component_id == 83 async def test_ev_charger_data(self) -> None: + """Test the ev_charger_data() method.""" servicer = mock_api.MockMicrogridServicer() server = mock_api.MockGrpcServer(servicer, port=57899) await server.start() @@ -503,11 +510,11 @@ async def test_ev_charger_data(self) -> None: ) with pytest.raises(ValueError): - ## should raise a ValueError for missing component_id + # should raise a ValueError for missing component_id await microgrid.meter_data(20) with pytest.raises(ValueError): - ## should raise a ValueError for wrong component category + # should raise a ValueError for wrong component category await microgrid.meter_data(38) peekable = (await microgrid.ev_charger_data(83)).into_peekable() await asyncio.sleep(0.2) @@ -565,6 +572,7 @@ async def test_discharge(self) -> None: assert await server.graceful_shutdown() async def test_set_bounds(self) -> None: + """Check if set_bounds is able to set bounds for component.""" servicer = mock_api.MockMicrogridServicer() server = mock_api.MockGrpcServer(servicer, port=57899) await server.start() @@ -596,8 +604,11 @@ async def test_set_bounds(self) -> None: assert len(expected_bounds) == len(servicer.get_bounds()) - # pylint:disable=unnecessary-lambda-assignment - sort_key = lambda bound: bound.target_metric + def sort_key( + bound: microgrid_pb.SetBoundsParam, + ) -> microgrid_pb.SetBoundsParam.TargetMetric.ValueType: + return bound.target_metric + assert sorted(servicer.get_bounds(), key=sort_key) == sorted( expected_bounds, key=sort_key ) diff --git a/tests/microgrid/test_component.py b/tests/microgrid/test_component.py index 3d8ff2773..1dda260c2 100644 --- a/tests/microgrid/test_component.py +++ b/tests/microgrid/test_component.py @@ -1,9 +1,7 @@ # License: MIT # Copyright © 2022 Frequenz Energy-as-a-Service GmbH -""" -Tests for the microgrid component wrapper. -""" +"""Tests for the microgrid component wrapper.""" import frequenz.api.common.components_pb2 as components_pb import pytest diff --git a/tests/microgrid/test_component_data.py b/tests/microgrid/test_component_data.py index bc8b9d2ea..6670512d9 100644 --- a/tests/microgrid/test_component_data.py +++ b/tests/microgrid/test_component_data.py @@ -1,9 +1,7 @@ # License: MIT # Copyright © 2022 Frequenz Energy-as-a-Service GmbH -""" -Tests for the microgrid component data. -""" +"""Tests for the microgrid component data.""" from datetime import datetime, timezone diff --git a/tests/microgrid/test_connection.py b/tests/microgrid/test_connection.py index 63eb42142..fc4898eeb 100644 --- a/tests/microgrid/test_connection.py +++ b/tests/microgrid/test_connection.py @@ -1,9 +1,7 @@ # License: MIT # Copyright © 2022 Frequenz Energy-as-a-Service GmbH -""" -Tests for the microgrid Connection type. -""" +"""Tests for the microgrid Connection type.""" from frequenz.sdk.microgrid import client diff --git a/tests/microgrid/test_datapipeline.py b/tests/microgrid/test_datapipeline.py index 029d49d63..9c715961d 100644 --- a/tests/microgrid/test_datapipeline.py +++ b/tests/microgrid/test_datapipeline.py @@ -30,7 +30,6 @@ def event_loop() -> Iterator[async_solipsism.EventLoop]: async def test_actors_started(mocker: MockerFixture) -> None: """Test that the datasourcing, resampling and power distributing actors are started.""" - datapipeline = _DataPipeline( resampler_config=ResamplerConfig(resampling_period=timedelta(seconds=1)) ) diff --git a/tests/microgrid/test_graph.py b/tests/microgrid/test_graph.py index 7d6f35640..07599aa8f 100644 --- a/tests/microgrid/test_graph.py +++ b/tests/microgrid/test_graph.py @@ -1,9 +1,7 @@ # License: MIT # Copyright © 2022 Frequenz Energy-as-a-Service GmbH -""" -Tests for the microgrid component graph. -""" +"""Tests for the microgrid component graph.""" # pylint: disable=too-many-lines,use-implicit-booleaness-not-comparison # pylint: disable=invalid-name,missing-function-docstring,too-many-statements @@ -66,6 +64,7 @@ class TestComponentGraph: @pytest.fixture() def sample_input_components(self) -> Set[Component]: + """Create a sample set of components for testing purposes.""" return { Component(11, ComponentCategory.GRID), Component(21, ComponentCategory.METER), @@ -76,6 +75,7 @@ def sample_input_components(self) -> Set[Component]: @pytest.fixture() def sample_input_connections(self) -> Set[Connection]: + """Create a sample set of connections for testing purposes.""" return { Connection(11, 21), Connection(21, 41), @@ -97,6 +97,7 @@ def sample_graph( return _graph_implementation def test_without_filters(self) -> None: + """Test the graph component query without filters.""" _graph_implementation = gr._MicrogridComponentGraph() graph: gr.ComponentGraph = _graph_implementation @@ -212,6 +213,7 @@ def test_without_filters(self) -> None: def test_filter_graph_components_by_id( self, sample_graph: gr.ComponentGraph, ids: Set[int], expected: Set[Component] ) -> None: + """Test the graph component query with component ID filter.""" # with component_id filter specified, we get back only components whose ID # matches one of the specified values assert len(sample_graph.components(component_id=ids)) == len(expected) @@ -262,6 +264,7 @@ def test_filter_graph_components_by_type( types: Set[ComponentCategory], expected: Set[Component], ) -> None: + """Test the graph component query with component category filter.""" # with component_id filter specified, we get back only components whose ID # matches one of the specified values assert len(sample_graph.components(component_category=types)) == len(expected) @@ -294,6 +297,7 @@ def test_filter_graph_components_with_composite_filter( types: Set[ComponentCategory], expected: Set[Component], ) -> None: + """Test the graph component query with composite filter.""" # when both filters are applied, they are combined via AND logic, i.e. # the component must have one of the specified IDs and be of one of # the specified types @@ -308,11 +312,13 @@ def test_filter_graph_components_with_composite_filter( def test_components_without_filters( self, sample_input_components: Set[Component], sample_graph: gr.ComponentGraph ) -> None: + """Test the graph component query without filters.""" # without any filter applied, we get back all the components in the graph assert len(sample_graph.components()) == len(sample_input_components) assert sample_graph.components() == sample_input_components def test_connection_filters(self) -> None: + """Test the graph connection query with filters.""" _graph_implementation = gr._MicrogridComponentGraph( components={ Component(1, ComponentCategory.GRID), @@ -530,6 +536,7 @@ class Test_MicrogridComponentGraph: """ def test___init__(self) -> None: + """Test the constructor.""" # it is possible to instantiate an empty graph, but # it will not be considered valid until it has been # populated with components and connections @@ -606,6 +613,7 @@ def test___init__(self) -> None: ) def test_refresh_from(self) -> None: + """Test the refresh_from method.""" graph = gr._MicrogridComponentGraph() assert set(graph.components()) == set() assert list(graph.connections()) == [] @@ -767,6 +775,7 @@ def pretend_to_correct_errors(_g: gr._MicrogridComponentGraph) -> None: graph.validate() async def test_refresh_from_api(self) -> None: + """Test the refresh_from_api method.""" graph = gr._MicrogridComponentGraph() assert graph.components() == set() assert graph.connections() == set() @@ -887,6 +896,7 @@ async def test_refresh_from_api(self) -> None: assert await server.graceful_shutdown() def test_validate(self) -> None: + """Test the validate method.""" # `validate` will fail if any of the following are the case: # # * the graph data is not valid @@ -955,6 +965,7 @@ def test_validate(self) -> None: graph.validate() def test__validate_graph(self) -> None: + """Test the _validate_graph method.""" # to ensure clean testing of the individual method, # we cheat by setting underlying graph data directly @@ -1005,6 +1016,7 @@ def test__validate_graph(self) -> None: graph._validate_graph() def test__validate_graph_root(self) -> None: + """Test the _validate_graph_root method.""" # to ensure clean testing of the individual method, # we cheat by setting underlying graph data directly @@ -1127,6 +1139,7 @@ def test__validate_graph_root(self) -> None: graph._validate_graph_root() def test__validate_grid_endpoint(self) -> None: + """Test the _validate_grid_endpoint method.""" # to ensure clean testing of the individual method, # we cheat by setting underlying graph data directly @@ -1171,7 +1184,7 @@ def test__validate_grid_endpoint(self) -> None: match=r"Grid endpoint 1 has graph predecessors: \[Component" r"\(component_id=99, category=, " r"type=None, metadata=None\)\]", - ) as _err_predecessors: + ): graph._validate_grid_endpoint() # grid endpoint has no successors @@ -1196,6 +1209,7 @@ def test__validate_grid_endpoint(self) -> None: graph._validate_grid_endpoint() def test__validate_intermediary_components(self) -> None: + """Test the _validate_intermediary_components method.""" # to ensure clean testing of the individual method, # we cheat by setting underlying graph data directly @@ -1249,6 +1263,7 @@ def test__validate_intermediary_components(self) -> None: graph._validate_intermediary_components() def test__validate_leaf_components(self) -> None: + """Test the _validate_leaf_components method.""" # to ensure clean testing of the individual method, # we cheat by setting underlying graph data directly @@ -1338,6 +1353,7 @@ def test__validate_leaf_components(self) -> None: graph._validate_leaf_components() def test_graph_correction(self) -> None: + """Test the graph correction functionality.""" # Simple test cases for our built-in graph correction # functionality. We test only with `refresh_from`: # for `refresh_from_api` it suffices to test that any diff --git a/tests/microgrid/test_grid.py b/tests/microgrid/test_grid.py index b401266c4..eb1bddfe6 100644 --- a/tests/microgrid/test_grid.py +++ b/tests/microgrid/test_grid.py @@ -1,9 +1,7 @@ # License: MIT # Copyright © 2023 Frequenz Energy-as-a-Service GmbH -""" -Tests for the `Grid` module. -""" +"""Tests for the `Grid` module.""" from frequenz.sdk import microgrid from frequenz.sdk.microgrid.component import Component, ComponentCategory, GridMetadata @@ -14,7 +12,6 @@ async def test_grid() -> None: """Test the grid connection module.""" - # The tests here need to be in this exact sequence, because the grid connection # is a singleton. Once it gets created, it stays in memory for the duration of # the tests, unless we explicitly delete it. @@ -58,6 +55,7 @@ async def test_grid() -> None: microgrid.grid.initialize(components) grid = microgrid.grid.get() + assert grid is not None expected_fuse_current = Current.from_amperes(123.0) expected_fuse = Fuse(expected_fuse_current) diff --git a/tests/microgrid/test_microgrid_api.py b/tests/microgrid/test_microgrid_api.py index 7f93dc416..7c9a41df9 100644 --- a/tests/microgrid/test_microgrid_api.py +++ b/tests/microgrid/test_microgrid_api.py @@ -1,7 +1,8 @@ # License: MIT # Copyright © 2022 Frequenz Energy-as-a-Service GmbH -"""Tests of MicrogridApi""" +"""Tests of MicrogridApi.""" + import asyncio from asyncio.tasks import ALL_COMPLETED from typing import List @@ -164,7 +165,6 @@ async def test_connection_manager_another_method( components: components connections: connections """ - microgrid_client = MagicMock() microgrid_client.components = AsyncMock(return_value=[]) microgrid_client.connections = AsyncMock(return_value=[]) diff --git a/tests/microgrid/test_mock_api.py b/tests/microgrid/test_mock_api.py index 9d1ffa18c..814161636 100644 --- a/tests/microgrid/test_mock_api.py +++ b/tests/microgrid/test_mock_api.py @@ -1,9 +1,7 @@ # License: MIT # Copyright © 2022 Frequenz Energy-as-a-Service GmbH -""" -Tests for the microgrid mock api. -""" +"""Tests for the microgrid mock api.""" # pylint: disable=missing-function-docstring,use-implicit-booleaness-not-comparison # pylint: disable=invalid-name,no-name-in-module,no-member @@ -25,6 +23,7 @@ def test_MockMicrogridServicer() -> None: + """Test the MockMicrogridServicer.""" api = mock_api.MockMicrogridServicer() service_context_mock = Mock(spec=grpc.ServicerContext) assert ( @@ -213,6 +212,7 @@ def test_MockMicrogridServicer() -> None: async def test_MockGrpcServer() -> None: + """Test the MockGrpcServer.""" servicer1 = mock_api.MockMicrogridServicer( components=[ (1, ComponentCategory.COMPONENT_CATEGORY_GRID), diff --git a/tests/microgrid/test_timeout.py b/tests/microgrid/test_timeout.py index 21edddf99..af9c4affd 100644 --- a/tests/microgrid/test_timeout.py +++ b/tests/microgrid/test_timeout.py @@ -39,7 +39,7 @@ "frequenz.sdk.microgrid.client._client.DEFAULT_GRPC_CALL_TIMEOUT", GRPC_CALL_TIMEOUT ) async def test_components_timeout(mocker: MockerFixture) -> None: - """Test if the components() method properly raises AioRpcError""" + """Test if the components() method properly raises AioRpcError.""" servicer = MockMicrogridServicer() def mock_list_components( @@ -66,7 +66,7 @@ def mock_list_components( "frequenz.sdk.microgrid.client._client.DEFAULT_GRPC_CALL_TIMEOUT", GRPC_CALL_TIMEOUT ) async def test_connections_timeout(mocker: MockerFixture) -> None: - """Test if the connections() method properly raises AioRpcError""" + """Test if the connections() method properly raises AioRpcError.""" servicer = MockMicrogridServicer() def mock_list_connections( @@ -93,7 +93,7 @@ def mock_list_connections( "frequenz.sdk.microgrid.client._client.DEFAULT_GRPC_CALL_TIMEOUT", GRPC_CALL_TIMEOUT ) async def test_set_power_timeout(mocker: MockerFixture) -> None: - """Test if the set_power() method properly raises AioRpcError""" + """Test if the set_power() method properly raises AioRpcError.""" servicer = MockMicrogridServicer() def mock_set_power( diff --git a/tests/test_sdk.py b/tests/test_sdk.py index 707bab2bf..46167abb5 100644 --- a/tests/test_sdk.py +++ b/tests/test_sdk.py @@ -1,9 +1,7 @@ # License: MIT # Copyright © 2022 Frequenz Energy-as-a-Service GmbH -""" -Top level tests for the `frequenz.sdk` pacakge -""" +"""Top level tests for the `frequenz.sdk` pacakge.""" import frequenz.sdk from frequenz.sdk import config @@ -11,15 +9,15 @@ def test_sdk_import() -> None: - """Checks that `import frequenz.sdk` works""" + """Checks that `import frequenz.sdk` works.""" assert frequenz.sdk is not None def test_sdk_import_config() -> None: - """Checks that `import frequenz.sdk` works""" + """Checks that `import frequenz.sdk` works.""" assert config is not None def test_sdk_import_config_manager() -> None: - """Checks that `import frequenz.sdk` works""" + """Checks that `import frequenz.sdk` works.""" assert ConfigManagingActor is not None diff --git a/tests/timeseries/_battery_pool/test_battery_pool.py b/tests/timeseries/_battery_pool/test_battery_pool.py index 34923e855..2f4753d2d 100644 --- a/tests/timeseries/_battery_pool/test_battery_pool.py +++ b/tests/timeseries/_battery_pool/test_battery_pool.py @@ -62,7 +62,7 @@ def event_loop() -> Iterator[async_solipsism.EventLoop]: def get_components( mock_microgrid: MockMicrogridClient, component_category: ComponentCategory ) -> set[int]: - """Get components of given category from mock microgrid + """Get components of given category from mock microgrid. Args: mock_microgrid: mock microgrid @@ -301,7 +301,7 @@ async def test_battery_pool_capacity(setup_batteries_pool: SetupArgs) -> None: """Test capacity metric for battery pool with subset of components in the microgrid. Args: - setup_all_batteries: Fixture that creates needed microgrid tools. + setup_batteries_pool: Fixture that creates needed microgrid tools. """ await run_capacity_test(setup_batteries_pool) @@ -319,7 +319,7 @@ async def test_battery_pool_soc(setup_batteries_pool: SetupArgs) -> None: """Test soc metric for battery pool with subset of components in the microgrid. Args: - setup_all_batteries: Fixture that creates needed microgrid tools. + setup_batteries_pool: Fixture that creates needed microgrid tools. """ await run_soc_test(setup_batteries_pool) @@ -337,7 +337,7 @@ async def test_battery_pool_power_bounds(setup_batteries_pool: SetupArgs) -> Non """Test power bounds metric for battery pool with subset of components in the microgrid. Args: - setup_all_batteries: Fixture that creates needed microgrid tools. + setup_batteries_pool: Fixture that creates needed microgrid tools. """ await run_power_bounds_test(setup_batteries_pool) @@ -355,7 +355,7 @@ async def test_battery_pool_temperature(setup_batteries_pool: SetupArgs) -> None """Test temperature for battery pool with subset of components in the microgrid. Args: - setup_all_batteries: Fixture that creates needed microgrid tools. + setup_batteries_pool: Fixture that creates needed microgrid tools. """ await run_temperature_test(setup_batteries_pool) diff --git a/tests/timeseries/mock_microgrid.py b/tests/timeseries/mock_microgrid.py index 3d97f3969..0f2ef3be0 100644 --- a/tests/timeseries/mock_microgrid.py +++ b/tests/timeseries/mock_microgrid.py @@ -75,6 +75,9 @@ def __init__( # pylint: disable=too-many-arguments different namespaces. graph: optional, a graph of components to use instead of the default grid layout. If specified, grid_meter must be None. + + Raises: + ValueError: if both grid_meter and graph are specified. """ if grid_meter is not None and graph is not None: raise ValueError("grid_meter and graph are mutually exclusive") @@ -304,6 +307,7 @@ def add_chps(self, count: int, no_meters: bool = False) -> None: Args: count: number of CHPs to add. + no_meters: if True, do not add a meter for each CHP. """ for _ in range(count): meter_id = self._id_increment * 10 + self.meter_id_suffix @@ -464,7 +468,7 @@ async def send_battery_data(self, socs: list[float]) -> None: """Send raw battery data from the mock microgrid. Args: - values: list of soc values for each battery. + socs: list of soc values for each battery. """ assert len(socs) == len(self.battery_ids) timestamp = datetime.now(tz=timezone.utc) diff --git a/tests/timeseries/test_ev_charger_pool.py b/tests/timeseries/test_ev_charger_pool.py index 32ba8970f..dc2a80b5c 100644 --- a/tests/timeseries/test_ev_charger_pool.py +++ b/tests/timeseries/test_ev_charger_pool.py @@ -27,7 +27,6 @@ class TestEVChargerPool: async def test_state_updates(self, mocker: MockerFixture) -> None: """Test ev charger state updates are visible.""" - mockgrid = MockMicrogrid( grid_meter=False, api_client_streaming=True, sample_rate_s=0.01 ) @@ -47,26 +46,26 @@ async def check_states( for comp_id, exp_state in expected.items(): assert state_tracker.get(comp_id) == exp_state - ## check that all chargers are in idle state. + # check that all chargers are in idle state. expected_states = {evc_id: EVChargerState.IDLE for evc_id in mockgrid.evc_ids} assert len(expected_states) == 5 await check_states(expected_states) - ## check that EV_PLUGGED state gets set + # check that EV_PLUGGED state gets set evc_2_id = mockgrid.evc_ids[2] mockgrid.evc_cable_states[evc_2_id] = EVChargerCableState.EV_PLUGGED mockgrid.evc_component_states[evc_2_id] = EVChargerComponentState.READY expected_states[evc_2_id] = EVChargerState.EV_PLUGGED await check_states(expected_states) - ## check that EV_LOCKED state gets set + # check that EV_LOCKED state gets set evc_3_id = mockgrid.evc_ids[3] mockgrid.evc_cable_states[evc_3_id] = EVChargerCableState.EV_LOCKED mockgrid.evc_component_states[evc_3_id] = EVChargerComponentState.READY expected_states[evc_3_id] = EVChargerState.EV_LOCKED await check_states(expected_states) - ## check that ERROR state gets set + # check that ERROR state gets set evc_1_id = mockgrid.evc_ids[1] mockgrid.evc_cable_states[evc_1_id] = EVChargerCableState.EV_LOCKED mockgrid.evc_component_states[evc_1_id] = EVChargerComponentState.ERROR diff --git a/tests/timeseries/test_formula_engine.py b/tests/timeseries/test_formula_engine.py index 17db38ac3..9f0595851 100644 --- a/tests/timeseries/test_formula_engine.py +++ b/tests/timeseries/test_formula_engine.py @@ -643,7 +643,6 @@ class TestConstantValue: async def test_constant_value(self) -> None: """Test using constant values in formulas.""" - channel_1 = Broadcast[Sample[Quantity]]("channel_1") channel_2 = Broadcast[Sample[Quantity]]("channel_2") diff --git a/tests/timeseries/test_moving_window.py b/tests/timeseries/test_moving_window.py index 7635d49b8..4f37eea84 100644 --- a/tests/timeseries/test_moving_window.py +++ b/tests/timeseries/test_moving_window.py @@ -38,8 +38,8 @@ def fake_time() -> Iterator[time_machine.Coordinates]: async def push_logical_meter_data( sender: Sender[Sample[Quantity]], test_seq: Sequence[float] ) -> None: - """ - Push data in the passed sender to mock `LogicalMeter` behaviour. + """Push data in the passed sender to mock `LogicalMeter` behaviour. + Starting with the First of January 2023. Args: @@ -57,8 +57,7 @@ async def push_logical_meter_data( def init_moving_window( size: timedelta, ) -> Tuple[MovingWindow, Sender[Sample[Quantity]]]: - """ - Initialize the moving window with given shape + """Initialize the moving window with given shape. Args: size: The size of the `MovingWindow` @@ -73,7 +72,7 @@ def init_moving_window( async def test_access_window_by_index() -> None: - """Test indexing a window by integer index""" + """Test indexing a window by integer index.""" window, sender = init_moving_window(timedelta(seconds=1)) async with window: await push_logical_meter_data(sender, [1]) @@ -81,7 +80,7 @@ async def test_access_window_by_index() -> None: async def test_access_window_by_timestamp() -> None: - """Test indexing a window by timestamp""" + """Test indexing a window by timestamp.""" window, sender = init_moving_window(timedelta(seconds=1)) async with window: await push_logical_meter_data(sender, [1]) @@ -89,8 +88,7 @@ async def test_access_window_by_timestamp() -> None: async def test_access_window_by_int_slice() -> None: - """ - Test accessing a subwindow with an integer slice + """Test accessing a subwindow with an integer slice. Note that the second test is overwriting the data of the first test. since the push_lm_data function is starting with the same initial timestamp. @@ -106,7 +104,7 @@ async def test_access_window_by_int_slice() -> None: async def test_access_window_by_ts_slice() -> None: - """Test accessing a subwindow with a timestamp slice""" + """Test accessing a subwindow with a timestamp slice.""" window, sender = init_moving_window(timedelta(seconds=5)) async with window: await push_logical_meter_data(sender, range(0, 5)) @@ -116,7 +114,7 @@ async def test_access_window_by_ts_slice() -> None: async def test_access_empty_window() -> None: - """Test accessing an empty window, should throw IndexError""" + """Test accessing an empty window, should throw IndexError.""" window, _ = init_moving_window(timedelta(seconds=5)) async with window: with pytest.raises(IndexError, match=r"^The buffer is empty\.$"): diff --git a/tests/timeseries/test_periodic_feature_extractor.py b/tests/timeseries/test_periodic_feature_extractor.py index 7320db130..a84d22340 100644 --- a/tests/timeseries/test_periodic_feature_extractor.py +++ b/tests/timeseries/test_periodic_feature_extractor.py @@ -3,8 +3,8 @@ """Tests for the timeseries averager.""" -import collections.abc import contextlib +from collections.abc import AsyncIterator from datetime import datetime, timedelta, timezone from typing import List @@ -28,7 +28,7 @@ @contextlib.asynccontextmanager async def init_feature_extractor( data: List[float], period: timedelta -) -> collections.abc.AsyncIterator[PeriodicFeatureExtractor]: +) -> AsyncIterator[PeriodicFeatureExtractor]: """ Initialize a PeriodicFeatureExtractor with a `MovingWindow` that contains the data. @@ -36,7 +36,7 @@ async def init_feature_extractor( data: The data that is pushed into the moving window. period: The distance between two successive windows. - Returns: + Yields: PeriodicFeatureExtractor """ window, sender = init_moving_window(timedelta(seconds=len(data))) @@ -48,14 +48,14 @@ async def init_feature_extractor( @contextlib.asynccontextmanager async def init_feature_extractor_no_data( period: int, -) -> collections.abc.AsyncIterator[PeriodicFeatureExtractor]: +) -> AsyncIterator[PeriodicFeatureExtractor]: """ Initialize a PeriodicFeatureExtractor with a `MovingWindow` that contains no data. Args: period: The distance between two successive windows. - Returns: + Yields: PeriodicFeatureExtractor """ # We only need the moving window to initialize the PeriodicFeatureExtractor class. @@ -74,9 +74,7 @@ async def init_feature_extractor_no_data( async def test_interval_shifting() -> None: - """ - Test if a interval is properly shifted into a moving window - """ + """Test if a interval is properly shifted into a moving window.""" async with init_feature_extractor( [1, 2, 2, 1, 1, 1, 2, 2, 1, 1], timedelta(seconds=5) ) as feature_extractor: @@ -238,9 +236,10 @@ async def test_011( async def test_profiler_calculate_np() -> None: - """ - Test the calculation of the average using a numpy array and compare it - against the pure python method with the same functionality. + """Test calculating the average with numpy and a pure python version. + + Calculate the average using a numpy array and compare the run time against the pure + python method with the same functionality. """ data = np.array([2, 2.5, 1, 1, 1, 2]) async with init_feature_extractor_no_data(4) as feature_extractor: diff --git a/tests/timeseries/test_quantities.py b/tests/timeseries/test_quantities.py index c9e5311bd..824c13215 100644 --- a/tests/timeseries/test_quantities.py +++ b/tests/timeseries/test_quantities.py @@ -407,7 +407,7 @@ def test_neg() -> None: def test_inf() -> None: - """Test proper formating when using inf in quantities""" + """Test proper formating when using inf in quantities.""" assert f"{Power.from_watts(float('inf'))}" == "inf W" assert f"{Power.from_watts(float('-inf'))}" == "-inf W" @@ -428,7 +428,7 @@ def test_inf() -> None: def test_nan() -> None: - """Test proper formating when using nan in quantities""" + """Test proper formating when using nan in quantities.""" assert f"{Power.from_watts(float('nan'))}" == "nan W" assert f"{Voltage.from_volts(float('nan'))}" == "nan V" assert f"{Current.from_amperes(float('nan'))}" == "nan A" diff --git a/tests/timeseries/test_resampling.py b/tests/timeseries/test_resampling.py index 6f8ab5a6e..7e8e1bd15 100644 --- a/tests/timeseries/test_resampling.py +++ b/tests/timeseries/test_resampling.py @@ -1,9 +1,7 @@ # License: MIT # Copyright © 2022 Frequenz Energy-as-a-Service GmbH -""" -Tests for the `TimeSeriesResampler` -""" +"""Tests for the `TimeSeriesResampler` class.""" from __future__ import annotations diff --git a/tests/timeseries/test_ringbuffer.py b/tests/timeseries/test_ringbuffer.py index f1b4e6708..ee2344989 100644 --- a/tests/timeseries/test_ringbuffer.py +++ b/tests/timeseries/test_ringbuffer.py @@ -393,8 +393,8 @@ def test_cleanup_oldest_gap_timestamp() -> None: def test_delete_oudated_gap() -> None: - """ - Update the buffer such that the gap is no longer valid. + """Test updating the buffer such that the gap is no longer valid. + We introduce two gaps and check that the oldest is removed. """ buffer = OrderedRingBuffer( diff --git a/tests/utils/graph_generator.py b/tests/utils/graph_generator.py index 912a5f6dc..d04d983bd 100644 --- a/tests/utils/graph_generator.py +++ b/tests/utils/graph_generator.py @@ -185,10 +185,14 @@ def _to_graph( """Convert a list of components to a graph. Args: - components: the components to convert to a graph. + parent: the parent component. + children: the children components. Returns: a tuple containing the components and connections of the graph. + + Raises: + ValueError: if the input is invalid. """ def inverter_type(category: ComponentCategory) -> InverterType | None: diff --git a/tests/utils/mock_microgrid_client.py b/tests/utils/mock_microgrid_client.py index 32c390b8c..364267825 100644 --- a/tests/utils/mock_microgrid_client.py +++ b/tests/utils/mock_microgrid_client.py @@ -229,11 +229,14 @@ def _create_mock_api( """Create mock of MicrogridApiClient. Args: - components: set of components. - bat_channel: battery channels to be returned from + bat_channels: battery channels to be returned from MicrogridApiClient.battery_data. - inv_channel: inverter channels to be returned from + inv_channels: inverter channels to be returned from MicrogridApiClient.inverter_data. + meter_channels: meter channels to be returned from + MicrogridApiClient.meter_data. + ev_charger_channels: ev_charger channels to be returned from + MicrogridApiClient.ev_charger_data. Returns: Magic mock instance of MicrogridApiClient. @@ -275,6 +278,7 @@ def _get_battery_receiver( Args: component_id: component_id channels: Broadcast channels + maxsize: Max size of the channel Returns: Receiver from the given channels. @@ -294,6 +298,7 @@ def _get_meter_receiver( Args: component_id: component_id channels: Broadcast channels + maxsize: Max size of the channel Returns: Receiver from the given channels. @@ -313,6 +318,7 @@ def _get_ev_charger_receiver( Args: component_id: component_id channels: Broadcast channels + maxsize: Max size of the channel Returns: Receiver from the given channels. @@ -332,6 +338,7 @@ def _get_inverter_receiver( Args: component_id: component_id channels: Broadcast channels + maxsize: Max size of the channel Returns: Receiver from the given channels.