frequenz-core 1.3.0__tar.gz → 1.5.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. {frequenz_core-1.3.0/src/frequenz_core.egg-info → frequenz_core-1.5.0}/PKG-INFO +131 -34
  2. {frequenz_core-1.3.0 → frequenz_core-1.5.0}/README.md +101 -5
  3. frequenz_core-1.5.0/RELEASE_NOTES.md +11 -0
  4. {frequenz_core-1.3.0 → frequenz_core-1.5.0}/pyproject.toml +48 -30
  5. {frequenz_core-1.3.0 → frequenz_core-1.5.0}/src/frequenz/core/enum.py +4 -2
  6. {frequenz_core-1.3.0 → frequenz_core-1.5.0}/src/frequenz/core/id.py +18 -2
  7. {frequenz_core-1.3.0 → frequenz_core-1.5.0}/src/frequenz/core/math.py +34 -40
  8. {frequenz_core-1.3.0 → frequenz_core-1.5.0}/src/frequenz/core/typing.py +52 -2
  9. frequenz_core-1.5.0/src/frequenz/core/warnings/__init__.py +71 -0
  10. frequenz_core-1.5.0/src/frequenz/core/warnings/_asserting.py +309 -0
  11. frequenz_core-1.5.0/src/frequenz/core/warnings/_deprecated_aliases.py +520 -0
  12. frequenz_core-1.5.0/src/frequenz/core/warnings/_ignoring.py +366 -0
  13. {frequenz_core-1.3.0 → frequenz_core-1.5.0/src/frequenz_core.egg-info}/PKG-INFO +131 -34
  14. {frequenz_core-1.3.0 → frequenz_core-1.5.0}/src/frequenz_core.egg-info/SOURCES.txt +6 -0
  15. frequenz_core-1.5.0/src/frequenz_core.egg-info/requires.txt +50 -0
  16. frequenz_core-1.5.0/src/frequenz_core.egg-info/scm_file_list.json +66 -0
  17. frequenz_core-1.5.0/src/frequenz_core.egg-info/scm_version.json +8 -0
  18. frequenz_core-1.3.0/RELEASE_NOTES.md +0 -9
  19. frequenz_core-1.3.0/src/frequenz_core.egg-info/requires.txt +0 -48
  20. {frequenz_core-1.3.0 → frequenz_core-1.5.0}/LICENSE +0 -0
  21. {frequenz_core-1.3.0 → frequenz_core-1.5.0}/MANIFEST.in +0 -0
  22. {frequenz_core-1.3.0 → frequenz_core-1.5.0}/setup.cfg +0 -0
  23. {frequenz_core-1.3.0 → frequenz_core-1.5.0}/src/frequenz/core/__init__.py +0 -0
  24. {frequenz_core-1.3.0 → frequenz_core-1.5.0}/src/frequenz/core/conftest.py +0 -0
  25. {frequenz_core-1.3.0 → frequenz_core-1.5.0}/src/frequenz/core/datetime.py +0 -0
  26. {frequenz_core-1.3.0 → frequenz_core-1.5.0}/src/frequenz/core/module.py +0 -0
  27. {frequenz_core-1.3.0 → frequenz_core-1.5.0}/src/frequenz/core/py.typed +0 -0
  28. {frequenz_core-1.3.0 → frequenz_core-1.5.0}/src/frequenz_core.egg-info/dependency_links.txt +0 -0
  29. {frequenz_core-1.3.0 → frequenz_core-1.5.0}/src/frequenz_core.egg-info/top_level.txt +0 -0
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: frequenz-core
3
- Version: 1.3.0
3
+ Version: 1.5.0
4
4
  Summary: Core utilities to complement Python's standard library
5
5
  Author-email: Frequenz Energy-as-a-Service GmbH <floss@frequenz.com>
6
- License: MIT
6
+ License-Expression: MIT
7
7
  Project-URL: Documentation, https://frequenz-floss.github.io/frequenz-core-python/
8
8
  Project-URL: Changelog, https://github.com/frequenz-floss/frequenz-core-python/releases
9
9
  Project-URL: Issues, https://github.com/frequenz-floss/frequenz-core-python/issues
@@ -12,7 +12,6 @@ Project-URL: Support, https://github.com/frequenz-floss/frequenz-core-python/dis
12
12
  Keywords: asyncio,collections,core,datetime,frequenz,lib,library,math,python,stdlib,typing
13
13
  Classifier: Development Status :: 5 - Production/Stable
14
14
  Classifier: Intended Audience :: Developers
15
- Classifier: License :: OSI Approved :: MIT License
16
15
  Classifier: Programming Language :: Python :: 3
17
16
  Classifier: Programming Language :: Python :: 3 :: Only
18
17
  Classifier: Topic :: Software Development :: Libraries
@@ -22,42 +21,44 @@ Description-Content-Type: text/markdown
22
21
  License-File: LICENSE
23
22
  Requires-Dist: typing-extensions<5,>=4.13.0
24
23
  Provides-Extra: dev-flake8
25
- Requires-Dist: flake8==7.3.0; extra == "dev-flake8"
24
+ Requires-Dist: flake8==7.4.1; extra == "dev-flake8"
25
+ Requires-Dist: flake8-datetimez==26.8.1; extra == "dev-flake8"
26
26
  Requires-Dist: flake8-docstrings==1.7.0; extra == "dev-flake8"
27
- Requires-Dist: flake8-pyproject==1.2.3; extra == "dev-flake8"
28
- Requires-Dist: pydoclint==0.6.11; extra == "dev-flake8"
27
+ Requires-Dist: flake8-pyproject==1.2.4; extra == "dev-flake8"
28
+ Requires-Dist: pydoclint==0.10.0; extra == "dev-flake8"
29
29
  Requires-Dist: pydocstyle==6.3.0; extra == "dev-flake8"
30
30
  Provides-Extra: dev-formatting
31
- Requires-Dist: black==25.1.0; extra == "dev-formatting"
32
- Requires-Dist: isort==6.0.1; extra == "dev-formatting"
31
+ Requires-Dist: black==26.5.1; extra == "dev-formatting"
32
+ Requires-Dist: isort==9.0.1; extra == "dev-formatting"
33
33
  Provides-Extra: dev-mkdocs
34
- Requires-Dist: Markdown==3.8.2; extra == "dev-mkdocs"
35
- Requires-Dist: black==25.1.0; extra == "dev-mkdocs"
36
- Requires-Dist: mike==2.1.3; extra == "dev-mkdocs"
37
- Requires-Dist: mkdocs-gen-files==0.5.0; extra == "dev-mkdocs"
38
- Requires-Dist: mkdocs-literate-nav==0.6.2; extra == "dev-mkdocs"
39
- Requires-Dist: mkdocs-macros-plugin==1.3.9; extra == "dev-mkdocs"
40
- Requires-Dist: mkdocs-material==9.6.18; extra == "dev-mkdocs"
41
- Requires-Dist: mkdocstrings[python]==0.30.0; extra == "dev-mkdocs"
42
- Requires-Dist: mkdocstrings-python==1.18.2; extra == "dev-mkdocs"
43
- Requires-Dist: frequenz-repo-config[lib]==0.13.5; extra == "dev-mkdocs"
34
+ Requires-Dist: Markdown==3.11; extra == "dev-mkdocs"
35
+ Requires-Dist: black==26.5.1; extra == "dev-mkdocs"
36
+ Requires-Dist: griffe-warnings-deprecated==1.1.1; extra == "dev-mkdocs"
37
+ Requires-Dist: mike==2.2.0; extra == "dev-mkdocs"
38
+ Requires-Dist: mkdocs-gen-files==0.6.1; extra == "dev-mkdocs"
39
+ Requires-Dist: mkdocs-literate-nav==0.6.3; extra == "dev-mkdocs"
40
+ Requires-Dist: mkdocs-macros-plugin==1.5.0; extra == "dev-mkdocs"
41
+ Requires-Dist: mkdocs-material==9.7.7; extra == "dev-mkdocs"
42
+ Requires-Dist: mkdocstrings[python]==1.0.6; extra == "dev-mkdocs"
43
+ Requires-Dist: mkdocstrings-python==2.0.7; extra == "dev-mkdocs"
44
+ Requires-Dist: frequenz-repo-config[lib]==0.19.0; extra == "dev-mkdocs"
44
45
  Provides-Extra: dev-mypy
45
- Requires-Dist: mypy==1.17.1; extra == "dev-mypy"
46
- Requires-Dist: types-Markdown==3.8.0.20250809; extra == "dev-mypy"
46
+ Requires-Dist: mypy==2.3.1; extra == "dev-mypy"
47
+ Requires-Dist: types-Markdown==3.10.2.20260712; extra == "dev-mypy"
47
48
  Requires-Dist: frequenz-core[dev-mkdocs,dev-noxfile,dev-pytest]; extra == "dev-mypy"
48
49
  Provides-Extra: dev-noxfile
49
- Requires-Dist: nox==2025.5.1; extra == "dev-noxfile"
50
- Requires-Dist: frequenz-repo-config[lib]==0.13.5; extra == "dev-noxfile"
50
+ Requires-Dist: nox==2026.8.17; extra == "dev-noxfile"
51
+ Requires-Dist: frequenz-repo-config[lib]==0.19.0; extra == "dev-noxfile"
51
52
  Provides-Extra: dev-pylint
52
53
  Requires-Dist: frequenz-core[dev-mkdocs,dev-noxfile,dev-pytest]; extra == "dev-pylint"
53
54
  Provides-Extra: dev-pytest
54
- Requires-Dist: pytest==8.4.1; extra == "dev-pytest"
55
- Requires-Dist: pylint==3.3.8; extra == "dev-pytest"
56
- Requires-Dist: frequenz-repo-config[extra-lint-examples]==0.13.5; extra == "dev-pytest"
57
- Requires-Dist: pytest-mock==3.14.1; extra == "dev-pytest"
58
- Requires-Dist: pytest-asyncio==1.1.0; extra == "dev-pytest"
59
- Requires-Dist: async-solipsism==0.8; extra == "dev-pytest"
60
- Requires-Dist: hypothesis==6.138.11; extra == "dev-pytest"
55
+ Requires-Dist: pytest==9.1.1; extra == "dev-pytest"
56
+ Requires-Dist: pylint==4.0.8; extra == "dev-pytest"
57
+ Requires-Dist: frequenz-repo-config[extra-lint-examples]==0.19.0; extra == "dev-pytest"
58
+ Requires-Dist: pytest-mock==3.16.0; extra == "dev-pytest"
59
+ Requires-Dist: pytest-asyncio==1.4.0; extra == "dev-pytest"
60
+ Requires-Dist: async-solipsism==0.9; extra == "dev-pytest"
61
+ Requires-Dist: hypothesis==6.168.2; extra == "dev-pytest"
61
62
  Provides-Extra: dev
62
63
  Requires-Dist: frequenz-core[dev-flake8,dev-formatting,dev-mkdocs,dev-mypy,dev-noxfile,dev-pylint,dev-pytest]; extra == "dev"
63
64
  Dynamic: license-file
@@ -72,8 +73,8 @@ Dynamic: license-file
72
73
 
73
74
  Core utilities to complement Python's standard library. This library provides
74
75
  essential building blocks for Python applications, including mathematical
75
- utilities, datetime constants, typing helpers, strongly-typed identifiers, and
76
- module introspection tools.
76
+ utilities, datetime constants, typing helpers, strongly-typed identifiers,
77
+ module introspection tools, and warning and deprecation helpers.
77
78
 
78
79
  The `frequenz-core` library is designed to be lightweight, type-safe, and
79
80
  follow modern Python best practices. It fills common gaps in the standard
@@ -83,8 +84,8 @@ library with utilities that are frequently needed across different projects.
83
84
 
84
85
  The following platforms are officially supported (tested):
85
86
 
86
- - **Python:** 3.11
87
- - **Operating System:** Ubuntu Linux 20.04
87
+ - **Python:** 3.11, 3.12, 3.13, 3.14
88
+ - **Operating System:** Ubuntu Linux 24.04
88
89
  - **Architectures:** amd64, arm64
89
90
 
90
91
  ## Installation
@@ -156,7 +157,7 @@ positive = Interval(0, None) # [0, ∞]
156
157
  assert 1000 in positive # True
157
158
  ```
158
159
 
159
- ### `Enum` with deprecated members
160
+ ### `Enum` Utilities
160
161
 
161
162
  Define enums with deprecated members that raise deprecation warnings when
162
163
  accessed:
@@ -200,6 +201,102 @@ class ApiClient:
200
201
  client = ApiClient.create("my-api-key") # ✅ Works
201
202
  ```
202
203
 
204
+ Annotate floating point values honestly, as Python's numeric tower lets `int`
205
+ values through any `float` annotation:
206
+
207
+ ```python
208
+ from typing import assert_never
209
+
210
+ from frequenz.core.typing import FloatInt
211
+
212
+ def describe(value: FloatInt | None) -> str:
213
+ match value:
214
+ case float() | int():
215
+ return f"number {value}"
216
+ case None:
217
+ return "nothing"
218
+ case unexpected:
219
+ assert_never(unexpected)
220
+
221
+ assert describe(1) == "number 1" # ✅ `case float():` alone would crash here
222
+ assert describe(1.5) == "number 1.5"
223
+ assert describe(None) == "nothing"
224
+ ```
225
+
226
+ ### Warnings/Deprecations Utilities
227
+
228
+ Silence a warning around a call without the side effect
229
+ [`warnings.catch_warnings`](https://docs.python.org/3/library/warnings.html#warnings.catch_warnings)
230
+ has, which is to reset the deduplication history of the whole program, so
231
+ every warning already shown is shown again
232
+ ([python/cpython#73858](https://github.com/python/cpython/issues/73858)):
233
+
234
+ ```python
235
+ import warnings
236
+
237
+ from frequenz.core.warnings import ignoring_deprecations
238
+
239
+ def legacy_parse(raw: str) -> int:
240
+ warnings.warn("legacy_parse() is deprecated", DeprecationWarning, stacklevel=2)
241
+ return int(raw)
242
+
243
+ def parse(raw: str) -> int:
244
+ # Only around the call that reaches the deprecated symbol.
245
+ with ignoring_deprecations():
246
+ return legacy_parse(raw)
247
+
248
+ with warnings.catch_warnings(record=True, action="default") as shown:
249
+ for _ in range(10):
250
+ warnings.warn("said once", UserWarning)
251
+ parse("1")
252
+
253
+ assert len(shown) == 1 # ✅ `catch_warnings()` in `parse()` would show it 10 times
254
+ ```
255
+
256
+ Keep the old import path of a symbol that moved working, serving the very same
257
+ object so `isinstance` keeps working through both paths:
258
+
259
+ ```python
260
+ from typing import TYPE_CHECKING, TypeAlias
261
+
262
+ from frequenz.core.warnings import DeprecatedAlias, deprecated_aliases
263
+
264
+ if TYPE_CHECKING:
265
+ # Type checkers can't see the runtime `__getattr__` in the `else` branch.
266
+ from decimal import Decimal as _Decimal
267
+ from fractions import Fraction as _Fraction
268
+
269
+ Decimal: TypeAlias = _Decimal
270
+ Rational: TypeAlias = _Fraction
271
+ else:
272
+ # In the `else`, never at module level: a `__getattr__` mypy can see makes
273
+ # every name it doesn't know in this module `Any`, typos in downstream
274
+ # imports included.
275
+ __getattr__ = deprecated_aliases(
276
+ __name__,
277
+ # Warns "<module>.Decimal is deprecated since v1.2.0. Use
278
+ # decimal.Decimal instead."
279
+ DeprecatedAlias("Decimal", new_module="decimal", since="v1.2.0"),
280
+ # Renamed on the way out
281
+ DeprecatedAlias(
282
+ "Rational", new_module="fractions", new_name="Fraction", since="v1.3.0"
283
+ ),
284
+ )
285
+ ```
286
+
287
+ And check in a test that a piece of code doesn't warn, without the `"error"`
288
+ filter that would make `warnings.warn()` raise inside the code under test:
289
+
290
+ ```python
291
+ from frequenz.core.warnings import asserting_no_deprecations
292
+
293
+ def parse(raw: str) -> int:
294
+ return int(raw)
295
+
296
+ with asserting_no_deprecations():
297
+ assert parse("1") == 1
298
+ ```
299
+
203
300
  ### Strongly-Typed IDs
204
301
 
205
302
  Create type-safe identifiers for different entities:
@@ -8,8 +8,8 @@
8
8
 
9
9
  Core utilities to complement Python's standard library. This library provides
10
10
  essential building blocks for Python applications, including mathematical
11
- utilities, datetime constants, typing helpers, strongly-typed identifiers, and
12
- module introspection tools.
11
+ utilities, datetime constants, typing helpers, strongly-typed identifiers,
12
+ module introspection tools, and warning and deprecation helpers.
13
13
 
14
14
  The `frequenz-core` library is designed to be lightweight, type-safe, and
15
15
  follow modern Python best practices. It fills common gaps in the standard
@@ -19,8 +19,8 @@ library with utilities that are frequently needed across different projects.
19
19
 
20
20
  The following platforms are officially supported (tested):
21
21
 
22
- - **Python:** 3.11
23
- - **Operating System:** Ubuntu Linux 20.04
22
+ - **Python:** 3.11, 3.12, 3.13, 3.14
23
+ - **Operating System:** Ubuntu Linux 24.04
24
24
  - **Architectures:** amd64, arm64
25
25
 
26
26
  ## Installation
@@ -92,7 +92,7 @@ positive = Interval(0, None) # [0, ∞]
92
92
  assert 1000 in positive # True
93
93
  ```
94
94
 
95
- ### `Enum` with deprecated members
95
+ ### `Enum` Utilities
96
96
 
97
97
  Define enums with deprecated members that raise deprecation warnings when
98
98
  accessed:
@@ -136,6 +136,102 @@ class ApiClient:
136
136
  client = ApiClient.create("my-api-key") # ✅ Works
137
137
  ```
138
138
 
139
+ Annotate floating point values honestly, as Python's numeric tower lets `int`
140
+ values through any `float` annotation:
141
+
142
+ ```python
143
+ from typing import assert_never
144
+
145
+ from frequenz.core.typing import FloatInt
146
+
147
+ def describe(value: FloatInt | None) -> str:
148
+ match value:
149
+ case float() | int():
150
+ return f"number {value}"
151
+ case None:
152
+ return "nothing"
153
+ case unexpected:
154
+ assert_never(unexpected)
155
+
156
+ assert describe(1) == "number 1" # ✅ `case float():` alone would crash here
157
+ assert describe(1.5) == "number 1.5"
158
+ assert describe(None) == "nothing"
159
+ ```
160
+
161
+ ### Warnings/Deprecations Utilities
162
+
163
+ Silence a warning around a call without the side effect
164
+ [`warnings.catch_warnings`](https://docs.python.org/3/library/warnings.html#warnings.catch_warnings)
165
+ has, which is to reset the deduplication history of the whole program, so
166
+ every warning already shown is shown again
167
+ ([python/cpython#73858](https://github.com/python/cpython/issues/73858)):
168
+
169
+ ```python
170
+ import warnings
171
+
172
+ from frequenz.core.warnings import ignoring_deprecations
173
+
174
+ def legacy_parse(raw: str) -> int:
175
+ warnings.warn("legacy_parse() is deprecated", DeprecationWarning, stacklevel=2)
176
+ return int(raw)
177
+
178
+ def parse(raw: str) -> int:
179
+ # Only around the call that reaches the deprecated symbol.
180
+ with ignoring_deprecations():
181
+ return legacy_parse(raw)
182
+
183
+ with warnings.catch_warnings(record=True, action="default") as shown:
184
+ for _ in range(10):
185
+ warnings.warn("said once", UserWarning)
186
+ parse("1")
187
+
188
+ assert len(shown) == 1 # ✅ `catch_warnings()` in `parse()` would show it 10 times
189
+ ```
190
+
191
+ Keep the old import path of a symbol that moved working, serving the very same
192
+ object so `isinstance` keeps working through both paths:
193
+
194
+ ```python
195
+ from typing import TYPE_CHECKING, TypeAlias
196
+
197
+ from frequenz.core.warnings import DeprecatedAlias, deprecated_aliases
198
+
199
+ if TYPE_CHECKING:
200
+ # Type checkers can't see the runtime `__getattr__` in the `else` branch.
201
+ from decimal import Decimal as _Decimal
202
+ from fractions import Fraction as _Fraction
203
+
204
+ Decimal: TypeAlias = _Decimal
205
+ Rational: TypeAlias = _Fraction
206
+ else:
207
+ # In the `else`, never at module level: a `__getattr__` mypy can see makes
208
+ # every name it doesn't know in this module `Any`, typos in downstream
209
+ # imports included.
210
+ __getattr__ = deprecated_aliases(
211
+ __name__,
212
+ # Warns "<module>.Decimal is deprecated since v1.2.0. Use
213
+ # decimal.Decimal instead."
214
+ DeprecatedAlias("Decimal", new_module="decimal", since="v1.2.0"),
215
+ # Renamed on the way out
216
+ DeprecatedAlias(
217
+ "Rational", new_module="fractions", new_name="Fraction", since="v1.3.0"
218
+ ),
219
+ )
220
+ ```
221
+
222
+ And check in a test that a piece of code doesn't warn, without the `"error"`
223
+ filter that would make `warnings.warn()` raise inside the code under test:
224
+
225
+ ```python
226
+ from frequenz.core.warnings import asserting_no_deprecations
227
+
228
+ def parse(raw: str) -> int:
229
+ return int(raw)
230
+
231
+ with asserting_no_deprecations():
232
+ assert parse("1") == 1
233
+ ```
234
+
139
235
  ### Strongly-Typed IDs
140
236
 
141
237
  Create type-safe identifiers for different entities:
@@ -0,0 +1,11 @@
1
+ # Frequenz Core Library Release Notes
2
+
3
+ ## New Features
4
+
5
+ A new `frequenz.core.warnings` module with:
6
+
7
+ - `ignoring_warnings()` and `ignoring_deprecations()` to silence warnings/deprecations around a piece of code. Unlike `warnings.catch_warnings()`, entering and leaving it doesn't reset the warnings deduplication history of the program, so warnings already shown are not shown again, working around [python/cpython#73858](https://github.com/python/cpython/issues/73858). It costs about the same as the standard library block, so it is also usable on a hot path, where repairing the damage afterwards would not be.
8
+
9
+ - `asserting_no_warnings()`, and its `asserting_no_deprecations()` shortcut, fail when the code in the block raises a matching warning/deprecation, listing each one with the place it came from. They are meant for tests, and are a better tool than an `"error"` filter, which makes `warnings.warn()` raise inside the code under test and so changes the very behaviour the test is checking.
10
+
11
+ - `deprecated_aliases()` builds a module `__getattr__` that warns when a symbol that moved to another module, or was renamed, is reached through its old import path, serving the very same object so `isinstance` keeps working through both paths.
@@ -3,9 +3,9 @@
3
3
 
4
4
  [build-system]
5
5
  requires = [
6
- "setuptools == 80.9.0",
7
- "setuptools_scm[toml] == 9.2.0",
8
- "frequenz-repo-config[lib] == 0.13.5",
6
+ "setuptools == 84.0.0",
7
+ "setuptools_scm[toml] == 10.3.4",
8
+ "frequenz-repo-config[lib] == 0.19.0",
9
9
  ]
10
10
  build-backend = "setuptools.build_meta"
11
11
 
@@ -13,7 +13,8 @@ build-backend = "setuptools.build_meta"
13
13
  name = "frequenz-core"
14
14
  description = "Core utilities to complement Python's standard library"
15
15
  readme = "README.md"
16
- license = { text = "MIT" }
16
+ license = "MIT"
17
+ license-files = ["LICENSE"]
17
18
  keywords = [
18
19
  "asyncio",
19
20
  "collections",
@@ -30,7 +31,6 @@ keywords = [
30
31
  classifiers = [
31
32
  "Development Status :: 5 - Production/Stable",
32
33
  "Intended Audience :: Developers",
33
- "License :: OSI Approved :: MIT License",
34
34
  "Programming Language :: Python :: 3",
35
35
  "Programming Language :: Python :: 3 :: Only",
36
36
  "Topic :: Software Development :: Libraries",
@@ -46,45 +46,47 @@ email = "floss@frequenz.com"
46
46
 
47
47
  [project.optional-dependencies]
48
48
  dev-flake8 = [
49
- "flake8 == 7.3.0",
49
+ "flake8 == 7.4.1",
50
+ "flake8-datetimez == 26.8.1",
50
51
  "flake8-docstrings == 1.7.0",
51
- "flake8-pyproject == 1.2.3", # For reading the flake8 config from pyproject.toml
52
- "pydoclint == 0.6.11",
52
+ "flake8-pyproject == 1.2.4", # For reading the flake8 config from pyproject.toml
53
+ "pydoclint == 0.10.0",
53
54
  "pydocstyle == 6.3.0",
54
55
  ]
55
- dev-formatting = ["black == 25.1.0", "isort == 6.0.1"]
56
+ dev-formatting = ["black == 26.5.1", "isort == 9.0.1"]
56
57
  dev-mkdocs = [
57
- "Markdown == 3.8.2",
58
- "black == 25.1.0",
59
- "mike == 2.1.3",
60
- "mkdocs-gen-files == 0.5.0",
61
- "mkdocs-literate-nav == 0.6.2",
62
- "mkdocs-macros-plugin == 1.3.9",
63
- "mkdocs-material == 9.6.18",
64
- "mkdocstrings[python] == 0.30.0",
65
- "mkdocstrings-python == 1.18.2",
66
- "frequenz-repo-config[lib] == 0.13.5",
58
+ "Markdown == 3.11",
59
+ "black == 26.5.1",
60
+ "griffe-warnings-deprecated == 1.1.1",
61
+ "mike == 2.2.0",
62
+ "mkdocs-gen-files == 0.6.1",
63
+ "mkdocs-literate-nav == 0.6.3",
64
+ "mkdocs-macros-plugin == 1.5.0",
65
+ "mkdocs-material == 9.7.7",
66
+ "mkdocstrings[python] == 1.0.6",
67
+ "mkdocstrings-python == 2.0.7",
68
+ "frequenz-repo-config[lib] == 0.19.0",
67
69
  ]
68
70
  dev-mypy = [
69
- "mypy == 1.17.1",
70
- "types-Markdown == 3.8.0.20250809",
71
+ "mypy == 2.3.1",
72
+ "types-Markdown == 3.10.2.20260712",
71
73
  # For checking the noxfile, docs/ script, and tests
72
74
  "frequenz-core[dev-mkdocs,dev-noxfile,dev-pytest]",
73
75
  ]
74
- dev-noxfile = ["nox == 2025.5.1", "frequenz-repo-config[lib] == 0.13.5"]
76
+ dev-noxfile = ["nox == 2026.8.17", "frequenz-repo-config[lib] == 0.19.0"]
75
77
  dev-pylint = [
76
78
  # dev-pytest already defines a dependency to pylint because of the examples
77
79
  # For checking the noxfile, docs/ script, and tests
78
80
  "frequenz-core[dev-mkdocs,dev-noxfile,dev-pytest]",
79
81
  ]
80
82
  dev-pytest = [
81
- "pytest == 8.4.1",
82
- "pylint == 3.3.8", # We need this to check for the examples
83
- "frequenz-repo-config[extra-lint-examples] == 0.13.5",
84
- "pytest-mock == 3.14.1",
85
- "pytest-asyncio == 1.1.0",
86
- "async-solipsism == 0.8",
87
- "hypothesis == 6.138.11",
83
+ "pytest == 9.1.1",
84
+ "pylint == 4.0.8", # We need this to check for the examples
85
+ "frequenz-repo-config[extra-lint-examples] == 0.19.0",
86
+ "pytest-mock == 3.16.0",
87
+ "pytest-asyncio == 1.4.0",
88
+ "async-solipsism == 0.9",
89
+ "hypothesis == 6.168.2",
88
90
  ]
89
91
  dev = [
90
92
  "frequenz-core[dev-mkdocs,dev-flake8,dev-formatting,dev-mkdocs,dev-mypy,dev-noxfile,dev-pylint,dev-pytest]",
@@ -123,6 +125,10 @@ check-yield-types = false
123
125
  arg-type-hints-in-docstring = false
124
126
  arg-type-hints-in-signature = true
125
127
  allow-init-docstring = true
128
+ check-class-attributes = true
129
+ check-style-mismatch = true
130
+ require-inline-class-var-docs = true
131
+ skip-checking-short-docstrings = true
126
132
 
127
133
  [tool.pylint.similarities]
128
134
  ignore-comments = ['yes']
@@ -155,13 +161,25 @@ disable = [
155
161
  ]
156
162
 
157
163
  [tool.pytest.ini_options]
158
- addopts = "-W=all -Werror -Wdefault::DeprecationWarning -Wdefault::PendingDeprecationWarning -vv"
164
+ filterwarnings = [
165
+ "error",
166
+ "once::DeprecationWarning",
167
+ "once::PendingDeprecationWarning",
168
+ # We ignore warnings about protobuf gencode version being one version older
169
+ # than the current version, as this is supported by protobuf, and we expect to
170
+ # have such cases. If we go too far, we will get a proper error anyways.
171
+ # We use a raw string (single quotes) to avoid the need to escape special
172
+ # characters as this is a regex.
173
+ 'ignore:Protobuf gencode version .*exactly one major version older.*:UserWarning',
174
+ ]
159
175
  testpaths = ["tests", "src"]
176
+ asyncio_debug = true
160
177
  asyncio_mode = "auto"
161
178
  asyncio_default_fixture_loop_scope = "function"
162
179
  required_plugins = ["pytest-asyncio", "pytest-mock"]
163
180
 
164
181
  [tool.mypy]
182
+ enable_error_code = ["exhaustive-match"]
165
183
  explicit_package_bases = true
166
184
  namespace_packages = true
167
185
  # This option disables mypy cache, and it is sometimes useful to enable it if
@@ -76,11 +76,13 @@ class DeprecatingEnumType(enum.EnumType):
76
76
 
77
77
  Tip:
78
78
  Normally it is not necessary to use this class directly, use
79
- [`Enum`][frequenz.core.enum.Enum] instead.
79
+ [`Enum`][..Enum] instead.
80
80
 
81
81
  Behavior:
82
82
 
83
83
  - In the class body, members may be declared as `NAME = DeprecatedMember(value, msg)`.
84
+ See [`DeprecatedMember`][..DeprecatedMember] and
85
+ [`deprecated_member()`][..deprecated_member] for details.
84
86
  - During class creation, these wrappers are replaced with `value` so that
85
87
  a normal enum member or alias is created by [`EnumType`][enum.EnumType].
86
88
  - The deprecated names are recorded so that:
@@ -98,7 +100,7 @@ class DeprecatingEnumType(enum.EnumType):
98
100
  classdict: Mapping[str, Any],
99
101
  **kw: Any,
100
102
  ) -> type[EnumT]:
101
- """Create the new enum class, rewriting `DeprecatedMember` instances."""
103
+ """Create the new enum class rewriting [`DeprecatedMember`][...DeprecatedMember] members."""
102
104
  deprecated_names: dict[str, str] = {}
103
105
  prepared = super().__prepare__(name, bases, **kw)
104
106
 
@@ -20,7 +20,7 @@ the ID and must be unique across all ID types.
20
20
 
21
21
  Note:
22
22
  The `str_prefix` must be unique across all ID types. If you try to use a
23
- prefix that is already registered, a `ValueError` will be raised when defining
23
+ prefix that is already registered, a warning will be logged when defining
24
24
  the class.
25
25
 
26
26
  To encourage consistency, the class name must end with the suffix "Id" (e.g.,
@@ -61,7 +61,6 @@ Example: Creating an ID type with a non-standard name
61
61
  ```
62
62
  '''
63
63
 
64
-
65
64
  import logging
66
65
  from typing import Any, ClassVar, Self, cast
67
66
 
@@ -157,6 +156,13 @@ class BaseId:
157
156
 
158
157
  Equality is defined as being of the exact same type and having the same
159
158
  underlying ID.
159
+
160
+ Args:
161
+ other: The object to compare against.
162
+
163
+ Returns:
164
+ True if `other` is of the same type and has the same underlying ID,
165
+ `NotImplemented` if `other` is of a different type.
160
166
  """
161
167
  # pylint thinks this is not an unidiomatic typecheck, but in this case
162
168
  # it is not. isinstance() returns True for subclasses, which is not
@@ -173,6 +179,13 @@ class BaseId:
173
179
  """Check if this instance is less than another object.
174
180
 
175
181
  Comparison is only defined between instances of the exact same type.
182
+
183
+ Args:
184
+ other: The object to compare against.
185
+
186
+ Returns:
187
+ True if this instance is less than `other`, `NotImplemented` if
188
+ `other` is of a different type.
176
189
  """
177
190
  # pylint: disable-next=unidiomatic-typecheck
178
191
  if type(other) is not type(self):
@@ -185,6 +198,9 @@ class BaseId:
185
198
 
186
199
  The hash is based on the exact type and the underlying ID to ensure
187
200
  that IDs of different types but with the same numeric value have different hashes.
201
+
202
+ Returns:
203
+ The hash of this instance.
188
204
  """
189
205
  return hash((type(self), self._id))
190
206