Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions docs/_css/mkdocstrings.css
Original file line number Diff line number Diff line change
Expand Up @@ -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,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M10 2h4c3.31 0 5 2.69 5 6v10.66C16.88 17.63 15.07 17 12 17s-4.88.63-7 1.66V8c0-3.31 1.69-6 5-6M8 8v1.5h8V8zm1 4v1.5h6V12zM3 22v-.69c2.66-1.69 10.23-5.47 18-.06V22z"/></svg>');
}

.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);
}
41 changes: 33 additions & 8 deletions docs/wrapping-guide/deprecation-and-compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -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: `"<old FQCN> is deprecated. Use <new FQCN> 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: `"<old FQCN> is deprecated since v<X.Y.Z>. Use [<new FQCN>][]
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
Expand All @@ -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
Expand All @@ -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

Expand Down
4 changes: 4 additions & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
32 changes: 19 additions & 13 deletions src/frequenz/client/common/grid/_delivery_area.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand All @@ -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
Expand Down Expand Up @@ -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).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
12 changes: 9 additions & 3 deletions src/frequenz/client/common/metrics/_metric.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down
46 changes: 29 additions & 17 deletions src/frequenz/client/common/metrics/_sample.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down Expand Up @@ -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
Expand All @@ -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`.
Expand Down
29 changes: 13 additions & 16 deletions src/frequenz/client/common/metrics/proto/v1alpha8/_bounds.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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,
Expand All @@ -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.
Expand Down
Loading
Loading