From 0181988935c9f1e1ab7dcfcbc56159669f6a79f6 Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Mon, 21 Sep 2026 12:41:34 +0000 Subject: [PATCH 1/4] docs: Add styling for "Deprecated" admonitions Style the `deprecated` admonition class like a warning, but with the `material/grave-stone` icon and its own colour, so deprecation notices read as deprecation notices and not as generic warnings. That class is what a hand-written `Deprecated:` admonition in a docstring produces, and also what the griffe extension added next will emit, so a single rule covers both sources. Signed-off-by: Leandro Lucarella --- docs/_css/mkdocstrings.css | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/docs/_css/mkdocstrings.css b/docs/_css/mkdocstrings.css index 572abff1..851214f2 100644 --- a/docs/_css/mkdocstrings.css +++ b/docs/_css/mkdocstrings.css @@ -42,3 +42,25 @@ a.autorefs-external::after { a.autorefs-external:hover::after { background-color: var(--md-accent-fg-color); } + +/* A "Deprecated" admonition, styled like a warning but with its own icon. */ +:root { + --md-admonition-icon--deprecated: url('data:image/svg+xml;charset=utf-8,'); +} + +.md-typeset .admonition.deprecated, +.md-typeset details.deprecated { + border-color: #cc9900; +} + +.md-typeset .deprecated > .admonition-title, +.md-typeset .deprecated > summary { + background-color: #cc99001a; +} + +.md-typeset .deprecated > .admonition-title::before, +.md-typeset .deprecated > summary::before { + background-color: #cc9900; + -webkit-mask-image: var(--md-admonition-icon--deprecated); + mask-image: var(--md-admonition-icon--deprecated); +} From c98e3f8609d9499d060562f800c4de6b40db4651 Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Mon, 21 Sep 2026 12:41:34 +0000 Subject: [PATCH 2/4] docs: Generate "Deprecated" admonitions automatically Wire the `griffe-warnings-deprecated` extension into the mkdocstrings handler options, so every symbol decorated with `typing_extensions.deprecated` gets a "Deprecated" admonition in the API reference with no docstring edit at all. The extension's `kind` is set to `deprecated` rather than the default `warning`, so it emits `class="deprecated"`, which is exactly what a hand-written `Deprecated:` admonition produces. The CSS rule added in the previous commit then styles both, and the two are visually indistinguishable. That matters because the decorator cannot reach everything: module-level aliases, a single function argument, enum members and whole modules still need the admonition written by hand. Signed-off-by: Leandro Lucarella --- mkdocs.yml | 4 ++++ pyproject.toml | 1 + 2 files changed, 5 insertions(+) diff --git a/mkdocs.yml b/mkdocs.yml index 73a4c8d5..12677025 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -105,6 +105,10 @@ plugins: python: paths: ["src"] options: + extensions: + - griffe_warnings_deprecated: + kind: deprecated + title: Deprecated docstring_section_style: spacy inherited_members: true merge_init_into_class: false diff --git a/pyproject.toml b/pyproject.toml index a8006bff..acff00f8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,6 +50,7 @@ dev-formatting = ["black == 26.5.1", "isort == 9.0.1"] dev-mkdocs = [ "Markdown == 3.10.3", "black == 26.5.1", + "griffe-warnings-deprecated == 1.1.1", "mike == 2.2.0", "mkdocs-gen-files == 0.6.1", "mkdocs-literate-nav == 0.6.3", From e57a2f16a125cc6f8fc4c905227a4c907d25f8bb Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Mon, 21 Sep 2026 16:53:05 +0000 Subject: [PATCH 3/4] docs: Make every deprecation notice follow the deprecations guide Signed-off-by: Leandro Lucarella --- .../client/common/grid/_delivery_area.py | 32 +-- .../grid/proto/v1alpha8/_delivery_area.py | 17 +- src/frequenz/client/common/metrics/_metric.py | 12 +- src/frequenz/client/common/metrics/_sample.py | 46 +++-- .../common/metrics/proto/v1alpha8/_bounds.py | 29 ++- .../common/metrics/proto/v1alpha8/_sample.py | 34 ++-- .../common/microgrid/components/__init__.py | 6 +- .../electrical_components/_category.py | 186 +++++++++++++++--- .../electrical_components/_diagnostic_code.py | 13 +- .../electrical_components/_state_code.py | 13 +- .../proto/v1alpha8/_category.py | 12 +- .../proto/v1alpha8/_pagination_info.py | 13 +- src/frequenz/client/common/proto/_datetime.py | 18 +- .../client/common/streaming/_event.py | 12 +- tests/metrics/test_sample_metric_sample.py | 7 +- .../proto/v1alpha8/test_pagination_info.py | 6 +- tests/proto/test_datetime.py | 5 +- 17 files changed, 320 insertions(+), 141 deletions(-) diff --git a/src/frequenz/client/common/grid/_delivery_area.py b/src/frequenz/client/common/grid/_delivery_area.py index c51ba40e..b554a36d 100644 --- a/src/frequenz/client/common/grid/_delivery_area.py +++ b/src/frequenz/client/common/grid/_delivery_area.py @@ -46,10 +46,16 @@ class EnergyMarketCodeType(Enum): UNSPECIFIED = deprecated_member( 0, - "EnergyMarketCodeType.UNSPECIFIED is deprecated; use the `int` value `0` " - "instead if you really need to check for this low-level value.", + "frequenz.client.common.grid.EnergyMarketCodeType.UNSPECIFIED is " + "deprecated since v0.4.1. Use the int value 0 instead if you really " + "need to check for this low-level value.", ) - """Unspecified type. This value is a placeholder and should not be used.""" + """Unspecified type. This value is a placeholder and should not be used. + + Deprecated: + This member is deprecated since v0.4.1. Use the `int` value `0` instead + if you really need to check for this low-level value. + """ EUROPE_EIC = 1 """European Energy Identification Code Standard.""" @@ -72,11 +78,11 @@ class BaseDeliveryArea: code: str | None """The code representing the unique identifier for the delivery area. - Warning: Using `None` is deprecated - This field is required for a well-formed `DeliveryArea`, so we are - making this more explicit by deprecating the use of `None` here. In the - future, `| None` will be removed so passing `None` will fail type - checking. + Deprecated: + Passing `None` is deprecated since v0.4.1. This field is required for a + well-formed `DeliveryArea`, so we are making this more explicit by + deprecating the use of `None` here. In the future, `| None` will be + removed so passing `None` will fail type checking. """ code_type: EnergyMarketCodeType | int @@ -122,11 +128,11 @@ class DeliveryArea(BaseDeliveryArea): location. Delivery areas can have different codes based on the jurisdiction in which they operate. - Warning: Construction of invalid instances is deprecated - A well-formed `DeliveryArea` carries a non-empty [`code`][.code] and a - specified [`code_type`][.code_type]. Constructing one with data that - violates this invariant is **deprecated**, and will raise a - [`ValueError`][] in a future release. + Deprecated: + Constructing a `DeliveryArea` with invalid data is deprecated since + v0.4.1, and will raise a [`ValueError`][] in a future release. The type + itself is not deprecated. A well-formed `DeliveryArea` carries a + non-empty [`code`][.code] and a specified [`code_type`][.code_type]. You can temporarily use the `_raise_on_invalid` keyword argument to get the upcoming behavior now (raising instead of deprecation warning). diff --git a/src/frequenz/client/common/grid/proto/v1alpha8/_delivery_area.py b/src/frequenz/client/common/grid/proto/v1alpha8/_delivery_area.py index 131e591b..ac23b722 100644 --- a/src/frequenz/client/common/grid/proto/v1alpha8/_delivery_area.py +++ b/src/frequenz/client/common/grid/proto/v1alpha8/_delivery_area.py @@ -45,21 +45,20 @@ def energy_market_code_type_to_proto( @deprecated( - "`delivery_area_from_proto` is deprecated; use " - "`delivery_area_from_proto2` (returns " - "`DeliveryArea | InvalidDeliveryArea`) instead." + "frequenz.client.common.grid.proto.v1alpha8.delivery_area_from_proto is " + "deprecated since v0.4.1. Use " + "[frequenz.client.common.grid.proto.v1alpha8.delivery_area_from_proto2][] " + "instead." ) def delivery_area_from_proto( # noqa: DOC502 message: delivery_area_pb2.DeliveryArea, ) -> DeliveryArea: """Convert a protobuf message to a [`DeliveryArea`][....DeliveryArea] object. - Warning: Deprecated - Use [`delivery_area_from_proto2`][..delivery_area_from_proto2] - instead. The new converter distinguishes well-formed from - malformed data at the type level - (`DeliveryArea | InvalidDeliveryArea`) rather than silently - constructing a `DeliveryArea` with invalid content. + [`delivery_area_from_proto2`][..delivery_area_from_proto2] distinguishes + well-formed from malformed data at the type level + (`DeliveryArea | InvalidDeliveryArea`) rather than silently constructing a + `DeliveryArea` with invalid content. Args: message: The protobuf message to convert. diff --git a/src/frequenz/client/common/metrics/_metric.py b/src/frequenz/client/common/metrics/_metric.py index 587684cd..8c1a194d 100644 --- a/src/frequenz/client/common/metrics/_metric.py +++ b/src/frequenz/client/common/metrics/_metric.py @@ -41,10 +41,16 @@ class Metric(Enum): UNSPECIFIED = deprecated_member( 0, - "Metric.UNSPECIFIED is deprecated; use the `int` value `0` " - "instead if you really need to check for this low-level value.", + "frequenz.client.common.metrics.Metric.UNSPECIFIED is deprecated since " + "v0.4.1. Use the int value 0 instead if you really need to check for " + "this low-level value.", ) - """The metric is unspecified (this should not be used).""" + """The metric is unspecified (this should not be used). + + Deprecated: + This member is deprecated since v0.4.1. Use the `int` value `0` instead + if you really need to check for this low-level value. + """ DC_VOLTAGE = 1 """The DC voltage.""" diff --git a/src/frequenz/client/common/metrics/_sample.py b/src/frequenz/client/common/metrics/_sample.py index ea80c836..98b0ccee 100644 --- a/src/frequenz/client/common/metrics/_sample.py +++ b/src/frequenz/client/common/metrics/_sample.py @@ -78,10 +78,16 @@ class MetricConnectionCategory(Enum): UNSPECIFIED = deprecated_member( 0, - "MetricConnectionCategory.UNSPECIFIED is deprecated; use the `int` value `0` " - "instead if you really need to check for this low-level value.", + "frequenz.client.common.metrics.MetricConnectionCategory.UNSPECIFIED " + "is deprecated since v0.4.1. Use the int value 0 instead if you really " + "need to check for this low-level value.", ) - """The connection category was not specified (do not use).""" + """The connection category was not specified (do not use). + + Deprecated: + This member is deprecated since v0.4.1. Use the `int` value `0` instead + if you really need to check for this low-level value. + """ OTHER = 1 """A generic connection for metrics that do not fit into any other category.""" @@ -381,16 +387,16 @@ def __str__(self) -> str: return sample @property - @deprecated("`MetricSample.sample_time` is deprecated; use `sample_time2` instead.") + @deprecated( + "frequenz.client.common.metrics.MetricSample.sample_time is deprecated " + "since v0.4.1. Use " + "[frequenz.client.common.metrics.MetricSample.get_sample_time][] instead." + ) def sample_time(self) -> datetime: # noqa: DOC502 """The moment when the metric was sampled. - Warning: Deprecated - Use [`sample_time2`][..sample_time2] instead, or - [`get_sample_time()`][..get_sample_time] when a valid - [`datetime`][datetime.datetime] is required. This property keeps - the released `datetime` type, so it cannot express a malformed wire - timestamp and raises for one instead. + This property keeps the released `datetime` type, so it cannot express + a malformed wire timestamp and raises for one instead. Returns: The sample time, when it is a valid @@ -404,16 +410,22 @@ def sample_time(self) -> datetime: # noqa: DOC502 return self.get_sample_time() @property - @deprecated("`MetricSample.bounds` is deprecated; use `bounds_set` instead.") + @deprecated( + "frequenz.client.common.metrics.MetricSample.bounds is deprecated " + "since v0.4.1. Use " + "[frequenz.client.common.metrics.MetricSample.bounds_set][] instead." + ) def bounds(self) -> list[Bounds]: """The valid bounds that apply to the metric sample. - Warning: Deprecated - Use `bounds_set` instead. For backward compatibility this returns - only the valid [`Bounds`][...Bounds] from `bounds_set` (dropping any - malformed entries, as the old field did), but it returns the - normalized, merged bounds rather than the raw list received on the - wire. + Deprecated: + This property is deprecated since v0.4.1. Use + [`bounds_set`][..bounds_set] instead. + + For backward compatibility this returns only the valid + [`Bounds`][...Bounds] from `bounds_set` (dropping any malformed entries, + as the old field did), but it returns the normalized, merged bounds + rather than the raw list received on the wire. Returns: The valid bounds in `bounds_set`. diff --git a/src/frequenz/client/common/metrics/proto/v1alpha8/_bounds.py b/src/frequenz/client/common/metrics/proto/v1alpha8/_bounds.py index 33368eb4..2181e782 100644 --- a/src/frequenz/client/common/metrics/proto/v1alpha8/_bounds.py +++ b/src/frequenz/client/common/metrics/proto/v1alpha8/_bounds.py @@ -13,17 +13,17 @@ @deprecated( - "`bounds_from_proto` is deprecated; use " - "`bounds_from_proto2` (returns `Bounds | InvalidBounds`) instead." + "frequenz.client.common.metrics.proto.v1alpha8.bounds_from_proto is " + "deprecated since v0.4.1. Use " + "[frequenz.client.common.metrics.proto.v1alpha8.bounds_from_proto2][] " + "instead." ) def bounds_from_proto(message: bounds_pb2.Bounds) -> Bounds: # noqa: DOC502 """Create a [`Bounds`][....Bounds] object from a protobuf message. - Warning: Deprecated - Use [`bounds_from_proto2`][..bounds_from_proto2] instead. The new - converter distinguishes well-formed from malformed data at the - type level (`Bounds | InvalidBounds`) rather than raising a - `ValueError` when the invariant fires. + [`bounds_from_proto2`][..bounds_from_proto2] distinguishes well-formed from + malformed data at the type level (`Bounds | InvalidBounds`) rather than + raising a `ValueError` when the invariant fires. Args: message: The protobuf message to convert. @@ -101,9 +101,9 @@ def bounds_set_from_proto( @deprecated( - "`bounds_from_proto_with_issues` is deprecated; use " - "`bounds_from_proto2` (returns `Bounds | InvalidBounds`) and inspect " - "the returned type instead." + "frequenz.client.common.metrics.proto.v1alpha8.bounds_from_proto_with_issues " + "is deprecated since v0.4.1. Use " + "[frequenz.client.common.metrics.proto.v1alpha8.bounds_from_proto2][] instead." ) def bounds_from_proto_with_issues( message: bounds_pb2.Bounds, @@ -113,12 +113,9 @@ def bounds_from_proto_with_issues( ) -> Bounds | None: # noqa: DOC502 """Create a [`Bounds`][....Bounds] object from a protobuf message, collecting issues. - Warning: Deprecated - Use [`bounds_from_proto2`][..bounds_from_proto2] instead and - inspect the returned type. The new converter distinguishes - well-formed from malformed data at the type level - (`Bounds | InvalidBounds`) rather than routing invalid data - through a side-channel string list. + [`bounds_from_proto2`][..bounds_from_proto2] distinguishes well-formed from + malformed data at the type level (`Bounds | InvalidBounds`) rather than + routing invalid data through a side-channel string list. Args: message: The protobuf message to convert. diff --git a/src/frequenz/client/common/metrics/proto/v1alpha8/_sample.py b/src/frequenz/client/common/metrics/proto/v1alpha8/_sample.py index c2abadad..02e27a51 100644 --- a/src/frequenz/client/common/metrics/proto/v1alpha8/_sample.py +++ b/src/frequenz/client/common/metrics/proto/v1alpha8/_sample.py @@ -122,8 +122,10 @@ def metric_sample_from_proto( @deprecated( - "`metric_connection_from_proto_with_issues` is deprecated; use " - "`metric_connection_from_proto` and inspect the returned type instead." + "frequenz.client.common.metrics.proto.v1alpha8." + "metric_connection_from_proto_with_issues is deprecated since v0.4.1. Use " + "[frequenz.client.common.metrics.proto.v1alpha8.metric_connection_from_proto][] " + "instead." ) def metric_connection_from_proto_with_issues( message: metrics_pb2.MetricConnection, @@ -133,12 +135,10 @@ def metric_connection_from_proto_with_issues( ) -> MetricConnection: """Convert a protobuf message to a [`MetricConnection`][....MetricConnection] object. - Warning: Deprecated - Use [`metric_connection_from_proto`][..metric_connection_from_proto] - instead and inspect the returned type. The new converter encodes an - unspecified or unrecognized category in the returned - `MetricConnection.category` field (`MetricConnectionCategory | int`) - rather than routing it through a side-channel string list. + [`metric_connection_from_proto`][..metric_connection_from_proto] encodes an + unspecified or unrecognized category in the returned + `MetricConnection.category` field (`MetricConnectionCategory | int`) rather + than routing it through a side-channel string list. Args: message: The protobuf message to convert. @@ -165,8 +165,10 @@ def metric_connection_from_proto_with_issues( @deprecated( - "`metric_sample_from_proto_with_issues` is deprecated; use " - "`metric_sample_from_proto` and inspect the returned type instead." + "frequenz.client.common.metrics.proto.v1alpha8." + "metric_sample_from_proto_with_issues is deprecated since v0.4.1. Use " + "[frequenz.client.common.metrics.proto.v1alpha8.metric_sample_from_proto][] " + "and inspect the returned type instead." ) def metric_sample_from_proto_with_issues( message: metrics_pb2.MetricSample, @@ -176,13 +178,11 @@ def metric_sample_from_proto_with_issues( ) -> MetricSample: """Convert a protobuf message to a [`MetricSample`][....MetricSample] object. - Warning: Deprecated - Use [`metric_sample_from_proto`][..metric_sample_from_proto] instead - and inspect the returned type. The new converter encodes an - unspecified or unrecognized `metric` (`Metric | int`), malformed - bounds (`InvalidBoundsSet`) and an unrepresentable sample time - (`InvalidDatetime`) in the returned `MetricSample` rather than - routing them through a side-channel string list. + [`metric_sample_from_proto`][..metric_sample_from_proto] encodes an + unspecified or unrecognized `metric` (`Metric | int`), malformed bounds + (`InvalidBoundsSet`) and an unrepresentable sample time (`InvalidDatetime`) + in the returned `MetricSample` rather than routing them through a + side-channel string list. Note: A malformed `sample_time` still raises `ValueError`, as it did when the diff --git a/src/frequenz/client/common/microgrid/components/__init__.py b/src/frequenz/client/common/microgrid/components/__init__.py index 5c1037c3..88bdd4c1 100644 --- a/src/frequenz/client/common/microgrid/components/__init__.py +++ b/src/frequenz/client/common/microgrid/components/__init__.py @@ -10,9 +10,9 @@ @deprecated( - "frequenz.client.common.microgrid.components.ComponentId is deprecated. " - "Use frequenz.client.common.microgrid.electrical_components." - "ElectricalComponentId instead." + "frequenz.client.common.microgrid.components.ComponentId is deprecated " + "since v0.4.1. Use [frequenz.client.common.microgrid." + "electrical_components.ElectricalComponentId][] instead." ) @final class ComponentId(BaseId, str_prefix="CID"): diff --git a/src/frequenz/client/common/microgrid/electrical_components/_category.py b/src/frequenz/client/common/microgrid/electrical_components/_category.py index 1843f99d..9498abd6 100644 --- a/src/frequenz/client/common/microgrid/electrical_components/_category.py +++ b/src/frequenz/client/common/microgrid/electrical_components/_category.py @@ -6,10 +6,12 @@ import typing_extensions from frequenz.core.enum import Enum, deprecated_member, unique -_DEPRECATION_MESSAGE = ( - "ElectricalComponentCategory is deprecated; use the ElectricalComponent class " - "hierarchy (isinstance) or electrical_component_class_to_proto()/" - "electrical_component_class_from_proto()." +_QUALNAME = "frequenz.client.common.microgrid.electrical_components" + +_REPLACEMENT = ( + f"Use the [{_QUALNAME}.ElectricalComponent][] class hierarchy with isinstance(), " + f"or [{_QUALNAME}.proto.v1alpha8.electrical_component_class_to_proto][] and " + f"[{_QUALNAME}.proto.v1alpha8.electrical_component_class_from_proto][], instead." ) @@ -23,82 +25,212 @@ def _member_message(name: str) -> str: The full deprecation message for that member. """ return ( - f"ElectricalComponentCategory.{name} is deprecated; use the " - "ElectricalComponent class hierarchy (isinstance) or " - "electrical_component_class_to_proto()/electrical_component_class_from_proto()." + f"{_QUALNAME}.ElectricalComponentCategory.{name} is deprecated since " + f"v0.4.1. {_REPLACEMENT}" ) -@typing_extensions.deprecated(_DEPRECATION_MESSAGE) +# The message is spelled out as a literal rather than built from the constants +# above because griffe-warnings-deprecated only renders the admonition when it +# can read the message statically; a module-level name renders nothing. +@typing_extensions.deprecated( + "frequenz.client.common.microgrid.electrical_components." + "ElectricalComponentCategory is deprecated since v0.4.1. Use the " + "[frequenz.client.common.microgrid.electrical_components." + "ElectricalComponent][] class hierarchy with isinstance(), or " + "[frequenz.client.common.microgrid.electrical_components.proto.v1alpha8." + "electrical_component_class_to_proto][] and " + "[frequenz.client.common.microgrid.electrical_components.proto.v1alpha8." + "electrical_component_class_from_proto][], instead." +) @unique class ElectricalComponentCategory(Enum): """Possible types of microgrid electrical component.""" UNSPECIFIED = deprecated_member(0, _member_message("UNSPECIFIED")) - """The component category is unspecified. This should not be used.""" + """The component category is unspecified. This should not be used. + + Deprecated: + This member is deprecated since v0.4.1. See + [`ElectricalComponentCategory`][...ElectricalComponentCategory] for + what to use instead. + """ GRID_CONNECTION_POINT = deprecated_member( 1, _member_message("GRID_CONNECTION_POINT") ) - """The point where the local microgrid is connected to the grid.""" + """The point where the local microgrid is connected to the grid. + + Deprecated: + This member is deprecated since v0.4.1. See + [`ElectricalComponentCategory`][...ElectricalComponentCategory] for + what to use instead. + """ METER = deprecated_member(2, _member_message("METER")) - """A meter, for measuring electrical metrics, e.g., current, voltage, etc.""" + """A meter, for measuring electrical metrics, e.g., current, voltage, etc. + + Deprecated: + This member is deprecated since v0.4.1. See + [`ElectricalComponentCategory`][...ElectricalComponentCategory] for + what to use instead. + """ INVERTER = deprecated_member(3, _member_message("INVERTER")) - """An inverter that converts DC to AC power and vice versa.""" + """An inverter that converts DC to AC power and vice versa. + + Deprecated: + This member is deprecated since v0.4.1. See + [`ElectricalComponentCategory`][...ElectricalComponentCategory] for + what to use instead. + """ CONVERTER = deprecated_member(4, _member_message("CONVERTER")) - """An electricity converter, e.g., a DC-DC converter.""" + """An electricity converter, e.g., a DC-DC converter. + + Deprecated: + This member is deprecated since v0.4.1. See + [`ElectricalComponentCategory`][...ElectricalComponentCategory] for + what to use instead. + """ BATTERY = deprecated_member(5, _member_message("BATTERY")) - """A battery energy storage system.""" + """A battery energy storage system. + + Deprecated: + This member is deprecated since v0.4.1. See + [`ElectricalComponentCategory`][...ElectricalComponentCategory] for + what to use instead. + """ EV_CHARGER = deprecated_member(6, _member_message("EV_CHARGER")) - """A station for charging electrical vehicles.""" + """A station for charging electrical vehicles. + + Deprecated: + This member is deprecated since v0.4.1. See + [`ElectricalComponentCategory`][...ElectricalComponentCategory] for + what to use instead. + """ BREAKER = deprecated_member(7, _member_message("BREAKER")) - """A circuit breaker, providing protection and switching by disconnecting circuits.""" + """A circuit breaker, providing protection and switching by disconnecting circuits. + + Deprecated: + This member is deprecated since v0.4.1. See + [`ElectricalComponentCategory`][...ElectricalComponentCategory] for + what to use instead. + """ PRECHARGER = deprecated_member(8, _member_message("PRECHARGER")) - """A precharger, used for preparing electrical circuits for switching on.""" + """A precharger, used for preparing electrical circuits for switching on. + + Deprecated: + This member is deprecated since v0.4.1. See + [`ElectricalComponentCategory`][...ElectricalComponentCategory] for + what to use instead. + """ CHP = deprecated_member(9, _member_message("CHP")) """A combined heat and power (CHP) plant. + Deprecated: + This member is deprecated since v0.4.1. See + [`ElectricalComponentCategory`][...ElectricalComponentCategory] for + what to use instead. + It generates electricity and useful heat from a single energy source. """ ELECTROLYZER = deprecated_member(10, _member_message("ELECTROLYZER")) - """A device for splitting water into hydrogen and oxygen using electricity.""" + """A device for splitting water into hydrogen and oxygen using electricity. + + Deprecated: + This member is deprecated since v0.4.1. See + [`ElectricalComponentCategory`][...ElectricalComponentCategory] for + what to use instead. + """ POWER_TRANSFORMER = deprecated_member(11, _member_message("POWER_TRANSFORMER")) - """A transformer, used for changing the voltage of electrical circuits.""" + """A transformer, used for changing the voltage of electrical circuits. + + Deprecated: + This member is deprecated since v0.4.1. See + [`ElectricalComponentCategory`][...ElectricalComponentCategory] for + what to use instead. + """ HVAC = deprecated_member(12, _member_message("HVAC")) - """A heating, ventilation, and air conditioning (HVAC) system.""" + """A heating, ventilation, and air conditioning (HVAC) system. + + Deprecated: + This member is deprecated since v0.4.1. See + [`ElectricalComponentCategory`][...ElectricalComponentCategory] for + what to use instead. + """ PLC = deprecated_member(13, _member_message("PLC")) - """A programmable logic controller (PLC).""" + """A programmable logic controller (PLC). + + Deprecated: + This member is deprecated since v0.4.1. See + [`ElectricalComponentCategory`][...ElectricalComponentCategory] for + what to use instead. + """ CRYPTO_MINER = deprecated_member(14, _member_message("CRYPTO_MINER")) - """A device for mining cryptocurrencies.""" + """A device for mining cryptocurrencies. + + Deprecated: + This member is deprecated since v0.4.1. See + [`ElectricalComponentCategory`][...ElectricalComponentCategory] for + what to use instead. + """ STATIC_TRANSFER_SWITCH = deprecated_member( 15, _member_message("STATIC_TRANSFER_SWITCH") ) - """A static transfer switch, used for switching between power sources.""" + """A static transfer switch, used for switching between power sources. + + Deprecated: + This member is deprecated since v0.4.1. See + [`ElectricalComponentCategory`][...ElectricalComponentCategory] for + what to use instead. + """ UNINTERRUPTIBLE_POWER_SUPPLY = deprecated_member( 16, _member_message("UNINTERRUPTIBLE_POWER_SUPPLY") ) - """An uninterruptible power supply (UPS), used to provide backup power.""" + """An uninterruptible power supply (UPS), used to provide backup power. + + Deprecated: + This member is deprecated since v0.4.1. See + [`ElectricalComponentCategory`][...ElectricalComponentCategory] for + what to use instead. + """ CAPACITOR_BANK = deprecated_member(17, _member_message("CAPACITOR_BANK")) - """A capacitor bank, used for power factor correction and reactive power compensation.""" + """A capacitor bank, used for power factor correction and reactive power compensation. + + Deprecated: + This member is deprecated since v0.4.1. See + [`ElectricalComponentCategory`][...ElectricalComponentCategory] for + what to use instead. + """ WIND_TURBINE = deprecated_member(18, _member_message("WIND_TURBINE")) - """A wind turbine, used to generate electricity from wind energy.""" + """A wind turbine, used to generate electricity from wind energy. + + Deprecated: + This member is deprecated since v0.4.1. See + [`ElectricalComponentCategory`][...ElectricalComponentCategory] for + what to use instead. + """ STEAM_BOILER = deprecated_member(19, _member_message("STEAM_BOILER")) - """A steam boiler, used to generate steam for heating or industrial processes.""" + """A steam boiler, used to generate steam for heating or industrial processes. + + Deprecated: + This member is deprecated since v0.4.1. See + [`ElectricalComponentCategory`][...ElectricalComponentCategory] for + what to use instead. + """ diff --git a/src/frequenz/client/common/microgrid/electrical_components/_diagnostic_code.py b/src/frequenz/client/common/microgrid/electrical_components/_diagnostic_code.py index 092da57f..f12fd4f3 100644 --- a/src/frequenz/client/common/microgrid/electrical_components/_diagnostic_code.py +++ b/src/frequenz/client/common/microgrid/electrical_components/_diagnostic_code.py @@ -12,10 +12,17 @@ class ElectricalComponentDiagnosticCode(Enum): UNSPECIFIED = deprecated_member( 0, - "ElectricalComponentDiagnosticCode.UNSPECIFIED is deprecated; use the `int` value `0` " - "instead if you really need to check for this low-level value.", + "frequenz.client.common.microgrid.electrical_components." + "ElectricalComponentDiagnosticCode.UNSPECIFIED is deprecated since " + "v0.4.1. Use the int value 0 instead if you really need to check for " + "this low-level value.", ) - """Default value. No specific error is specified.""" + """Default value. No specific error is specified. + + Deprecated: + This member is deprecated since v0.4.1. Use the `int` value `0` instead + if you really need to check for this low-level value. + """ UNKNOWN = 1 """The component is reporting an unknown or an undefined error. diff --git a/src/frequenz/client/common/microgrid/electrical_components/_state_code.py b/src/frequenz/client/common/microgrid/electrical_components/_state_code.py index 36bd66dd..1c543b74 100644 --- a/src/frequenz/client/common/microgrid/electrical_components/_state_code.py +++ b/src/frequenz/client/common/microgrid/electrical_components/_state_code.py @@ -12,10 +12,17 @@ class ElectricalComponentStateCode(Enum): UNSPECIFIED = deprecated_member( 0, - "ElectricalComponentStateCode.UNSPECIFIED is deprecated; use the `int` value `0` " - "instead if you really need to check for this low-level value.", + "frequenz.client.common.microgrid.electrical_components." + "ElectricalComponentStateCode.UNSPECIFIED is deprecated since v0.4.1. " + "Use the int value 0 instead if you really need to check for this " + "low-level value.", ) - """Default value when the component state is not explicitly set.""" + """Default value when the component state is not explicitly set. + + Deprecated: + This member is deprecated since v0.4.1. Use the `int` value `0` instead + if you really need to check for this low-level value. + """ UNKNOWN = 1 """The component is in an unknown or undefined condition. diff --git a/src/frequenz/client/common/microgrid/electrical_components/proto/v1alpha8/_category.py b/src/frequenz/client/common/microgrid/electrical_components/proto/v1alpha8/_category.py index 1abc3305..1f807e6d 100644 --- a/src/frequenz/client/common/microgrid/electrical_components/proto/v1alpha8/_category.py +++ b/src/frequenz/client/common/microgrid/electrical_components/proto/v1alpha8/_category.py @@ -15,8 +15,10 @@ @typing_extensions.deprecated( - "electrical_component_category_from_proto() is deprecated; use " - "electrical_component_class_from_proto() instead." + "frequenz.client.common.microgrid.electrical_components.proto.v1alpha8." + "electrical_component_category_from_proto is deprecated since v0.4.1. Use " + "[frequenz.client.common.microgrid.electrical_components.proto.v1alpha8." + "electrical_component_class_from_proto][] instead." ) def electrical_component_category_from_proto( message: electrical_components_pb2.ElectricalComponentCategory.ValueType, @@ -37,8 +39,10 @@ def electrical_component_category_from_proto( @typing_extensions.deprecated( - "electrical_component_category_to_proto() is deprecated; use " - "electrical_component_class_to_proto() instead." + "frequenz.client.common.microgrid.electrical_components.proto.v1alpha8." + "electrical_component_category_to_proto is deprecated since v0.4.1. Use " + "[frequenz.client.common.microgrid.electrical_components.proto.v1alpha8." + "electrical_component_class_to_proto][] instead." ) def electrical_component_category_to_proto( category: ElectricalComponentCategory, diff --git a/src/frequenz/client/common/pagination/proto/v1alpha8/_pagination_info.py b/src/frequenz/client/common/pagination/proto/v1alpha8/_pagination_info.py index f510b2ec..de511bbe 100644 --- a/src/frequenz/client/common/pagination/proto/v1alpha8/_pagination_info.py +++ b/src/frequenz/client/common/pagination/proto/v1alpha8/_pagination_info.py @@ -13,8 +13,8 @@ @deprecated( "frequenz.client.common.pagination.proto.v1alpha8.pagination_info_from_proto " - "is deprecated. Use " - "frequenz.client.common.pagination.proto.v1alpha8.pagination_info_from_proto2 " + "is deprecated since v0.4.1. Use " + "[frequenz.client.common.pagination.proto.v1alpha8.pagination_info_from_proto2][] " "instead." ) def pagination_info_from_proto( # noqa: DOC502 @@ -22,11 +22,10 @@ def pagination_info_from_proto( # noqa: DOC502 ) -> PaginationInfo: """Convert a protobuf message to a [`PaginationInfo`][....PaginationInfo] object. - Warning: Deprecated - Use [`pagination_info_from_proto2`][..pagination_info_from_proto2] - instead. The new converter distinguishes well-formed from malformed - data at the type level (`PaginationInfo | InvalidPaginationInfo`) - rather than raising a `ValueError` when the invariant fires. + [`pagination_info_from_proto2`][..pagination_info_from_proto2] distinguishes + well-formed from malformed data at the type level + (`PaginationInfo | InvalidPaginationInfo`) rather than raising a + `ValueError` when the invariant fires. Args: message: The protobuf message to convert. diff --git a/src/frequenz/client/common/proto/_datetime.py b/src/frequenz/client/common/proto/_datetime.py index 80bf1f0c..a59d557d 100644 --- a/src/frequenz/client/common/proto/_datetime.py +++ b/src/frequenz/client/common/proto/_datetime.py @@ -60,22 +60,20 @@ def datetime_to_proto(dt: datetime | None) -> timestamp_pb2.Timestamp | None: @deprecated( - "`datetime_from_proto` is deprecated; use " - "`datetime_from_proto2` (returns `datetime | InvalidDatetime`) instead." + "frequenz.client.common.proto.datetime_from_proto is deprecated since " + "v0.4.1. Use [frequenz.client.common.proto.datetime_from_proto2][] instead." ) def datetime_from_proto( # noqa: DOC502 ts: timestamp_pb2.Timestamp, tz: timezone = timezone.utc ) -> datetime: """Convert a protobuf Timestamp to a datetime. - Warning: Deprecated - Use [`datetime_from_proto2`][..datetime_from_proto2] instead. The new - conversion function keeps a malformed timestamp in its return type - (`datetime | InvalidDatetime`) rather than raising or silently - repairing it, and is exact across the whole protobuf range, where this - function loses sub-second precision far from the epoch. It always - returns UTC; call [`astimezone()`][datetime.datetime.astimezone] on the - result instead of passing `tz`. + [`datetime_from_proto2`][..datetime_from_proto2] keeps a malformed + timestamp in its return type (`datetime | InvalidDatetime`) rather than + raising or silently repairing it, and is exact across the whole protobuf + range, where this function loses sub-second precision far from the epoch. + It always returns UTC; call [`astimezone()`][datetime.datetime.astimezone] + on the result instead of passing `tz`. Args: ts: The Timestamp object to convert. diff --git a/src/frequenz/client/common/streaming/_event.py b/src/frequenz/client/common/streaming/_event.py index 6907b476..54ccb462 100644 --- a/src/frequenz/client/common/streaming/_event.py +++ b/src/frequenz/client/common/streaming/_event.py @@ -12,10 +12,16 @@ class Event(Enum): UNSPECIFIED = deprecated_member( 0, - "Event.UNSPECIFIED is deprecated; use the `int` value `0` " - "instead if you really need to check for this low-level value.", + "frequenz.client.common.streaming.Event.UNSPECIFIED is deprecated " + "since v0.4.1. Use the int value 0 instead if you really need to check " + "for this low-level value.", ) - """Unspecified event type.""" + """Unspecified event type. + + Deprecated: + This member is deprecated since v0.4.1. Use the `int` value `0` instead + if you really need to check for this low-level value. + """ CREATED = 1 """Event when a new resource is created.""" diff --git a/tests/metrics/test_sample_metric_sample.py b/tests/metrics/test_sample_metric_sample.py index 1b90f58c..395a9950 100644 --- a/tests/metrics/test_sample_metric_sample.py +++ b/tests/metrics/test_sample_metric_sample.py @@ -413,7 +413,12 @@ def test_deprecated_sample_time_property(now: datetime) -> None: ) assert sample.sample_time2 is now with pytest.deprecated_call( - match="`MetricSample.sample_time` is deprecated; use `sample_time2` instead." + match=( + r"^frequenz\.client\.common\.metrics\.MetricSample\.sample_time is " + r"deprecated since v0\.4\.1\. Use " + r"\[frequenz\.client\.common\.metrics\.MetricSample\.sample_time2\]" + r"\[\] instead\.$" + ) ): assert sample.sample_time is now diff --git a/tests/pagination/proto/v1alpha8/test_pagination_info.py b/tests/pagination/proto/v1alpha8/test_pagination_info.py index 9230b803..d2aea970 100644 --- a/tests/pagination/proto/v1alpha8/test_pagination_info.py +++ b/tests/pagination/proto/v1alpha8/test_pagination_info.py @@ -39,9 +39,9 @@ def test_from_proto_emits_deprecation_warning() -> None: proto = pagination_info_pb2.PaginationInfo(total_items=1) with pytest.deprecated_call( match=r"^frequenz\.client\.common\.pagination\.proto\.v1alpha8\." - r"pagination_info_from_proto is deprecated\. Use " - r"frequenz\.client\.common\.pagination\.proto\.v1alpha8\." - r"pagination_info_from_proto2 instead\.$" + r"pagination_info_from_proto is deprecated since v0\.4\.1\. Use " + r"\[frequenz\.client\.common\.pagination\.proto\.v1alpha8\." + r"pagination_info_from_proto2\]\[\] instead\.$" ): pagination_info_from_proto(proto) diff --git a/tests/proto/test_datetime.py b/tests/proto/test_datetime.py index eb2b82d8..0c63e51a 100644 --- a/tests/proto/test_datetime.py +++ b/tests/proto/test_datetime.py @@ -83,8 +83,9 @@ def test_no_none_datetime(dt: datetime) -> None: def test_from_proto_is_deprecated() -> None: """`datetime_from_proto` warns and still converts as it always did.""" with pytest.deprecated_call( - match=r"`datetime_from_proto` is deprecated; use `datetime_from_proto2` " - r"\(returns `datetime \| InvalidDatetime`\) instead\." + match=r"^frequenz\.client\.common\.proto\.datetime_from_proto is " + r"deprecated since v0\.4\.1\. Use " + r"\[frequenz\.client\.common\.proto\.datetime_from_proto2\]\[\] instead\.$" ): converted = datetime_from_proto(Timestamp(seconds=1, nanos=500000000)) assert converted == datetime(1970, 1, 1, 0, 0, 1, 500000, tzinfo=timezone.utc) From 441e8efdf9a50f4bb9b548a549016aac6a72519c Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Mon, 21 Sep 2026 17:08:56 +0000 Subject: [PATCH 4/4] docs: Update the wrapping guide to the new deprecation style The previous commit changed every deprecation notice in the repo but left the wrapping guide describing the old message form, `" is deprecated. Use instead."`, with no version and no cross-reference brackets. The guide is what a contributor reads before writing a new deprecation, so leaving it behind would reintroduce the old form. It now describes the form the code uses and why: the version in the sentence because a separate "since" line cannot be expressed through the decorator, the replacement as a bare `[name][]` cross-reference so the generated admonition links to it, no backticks because the same string is printed as a runtime warning, and implicit concatenation of single-line strings because a triple-quoted message carries its indentation into both the cross-reference and the terminal. The worked example is updated to match, and now escapes the brackets and dots in its `pytest.deprecated_call()` regex. That is easy to get wrong silently, because `match` is a search and an unescaped `[...]` is a character class that still matches something. Also added is the hand-written `Deprecated:` admonition for the cases the decorator cannot reach, which the guide never mentioned: an argument, an enum member, or construction being made stricter, all of which the repo now has. The `deprecated_member` paragraph says outright that the helper produces no admonition on its own, since that is the whole reason the members need one written by hand. Signed-off-by: Leandro Lucarella --- .../deprecation-and-compatibility.md | 41 +++++++++++++++---- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/docs/wrapping-guide/deprecation-and-compatibility.md b/docs/wrapping-guide/deprecation-and-compatibility.md index 4009b81a..5f81f733 100644 --- a/docs/wrapping-guide/deprecation-and-compatibility.md +++ b/docs/wrapping-guide/deprecation-and-compatibility.md @@ -9,9 +9,20 @@ define the wider 0.x versioning process. Mark the old public symbol with [`typing_extensions.deprecated`][typing_extensions.deprecated]. Use this exact -message form: `" is deprecated. Use instead."`. Write both -fully qualified names exactly. In the old API documentation, explain any change -to the return type or behavior. A caller should know what to use from the +message form: `" is deprecated since v. Use [][] +instead."`. Write both fully qualified names exactly, the old one plain and the +replacement as a bare cross-reference so the rendered `Deprecated:` admonition +links to it. The version belongs in the sentence: a separate "since" line +cannot be expressed through the decorator, so the two would drift apart. + +The same string is printed as a runtime warning, so keep it to a sentence or +two and build it by concatenating single-line strings. A triple-quoted message +keeps its indentation, which stops the cross-reference from resolving and +prints an indented warning in the terminal. Do not put the names in backticks +either: they buy code font in the documentation at the cost of noise in the +console, where the reader cannot skip over them. Anything beyond "use X +instead", such as a change to the return type or behavior, goes in the +docstring body as prose. A caller should still know what to use from the warning alone. This example gives the replacement conversion function a numeric-suffixed name @@ -27,19 +38,31 @@ def thing_from_proto2(value: int) -> str: @deprecated( - "example.thing_from_proto is deprecated. Use example.thing_from_proto2 instead." + "example.thing_from_proto is deprecated since v0.4.1. " + "Use [example.thing_from_proto2][] instead." ) def thing_from_proto(value: int) -> str: return thing_from_proto2(value) with deprecated_call( - match="example.thing_from_proto is deprecated. " - "Use example.thing_from_proto2 instead." + match=r"^example\.thing_from_proto is deprecated since v0\.4\.1\. " + r"Use \[example\.thing_from_proto2\]\[\] instead\.$" ): assert thing_from_proto(3) == "3" ``` +`match` is a regular expression, so the brackets of the cross-reference have to +be escaped there, as do the dots of the qualified names. + +Where the decorator cannot reach, such as a single argument, an enum member, or +construction that is being made stricter, write the notice as a `Deprecated:` +admonition in the docstring instead. Put it immediately after the summary line +and give it no custom title (a title replaces +the word "Deprecated" in the rendered output), and again state the version in +the text. Never hand-write one for a symbol the decorator already marks, or the +page shows the same notice twice. + ## Check downstream adoption before removal When possible, check downstream client releases to see if they import or expose @@ -65,8 +88,10 @@ unsuffixed name while retaining a deprecated alias for the suffixed name. For an enum-member change, use [`deprecated_member`][frequenz.core.enum.deprecated_member]. It keeps the old -member temporarily and warns when code uses it. Document the representation new -code should use. +member temporarily and warns when code uses it, but it predates PEP 702 and +griffe knows nothing about it, so the member also needs the hand-written +`Deprecated:` admonition described above. Document the representation new code +should use. ## Tighten invariants in stages