From d829d238422fea8c2236d37032e5fa6ede389dc3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Aug 2026 18:14:29 +0000 Subject: [PATCH 1/3] Bump the minor group across 1 directory with 4 updates Bumps the minor group with 4 updates in the / directory: [mypy](https://github.com/python/mypy), [nox](https://github.com/wntrblm/nox), [pytest](https://github.com/pytest-dev/pytest) and [hypothesis](https://github.com/HypothesisWorks/hypothesis). Updates `mypy` from 2.1.0 to 2.3.0 - [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md) - [Commits](https://github.com/python/mypy/compare/v2.1.0...v2.3.0) Updates `nox` from 2026.4.10 to 2026.7.11 - [Release notes](https://github.com/wntrblm/nox/releases) - [Changelog](https://github.com/wntrblm/nox/blob/main/CHANGELOG.md) - [Commits](https://github.com/wntrblm/nox/compare/2026.04.10...2026.07.11) Updates `pytest` from 9.0.3 to 9.1.1 - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/9.0.3...9.1.1) Updates `hypothesis` from 6.155.7 to 6.163.0 - [Release notes](https://github.com/HypothesisWorks/hypothesis/releases) - [Commits](https://github.com/HypothesisWorks/hypothesis/compare/v6.155.7...v6.163.0) --- updated-dependencies: - dependency-name: mypy dependency-version: 2.3.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: minor - dependency-name: nox dependency-version: 2026.7.11 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: minor - dependency-name: pytest dependency-version: 9.1.1 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: minor - dependency-name: hypothesis dependency-version: 6.163.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 6c6b00bb7..5c68df294 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -69,27 +69,27 @@ dev-mkdocs = [ "frequenz-repo-config[lib] == 0.18.0", ] dev-mypy = [ - "mypy == 2.1.0", + "mypy == 2.3.0", "types-Markdown == 3.10.2.20260518", "types-protobuf == 7.34.1.20260518", "types-setuptools == 82.0.0.20260518", # For checking the noxfile, docs/ script, and tests "frequenz-sdk[dev-mkdocs,dev-noxfile,dev-pytest]", ] -dev-noxfile = ["nox == 2026.4.10", "frequenz-repo-config[lib] == 0.18.0"] +dev-noxfile = ["nox == 2026.7.11", "frequenz-repo-config[lib] == 0.18.0"] dev-pylint = [ "pylint == 4.0.6", # For checking the noxfile, docs/ script, and tests "frequenz-sdk[dev-mkdocs,dev-noxfile,dev-pytest]", ] dev-pytest = [ - "pytest == 9.0.3", + "pytest == 9.1.1", "frequenz-repo-config[extra-lint-examples] == 0.18.0", "pytest-mock == 3.15.1", "pytest-asyncio == 1.4.0", "time-machine == 2.16.0", "async-solipsism == 0.9", - "hypothesis == 6.155.7", + "hypothesis == 6.163.0", ] dev = [ "frequenz-sdk[dev-mkdocs,dev-flake8,dev-formatting,dev-mkdocs,dev-mypy,dev-noxfile,dev-pylint,dev-pytest]", From c1befb8e63504471f51e8e0489eb936909e3661a Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Wed, 12 Aug 2026 15:00:01 +0000 Subject: [PATCH 2/3] Reimplement `approx_time` on top of `pytest.approx()` `approx_time` was a subclass of the private `_pytest.python_api.ApproxBase`, written because `pytest.approx()` did not support `datetime`/`timedelta`. The comment on the import pointed at the upstream issue tracking this: https://github.com/pytest-dev/pytest/issues/8395 That issue was fixed in pytest 9.1.0, which we now depend on: `approx()` dispatches `datetime`/`timedelta` to a new `ApproxTimedelta` class doing exactly what our own implementation did. So `approx_time` becomes a thin wrapper that only defaults the tolerance to 1ms, since `approx()` requires an explicit tolerance for these types. Besides dropping the dependency on a private pytest API, this also gives better failure output, as `ApproxTimedelta` implements `_repr_compare()`, which our subclass didn't, so it now reports the absolute difference and the tolerance instead of only the expected value. Signed-off-by: Leandro Lucarella --- .../_resampling/wall_clock_timer/util.py | 63 +++++-------------- 1 file changed, 15 insertions(+), 48 deletions(-) diff --git a/tests/timeseries/_resampling/wall_clock_timer/util.py b/tests/timeseries/_resampling/wall_clock_timer/util.py index 1068c467e..9ab110c22 100644 --- a/tests/timeseries/_resampling/wall_clock_timer/util.py +++ b/tests/timeseries/_resampling/wall_clock_timer/util.py @@ -9,18 +9,10 @@ from collections.abc import Coroutine, Sequence from dataclasses import dataclass, field from datetime import datetime, timedelta, timezone -from typing import NamedTuple, TypeVar, assert_never, overload +from typing import Any, NamedTuple, TypeVar, assert_never, overload from unittest.mock import MagicMock import pytest - -# This is not great, we are depending on an internal pytest API, but it is -# the most convenient way to provide a custom approx() comparison for datetime -# and timedelta. -# Other alternatives proven to be even more complex and hacky. -# It also looks like we are not the only ones doing this, see: -# https://github.com/pytest-dev/pytest/issues/8395 -from _pytest.python_api import ApproxBase from typing_extensions import override from frequenz.sdk.timeseries import ClocksInfo, TickInfo @@ -86,49 +78,24 @@ def mono_now() -> float: return asyncio.get_running_loop().time() -# Pylint complains about abstract-method because _yield_comparisons is not implemented -# but it is used only in the default __eq__ method, which we are re-defining, so we can -# ignore it. -class approx_time(ApproxBase): # pylint: disable=invalid-name, abstract-method +def approx_time( + expected: datetime | timedelta, + *, + abs: timedelta = timedelta(milliseconds=1), # pylint: disable=redefined-builtin +) -> Any: """Perform approximate comparisons for datetime or timedelta objects. - Inherits from `ApproxBase` to provide a rich comparison output in pytest. - """ + This is only a thin wrapper around `pytest.approx()` to default the tolerance to + 1ms, as `pytest.approx()` requires an explicit tolerance for these types. - expected: datetime | timedelta - abs: timedelta - - def __init__( - self, - expected: datetime | timedelta, - *, - abs: timedelta = timedelta(milliseconds=1), # pylint: disable=redefined-builtin - ) -> None: - """Initialize this instance.""" - if abs < timedelta(): - raise ValueError( - f"absolute tolerance must be a non-negative timedelta, not {abs}" - ) - super().__init__(expected, abs=abs) + Args: + expected: The expected `datetime` or `timedelta` to compare against. + abs: The absolute tolerance as a `timedelta`. Defaults to 1ms. - def __repr__(self) -> str: - """Return a string representation of this instance.""" - return f"{self.expected} ± {self.abs}" - - def __eq__(self, actual: object) -> bool: - """Compare this instance with another object.""" - # We need to split the cases for datetime and timedelta for type checking - # reasons. - diff: timedelta - match (self.expected, actual): - case (datetime(), datetime()): - diff = self.expected - actual - case (timedelta(), timedelta()): - diff = self.expected - actual - case _: - return NotImplemented - - return abs(diff) <= self.abs + Returns: + An object comparing equal to any value within `abs` of `expected`. + """ + return pytest.approx(expected, abs=abs) # We need to rewrite most of the attributes in these classes to use approximate From b0929bc8e4bf32e6b299fae835f950b1e476e7e4 Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Wed, 12 Aug 2026 15:04:43 +0000 Subject: [PATCH 3/3] Use `approx_time` to compare timedeltas in tests pytest 9.1.0 added proper support for `datetime`/`timedelta` in `pytest.approx()`, and as part of it, it now refuses to guess a tolerance for these types, raising instead: TypeError: pytest.approx() requires an explicit tolerance for datetime/timedelta comparisons: e.g. approx(expected, abs=timedelta(seconds=1)) or approx(expected, rel=0.01) This broke 12 tests in `test_clocksinfo.py` and `test_config.py`, which were using the bare `pytest.approx()` on `timedelta`s instead of the `approx_time()` helper used everywhere else in this test package. These assertions never really used a tolerance anyway: all the compared values are exact, so they only ever succeeded through the exact-equality short-circuit in `approx()`, before any tolerance was computed. Switching them to `approx_time()` provides the required tolerance (1ms by default) and makes the whole test package consistent. Signed-off-by: Leandro Lucarella --- .../_resampling/wall_clock_timer/test_clocksinfo.py | 8 +++++--- .../_resampling/wall_clock_timer/test_config.py | 10 ++++++---- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/tests/timeseries/_resampling/wall_clock_timer/test_clocksinfo.py b/tests/timeseries/_resampling/wall_clock_timer/test_clocksinfo.py index 55b7e30da..8c3ad355e 100644 --- a/tests/timeseries/_resampling/wall_clock_timer/test_clocksinfo.py +++ b/tests/timeseries/_resampling/wall_clock_timer/test_clocksinfo.py @@ -12,6 +12,8 @@ from frequenz.sdk.timeseries._resampling._wall_clock_timer import ClocksInfo +from .util import approx_time + _DEFAULT_MONOTONIC_REQUESTED_SLEEP = timedelta(seconds=1.0) _DEFAULT_MONOTONIC_TIME = 1234.5 _DEFAULT_WALL_CLOCK_TIME = datetime(2023, 1, 1, tzinfo=timezone.utc) @@ -141,7 +143,7 @@ def test_monotonic_drift( monotonic_elapsed=monotonic_elapsed, wall_clock_elapsed=_DEFAULT_WALL_CLOCK_ELAPSED, ) - assert info.monotonic_drift == pytest.approx(expected_drift) + assert info.monotonic_drift == approx_time(expected_drift) @pytest.mark.parametrize( @@ -166,7 +168,7 @@ def test_wall_clock_jump( monotonic_elapsed=monotonic_elapsed, wall_clock_elapsed=wall_clock_elapsed, ) - assert info.wall_clock_jump == pytest.approx(expected_jump) + assert info.wall_clock_jump == approx_time(expected_jump) @dataclass(kw_only=True, frozen=True) @@ -219,7 +221,7 @@ def test_wall_clock_factor(case: _TestCaseWallClockFactor) -> None: wall_clock_elapsed=case.wall_clock_elapsed, ) assert info.wall_clock_factor == pytest.approx(case.expected_factor) - assert info.wall_clock_to_monotonic(case.wall_clock_elapsed) == pytest.approx( + assert info.wall_clock_to_monotonic(case.wall_clock_elapsed) == approx_time( case.monotonic_elapsed ) diff --git a/tests/timeseries/_resampling/wall_clock_timer/test_config.py b/tests/timeseries/_resampling/wall_clock_timer/test_config.py index 5dd7e50d1..ae65c8307 100644 --- a/tests/timeseries/_resampling/wall_clock_timer/test_config.py +++ b/tests/timeseries/_resampling/wall_clock_timer/test_config.py @@ -12,15 +12,17 @@ from frequenz.sdk.timeseries._resampling._wall_clock_timer import WallClockTimerConfig +from .util import approx_time + def test_from_interval_defaults() -> None: """Test WallClockTimerConfig.from_interval() with only interval (all defaults).""" interval = timedelta(seconds=10) config = WallClockTimerConfig.from_interval(interval) assert config.align_to == UNIX_EPOCH - assert config.async_drift_tolerance == pytest.approx(timedelta(seconds=1.0)) + assert config.async_drift_tolerance == approx_time(timedelta(seconds=1.0)) assert config.wall_clock_drift_tolerance_factor == pytest.approx(0.1) - assert config.wall_clock_jump_threshold == pytest.approx(timedelta(seconds=10.0)) + assert config.wall_clock_jump_threshold == approx_time(timedelta(seconds=10.0)) def test_from_interval_all_args() -> None: @@ -38,9 +40,9 @@ def test_from_interval_all_args() -> None: wall_clock_jump_threshold_factor=jump_factor, ) assert config.align_to == align_to - assert config.async_drift_tolerance == pytest.approx(timedelta(seconds=1.0)) + assert config.async_drift_tolerance == approx_time(timedelta(seconds=1.0)) assert config.wall_clock_drift_tolerance_factor == pytest.approx(0.3) - assert config.wall_clock_jump_threshold == pytest.approx(timedelta(seconds=2.0)) + assert config.wall_clock_jump_threshold == approx_time(timedelta(seconds=2.0)) @pytest.mark.parametrize(