Skip to content

Read per-alias messages from DeprecatedAlias entries - #4

Draft
llucax wants to merge 10 commits into
frequenz-floss:v1.x.xfrom
llucax:alias-messages
Draft

llucax wants to merge 10 commits into
frequenz-floss:v1.x.xfrom
llucax:alias-messages

Conversation

@llucax

@llucax llucax commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Reads the final call shape of frequenz.core.warnings.deprecated_aliases() released in frequenz-core v1.5.0, where every alias is a DeprecatedAlias entry saying where the symbol is now (new_module, new_name or both) and either since="vX.Y.Z" or a message of its own. The dict form v1.0.0 read never made it into a core release, so it is dropped.

Notices follow the deprecations guide, which separates the runtime warning, written for a terminal, from the notice in the documentation, which says since which version and links what to use instead:

  • An alias' notice always comes from since and its target, through default_message, now Deprecated since {since}. Use {new_link} instead.: the reader is already looking at the alias, so it doesn't repeat its name, and the new {new_link} field links the target in code font, by its name alone when it is in the same module. An alias' message is only its runtime warning and is never read.
  • An enum member only carries its runtime warning, so its notice is written by hand. Without one, the warning is shown as the notice.
  • A hand-written Deprecated: notice always replaces the generated one, and on aliases and enum members it is moved to the top of the docstring, where generated notices go.

Deprecations whose arguments can't be read statically are documented on a best-effort basis instead of being left unmarked, the same way for enum members and aliases: a generic notice says as much as can be read (Deprecated[ since V].[ Use [`Y`][] instead.], or Deprecated. It will be removed in a future release. when neither is known), and enum values are unwrapped even then. Anything short of what the guide asks for is a warning, so strict builds catch it: a notice without the version or the replacement, a runtime warning shown as the notice, a value left as written, a deprecation left unmarked, a call that doesn't bind the way the runtime binds it, or a link that can't be resolved. Argument values aren't validated beyond that, since documenting the code isn't linting it. Reading module-level constants is left for #8, reading the structured forms proposed in frequenz-floss/frequenz-core-python#206 for #10, and handling @deprecated too for #9. The design rules behind all this are in a new, short AGENTS.md.

This is meant as v1.0.1: what it drops only ever matched unreleased core code. The release notes cover what a v1.0.0 user can notice: the new default_message and its fields, the alias message no longer being documented, the hand-written notices enum members need, and the new warnings. The test-only core dependency is pinned to the released frequenz-core == 1.5.0 and is still absent from runtime dependencies.

@github-actions github-actions Bot added part:docs Affects the documentation part:tests Affects the unit, integration and performance (benchmarks) tests part:tooling Affects the development tooling (CI, deployment, dependency management, etc.) labels Sep 28, 2026
@llucax
llucax force-pushed the alias-messages branch 3 times, most recently from 434b45f to 39e2aa2 Compare October 5, 2026 14:11
@llucax
llucax requested a balanced review from Copilot October 6, 2026 08:55

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

Generic fallback handling can render a different message from metadata supplied by an earlier extension.

Review effort: Balanced
Findings: 1 Medium severity

Open (1)
What changed in this PR

Updates deprecation parsing for frequenz-core v1.5.0’s per-alias API.

Changes:

  • Parses DeprecatedAlias entries and their individual messages or versions.
  • Adds best-effort generic notices for unreadable declarations.
  • Updates documentation, fixtures, compatibility tests, and dependency pinning.
File Description
src/​griffe_frequenz_core/​deprecations.py Implements new alias parsing and fallback notices.
tests/​test_deprecations.py Expands behavioral coverage.
tests/​test_warnings.py Tests warning emission.
tests/​compat/​test_frequenz_core_compat.py Verifies runtime compatibility.
tests/​compat/​fixture_aliases.py Adds real-core alias fixtures.
tests/​fixtures/​samplepkg/​statuses.py Updates enum fallback fixture.
tests/​fixtures/​samplepkg/​oldmod2.py Exercises module-qualified helpers.
tests/​fixtures/​samplepkg/​oldmod.py Migrates aliases to DeprecatedAlias.
tests/​fixtures/​samplepkg/​nonliteral.py Covers partially readable entries.
tests/​fixtures/​samplepkg/​edge.py Updates external-target aliases.
tests/​fixtures/​samplepkg/​constant.py Covers constant-held entries.
tests/​fixtures/​numpymod.py Updates NumPy-style fixture.
README.md Documents behavior and options.
RELEASE_NOTES.md Records migration and fixes.
pyproject.toml Pins frequenz-core 1.5.0 for tests.

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

Comment thread src/griffe_frequenz_core/deprecations.py Outdated
Pin the test-only core dependency to the first release containing the
final DeprecatedAlias API.

Signed-off-by: Leandro Lucarella <luca-frequenz@llucax.com>

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 statically invalid runtime call shapes are currently documented as valid aliases.

Review effort: Balanced
Findings: 3 Medium severity · 1 Low severity

Open (4)
Resolved since last review (1)

Comment thread src/griffe_frequenz_core/deprecations.py Outdated
Comment thread src/griffe_frequenz_core/deprecations.py
Comment thread src/griffe_frequenz_core/deprecations.py
Comment thread RELEASE_NOTES.md Outdated
llucax added 7 commits October 6, 2026 15:53
`frequenz.core.warnings.deprecated_aliases()` no longer takes a dict
mapping every name to its target plus one call-level `message`: each
alias is now its own `DeprecatedAlias` entry passed positionally after
the module, giving either `since="v1.2.0"` or a `message` template of
its own, exactly one of them. This lets every alias say since which
version it is deprecated, which a single message for the whole table
could not. The dict form was never in a released `frequenz-core`, so it
is dropped here instead of being read alongside the new one.

Calls to the configured alias table functions are now read with the
signature `deprecated_aliases(module, /, *aliases)`: every positional
argument after the module must be a call to one of the new
`alias_classes` (by default `frequenz.core.warnings.DeprecatedAlias`),
bound as `DeprecatedAlias(name, /, *, new_module=None, new_name=None,
since=None, message=None)` with string literals, at least one of
`new_module` and `new_name`, and exactly one of `since` and `message`. A
keyword passed as `None` counts as not passed, as at runtime. The alias
points at `new_module`, or the module defining it when there is none,
followed by `new_name`, or its own name when there is none, the same way
core resolves it. Entries are read one by one, so one that cannot be
read is skipped with a debug log and the others are still marked.
Keyword arguments of the table call, such as `category` and
`stacklevel`, are ignored as before.

The deprecations guide separates the runtime warning, written for a
terminal, from the notice in the documentation, which says since which
version the symbol is deprecated and links what to use instead. So the
notice comes from `since` and the target only, and an entry's `message`
is never read: it is only the runtime warning. The `default_message`
option stays, with a new job: it is the template of that notice, and its
default is now ``Deprecated since {since}. Use {new_link} instead.``.
Unlike the runtime warning, it is only shown in the documentation of
the alias itself, so repeating its name adds nothing. `{old}` and
`{new}` are plain paths, and the new `{new_link}` links the target in
code font, by its name alone when it is in the same module, since the
context makes it obvious. `since` is inserted as written, braces
included, as core does. An entry giving `message` has no version to
say, so its notice only says what to use instead, and a hand-written one
can say more.

The link is a normal cross-reference rather than v1.0.0's optional
autoref, so a target that can't be resolved is reported by
`mkdocs-autorefs` instead of silently becoming plain text; a project
aliasing symbols from another one adds that project's inventory.

The fixtures move to the new form, mostly with `since`, and add an
import through a module alias (`w.DeprecatedAlias`), which Griffe
resolves to the real path.

Signed-off-by: Leandro Lucarella <luca-frequenz@llucax.com>
Until now only `deprecated_member()` was checked against a real
`frequenz-core`, since no release had `frequenz.core.warnings`. With
`deprecated_aliases()` and `DeprecatedAlias` available, a fixture now
declares four aliases, one giving `since`, one moved and renamed and
giving a message of its own, one renamed in the fixture itself, without
`new_module`, and one moved giving a message, and is both loaded by
Griffe and imported, so the documentation of each alias is compared with
the warning the real helper emits. A notice is worded for the
documentation, so it is not the runtime warning: the ones giving `since`
must say the same version and the same replacement, and the ones giving
a message must warn with it and document the same replacement the
runtime resolves.

The runtime `{old}` comes from the module's `__name__`, so Griffe loads
the fixture under the same dotted path pytest imported it as.

Two more tests pin the parameter layout the extension relies on:
`DeprecatedAlias(name, /, *, new_module=None, new_name=None, since=None,
message=None)` and `deprecated_aliases(module, /, *aliases, ...)` with
only keyword-only parameters after the entries.

Signed-off-by: Leandro Lucarella <luca-frequenz@llucax.com>
A wrapped enum member whose message comes from a constant or a helper
call, like `BREAKER = deprecated_member(7, _member_message("BREAKER"))`,
was left unmarked and rendered as the whole wrapper call, because the
value was only rewritten after the message had been read as a string
literal. The wrapper call alone says the member is deprecated, though:
only its message is unknown.

Once the call is known to bind exactly a value and a message, the value
is now rewritten regardless of the message, as written rather than
evaluated, and the member is marked with a generic message, `Deprecated.
It will be removed in a future release.`. Like any text the extension
writes itself, it is only shown in the docs of the member, so it doesn't
say which member it is, and it says nothing it cannot know. A message
another extension already set is used instead, in the field and the
admonition alike, and a hand-written notice is kept instead of the
generic one.

A wrapper whose arguments are unpacked from `*args` or `**kwargs` is
marked with the generic message too, since it deprecates the member all
the same, but its value is left as written, since the positions cannot
be trusted. A call that does not bind a value and a message would fail
at runtime, so it is still left alone.

Signed-off-by: Leandro Lucarella <luca-frequenz@llucax.com>
An alias entry was only documented when every one of its arguments was a
string literal, so a `since` shared through a constant left the alias
looking like a current symbol: no label, no notice, and its private
type-checking value instead of the target.

The name is all it takes to know what to mark, so it is now the only
argument that has to be a literal; the message is never read anyway. The
rest is used as far as it can be read:

- The target is linked in the notice and shown as the value only when
  `new_module` and `new_name` can both be read.
- `default_message` is used when `since` and the target are known and it
  can be formatted.
- Otherwise a generic notice stands in for it, saying as much as is
  known, in the wording of `default_message`: `Deprecated since <since>.
  Use <new> instead.`, dropping ` since <since>` when the version is
  unknown and `Use <new> instead.` when the target is. With neither, it
  is the enum members' `Deprecated. It will be removed in a future
  release.`. What the notice can't say is logged at debug level.

The same applies when the entry's keyword arguments are unpacked from a
`**kwargs`, as long as the name is passed on its own: it is the only
thing that can be known. An entry whose name cannot be read, or that
would not bind at runtime, is still skipped, since it either names
nothing or deprecates nothing. A bad `default_message` now falls back to
the generic notice too, instead of leaving `since` entries unmarked.

This mirrors enum members, which are marked with the same generic notice
when their message cannot be read.

Signed-off-by: Leandro Lucarella <luca-frequenz@llucax.com>
Whatever the extension could not read was only logged at debug level, so
the documentation could quietly show less than the code says, and nobody
noticed unless they ran `mkdocs build --verbose` and looked. The
deprecations guide treats the notice as the only channel that reaches
users of an alias or an enum member, and asks it to say since which
version the symbol is deprecated and what to use instead, so a notice
short of that should not pass silently.

Every such case is now logged as a warning, so a `mkdocs build
--strict` fails until it is fixed, and a project that prefers not to
fail on these can build without `--strict`:

- A notice that can't say since which version or what to use instead,
  because an argument cannot be read or an alias gives a message instead
  of `since`, and a generic notice standing in for an enum message that
  cannot be read.
- An enum member with only its runtime warning, which is then shown as
  its notice: the wrapper can't say more, so the guide asks for a
  hand-written notice.
- A value left as written: an alias whose target cannot be read, or an
  enum member whose wrapper arguments are unpacked.
- A valid alias entry that cannot be documented at all, since its name
  cannot be read.
- A call whose arguments don't bind the way the runtime binds them, such
  as a wrapper or an alias entry with missing arguments: the
  documentation doesn't show it as a deprecation either. Argument values
  are not validated beyond that: documenting the code is not linting it,
  and the project's own tests catch a call that fails at import.

A hand-written notice, or a message another extension found, rules out
the ones about the notice, since the documentation is complete then. Why
something could not be read is still logged at debug level. The tests go
in a module of their own.

Signed-off-by: Leandro Lucarella <luca-frequenz@llucax.com>
A generated notice goes at the very top of the docstring, above the
summary, while a hand-written one can only come after the summary, since
a docstring has to start with it. A page mixing both showed the notices
in different places, and the deprecations guide asks for a hand-written
notice wherever the helper can't generate a complete one, which for an
enum member is always.

When the extension marks an alias or an enum member whose docstring
already has a deprecation notice, it now moves that notice to the top,
where a generated one would go. Only objects the extension marks are
touched: anywhere else, such as a function documenting one deprecated
argument, a `Deprecated:` section doesn't mean the whole object is
deprecated, so it stays where it was written.

Signed-off-by: Leandro Lucarella <luca-frequenz@llucax.com>
The enum member examples used messages like "PENDING is deprecated, use
OPEN instead", while the deprecations guide asks for runtime warnings
starting with the plain fully qualified name and saying what to use
instead, also by its fully qualified name, and for a hand-written notice
saying since which version and linking the replacement, since the
wrapper only carries the warning. The guide's own-deprecations pytest
filter relies on the first part, and these examples are what people
copy, so they now follow it. The README keeps one member without a
notice to show what happens then.

Signed-off-by: Leandro Lucarella <luca-frequenz@llucax.com>
llucax added 2 commits October 6, 2026 15:53
The rework of this release settled a few rules that are easy to break
again in a later change, mainly that the extension follows the
deprecations guide's split between the runtime warning and the notice,
that anything short of what the guide asks for must warn instead of
passing silently, that a hand-written notice always wins, that the
extension's own text is docs-only, that links are never optional, and
that calls are read the way the runtime binds them without validating
argument values. They are written down for agents and contributors
alike, kept short and general.

It is left out of the source distribution, like `CONTRIBUTING.md`.

Signed-off-by: Leandro Lucarella <luca-frequenz@llucax.com>
Replaces the first release's notes with the ones for the next release,
a fix release: the dict form v1.0.0 read never made it into a
`frequenz-core` release, so reading the released `DeprecatedAlias` form
instead is a bug fix, and so are the notices following the deprecations
guide and the generic notices. The changed meaning, fields and default
of `default_message` go under "Upgrading", since a value set for v1.0.0
has no `{since}` and no longer gets a link for `{new}`, and so do the
alias `message` no longer being documented, the hand-written notices
enum members now need, and the new warnings, since they can make a
strict build fail where v1.0.0 passed.

Signed-off-by: Leandro Lucarella <luca-frequenz@llucax.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

part:docs Affects the documentation part:tests Affects the unit, integration and performance (benchmarks) tests part:tooling Affects the development tooling (CI, deployment, dependency management, etc.)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants