Skip to content

Add a guideline on how to deprecate Python symbols - #6

Draft
llucax wants to merge 5 commits into
frequenz-floss:v0.x.xfrom
llucax:add-deprecations-doc
Draft

llucax wants to merge 5 commits into
frequenz-floss:v0.x.xfrom
llucax:add-deprecations-doc

Conversation

@llucax

@llucax llucax commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

Adds python/deprecations.md, the canonical description of how we deprecate things in Python projects, and links it from python/semver-0.x.x.md.

It goes in its own document rather than into the semver one because deprecation applies just as much to libraries past 1.0.0, so the 0.x.x rules can't be its home. There is no 1.0-and-later versioning document in this repository yet; when there is one, it should link here too.

The content is what we learned doing this across our repositories, not a restatement of PEP 702. The parts most worth reviewing are the claim that a deprecation must never break downstream builds (which rules out -W error::DeprecationWarning and hiding symbols from type checkers), and the section on what type checkers can never report, since aliases, re-exports, modules and constants keep being rediscovered as dead ends.

It is deliberately consistent with frequenz-client-common's deprecation and compatibility guide, which stays as the client-specific policy sitting under this one.

It also covers the tooling that goes with it: the frequenz.core.warnings helpers from frequenz-floss/frequenz-core-python#199, with the per-alias DeprecatedAlias messages used in the deprecated_aliases() example from frequenz-floss/frequenz-core-python#200, both merged and released in frequenz-core v1.5.0; the griffe-frequenz-core extension that renders the deprecations those helpers declare; and the Deprecated: admonition convention from frequenz-floss/frequenz-repo-config-python#641.

The pytest configuration it shows includes a filterwarnings entry that turns the project's own deprecations into errors, matching messages that start with the deprecated symbol's fully qualified name, while deprecations from dependencies stay warnings. It comes from frequenz-floss/frequenz-repo-config-python#647, which adds it to the template and to the migration script. It is merged but not released yet.

Draft until repo-config ships what the guide says the template provides: frequenz-floss/frequenz-repo-config-python#641 and frequenz-floss/frequenz-repo-config-python#647 are merged but not released yet, and the template support for the griffe-frequenz-core extension (including the griffe-frequenz-core release that reads DeprecatedAlias) is still to come. The frequenz.core.warnings links already resolve, as frequenz-core v1.5.0 is released.

@llucax llucax self-assigned this Sep 21, 2026
@llucax
llucax requested a balanced review from Copilot September 21, 2026 12:59

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Several requirements conflict with mypy behavior, the existing semver policy, and the referenced client guide.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 2 Low severity

Open (2)
What changed in this PR

Adds a canonical Python deprecation policy and connects it to the existing 0.x.x versioning guidance.

Changes:

  • Defines runtime, typing, documentation, testing, and release-note practices.
  • Documents compatibility aliases for moved symbols.
  • Links the versioning guide to the new policy.
File Description
python/​deprecations.md Adds the deprecation guide.
python/​semver-0.x.x.md Links to the guide.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread python/deprecations.md Outdated
Comment thread python/semver-0.x.x.md
@llucax
llucax force-pushed the add-deprecations-doc branch from 9703ba9 to 04e28a6 Compare September 22, 2026 07:32
@llucax

llucax commented Sep 22, 2026

Copy link
Copy Markdown
Contributor Author

This is a draft because it needs frequenz-core 1.5.0 to be released first.

`semver-0.x.x.md` says when a deprecated symbol may be removed, but not how
to deprecate one, and deprecation applies just as much to libraries past
1.0.0, so it can't live there.

The document collects what was learned by actually doing this across our
repositories: that a deprecation must never break downstream builds, that
`typing_extensions.deprecated` covers the type checker, the rendered docs and
the runtime warning from a single message, what PEP 702 explicitly left out
and therefore what only the rendered admonition can reach, how to keep an old
import path working through a module `__getattr__`, and the test and release
note every deprecation needs.

Signed-off-by: Leandro Lucarella <luca-frequenz@llucax.com>
Deprecation is what the 0.x.x rules lean on to keep patch releases
backwards-compatible, so readers landing there need a way to find how it is
actually done.

Signed-off-by: Leandro Lucarella <luca-frequenz@llucax.com>
The guide predates three things it now has to describe: the final
`frequenz.core.warnings` API (frequenz-core#199), the
`griffe-frequenz-core` extension, and the `Deprecated:` admonition
convention from frequenz-repo-config#641.

Enum members and moved symbols now have helpers that warn at runtime
and, through `griffe_frequenz_core.deprecations`, get the same rendered
admonition as a decorated symbol, so they move out of the "write it by
hand" list, which keeps only function arguments, modules, constants and
attributes. The alias example passes a `message` with the version,
since the default template has none and the guide requires one.

Library code touching its own deprecated symbols is told to use
`ignoring_deprecations()` instead of `warnings.catch_warnings()`, which
resets the deduplication history for the whole program, and tests get
`asserting_no_deprecations()` to check that a replacement doesn't go
through the deprecated symbol, a mistake the `once::DeprecationWarning`
filter hides.

The claim that hand-written admonitions go where the extension puts the
generated ones was wrong: both extensions insert them above the summary,
which a docstring can't do. The new last section shows the `mkdocs.yml`
configuration for both extensions.

Signed-off-by: Leandro Lucarella <luca-frequenz@llucax.com>
The "Enum members" bullet says to use `frequenz.core.enum.Enum` but not
how a member is actually marked, which the deprecations guide now
covers with `deprecated_member()`, so link that section.

Signed-off-by: Leandro Lucarella <luca-frequenz@llucax.com>
The repository configuration template now adds a `filterwarnings` entry
that turns deprecation warnings mentioning the project's own fully
qualified names into errors, so a project can't release code still using
symbols it deprecated itself. Dependencies' deprecations stay warnings,
so this doesn't contradict the rule that a deprecation must never break
downstream builds.

The guide showed the configuration without it, and the message rule
that makes it work was only loosely motivated, so both now point at each
other, and the rule says the name must be plain text, since backticks or
a cross-reference stop the filter from matching.

The testing section assumed every deprecation was only reported once.
It now says that tests using the project's own deprecated symbols on
purpose need `pytest.deprecated_call()` or a `filterwarnings` mark, and
that the filter catches a replacement still going through the deprecated
symbol only heuristically, which is why `asserting_no_deprecations()` is
still recommended.

Signed-off-by: Leandro Lucarella <luca-frequenz@llucax.com>
@llucax
llucax force-pushed the add-deprecations-doc branch from 7fb88d6 to 6956ff2 Compare September 30, 2026 13:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants