Skip to content

Enable py3.14 and deprecate some alias functionality#225

Open
nstarman wants to merge 4 commits intobeartype:masterfrom
nstarman:mnt/py314
Open

Enable py3.14 and deprecate some alias functionality#225
nstarman wants to merge 4 commits intobeartype:masterfrom
nstarman:mnt/py314

Conversation

@nstarman
Copy link
Collaborator

@nstarman nstarman commented Nov 23, 2025

Aliases are now always turned on. The alias machinery dynamically creates TypeAliasType objects that are ONLY used in string representations, never in the actual type.

Fixes #224
Fixes #227

@coveralls
Copy link

coveralls commented Nov 23, 2025

Pull Request Test Coverage Report for Build 22191117846

Details

  • 106 of 106 (100.0%) changed or added relevant lines in 4 files are covered.
  • No unchanged relevant lines lost coverage.
  • Overall coverage increased (+0.009%) to 99.6%

Totals Coverage Status
Change from base Build 22190629474: 0.009%
Covered Lines: 997
Relevant Lines: 1001

💛 - Coveralls

@nstarman nstarman force-pushed the mnt/py314 branch 2 times, most recently from 99374d0 to baee207 Compare November 24, 2025 00:03
@nstarman
Copy link
Collaborator Author

@wesselb I don't know how to resolve this serious problem.

@nstarman
Copy link
Collaborator Author

Looking at https://github.com/python/cpython/blob/425f24e4fad672c211307a9f0018c8d39c4db9de/Objects/unionobject.c#L279 I kind of doubt the union alias functionality can continue to work this way.
A potential alternative would be if beartype could dynamically generate TypeAliasType of the union and swap it in-place by modifying the function's annotations.

Meanwhile can I make this a no-op for Python 3.14+?

@wesselb
Copy link
Member

wesselb commented Nov 24, 2025

Meanwhile can I make this a no-op for Python 3.14+?

Absolutely. Let's do this for now until we figure out whether this is still possible for 3.14+.

@nstarman
Copy link
Collaborator Author

Fully functional until py3.14 then they turn into deprecated no-ops.

@nstarman
Copy link
Collaborator Author

Tests need adjustment

@nstarman
Copy link
Collaborator Author

I can work on this after #179

@wesselb
Copy link
Member

wesselb commented Dec 18, 2025

@nstarman that would be amazing! :)

@nstarman nstarman force-pushed the mnt/py314 branch 10 times, most recently from 23b77fd to 76e1a75 Compare February 13, 2026 04:34
@nstarman nstarman changed the title test: py3.14 enable py3.14 Feb 13, 2026
@nstarman
Copy link
Collaborator Author

CleanShot 2026-02-19 at 11 29 56

@nstarman nstarman marked this pull request as ready for review February 19, 2026 16:30
@nstarman nstarman requested review from Copilot and wesselb February 19, 2026 16:30
@nstarman nstarman changed the title enable py3.14 Enable py3.14 and deprecate some alias functionality Feb 19, 2026
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Enables Python 3.14 support by avoiding typing.Union monkeypatching (now immutable) and shifting union-alias handling to Plum’s own formatting/registry, with updated tests/docs/CI to cover the new behavior.

Changes:

  • Add Python 3.14 test coverage and split union-alias tests by Python version.
  • Update union aliasing internals to use TypeAliasType on 3.14+ and apply alias-aware formatting in repr_short/repr_type.
  • Adjust dependency constraints and documentation examples for the new 3.14+ behavior.

Reviewed changes

Copilot reviewed 13 out of 14 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/plum/_alias.py Adds 3.14+ path using TypeAliasType, deprecates activation APIs, and introduces _transform_union_alias.
src/plum/repr.py Uses _transform_union_alias so repr_short/repr_type can display registered union aliases.
src/plum/_signature.py Refactors beartype type-hint wrapping usage (import/usage cleanup).
src/plum/_type.py Small refactor in resolve_type_hint and adjusts UNION_TYPES container type.
tests/test_alias_upto313.py Skips these tests on 3.14+ and routes activation calls via plum.*.
tests/test_alias_314plus.py New 3.14+ focused tests for alias formatting and deprecated APIs.
tests/test_util.py Updates expected typing.Union[...] short repr for 3.14+.
tests/conftest.py Adds an autouse fixture to isolate/clear the union-alias registry per test.
docs/union_aliases.md Adds version-gated examples for the changed Union repr behavior in 3.14+.
docs/comparison.md Adds version-gated example output to match 3.14+ Union repr changes.
pyproject.toml Adds conditional beartype minimum versions keyed on Python version.
.github/workflows/ci.yml Adds Python 3.14 to CI and pre-release beartype test runs.
.pre-commit-config.yaml Removes default Python language version pinning.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +33 to +39
extra-install: "pip install --upgrade --pre beartype"
- name: "3.14"
python-version: "3.14"
extra-install: ""
- name: "3.14-pre-beartype"
python-version: "3.14"
extra-install: "pip install --upgrade --pre beartype"
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

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

The matrix extra-install uses pip install ... even though the job environment is managed via uv sync / uv run. Without activating the venv, pip may install outside the uv environment, so the pre-beartype runs might not actually test against the pre-release beartype. Use uv pip install --upgrade --pre beartype (or uv run python -m pip ...) to ensure the install lands in the environment used by the subsequent uv run pytest.

Suggested change
extra-install: "pip install --upgrade --pre beartype"
- name: "3.14"
python-version: "3.14"
extra-install: ""
- name: "3.14-pre-beartype"
python-version: "3.14"
extra-install: "pip install --upgrade --pre beartype"
extra-install: "uv pip install --upgrade --pre beartype"
- name: "3.14"
python-version: "3.14"
extra-install: ""
- name: "3.14-pre-beartype"
python-version: "3.14"
extra-install: "uv pip install --upgrade --pre beartype"

Copilot uses AI. Check for mistakes.
Comment on lines 41 to 42
Union aliases must be activated explicitly, because the feature
monkeypatches `Union.__str__` and `Union.__repr__`.
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

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

This paragraph implies union aliases must be activated because they monkeypatch Union.__str__/__repr__, but on Python 3.14+ activate_union_aliases is deprecated and Union repr can’t be monkeypatched. Consider rewording to make the behavior/version split explicit (e.g., activation required only on <=3.13; on 3.14+ aliasing affects Plum’s own formatting only).

Suggested change
Union aliases must be activated explicitly, because the feature
monkeypatches `Union.__str__` and `Union.__repr__`.
On Python 3.13 and earlier, union aliases work by monkeypatching
`typing.Union.__str__` and `typing.Union.__repr__`, and therefore must be
activated explicitly.
On Python 3.14 and later, `typing.Union`'s representation can no longer be
monkeypatched and `activate_union_aliases()` is deprecated; union aliases
instead only affect how Plum formats unions in its own output.

Copilot uses AI. Check for mistakes.
Comment on lines 120 to +124
>>> Union[tuple(scalar_types) + (tuple,)] # Scalar or tuple
typing.Union[Scalar, tuple]
typing.Union[Scalar, tuple]

>>> Union[tuple(scalar_types) + (tuple, list)] # Scalar or tuple or list
typing.Union[Scalar, tuple, list]
typing.Union[Scalar, tuple, list]
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

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

The two output lines in this code block have leading whitespace before typing.Union[...] (note the extra space). That will likely cause doctest/sybil output matching to fail and also looks unintentional; remove the leading space(s).

Copilot uses AI. Check for mistakes.
Comment on lines +60 to +65
def test_union_alias(display):
plum.set_union_alias(int | str, alias="IntStr")

# Check that printing is normal before registering any aliases.
assert display(Union[int, str]) == "IntStr" # noqa: UP007

Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

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

The comment says printing is checked "before registering any aliases", but the alias was already registered just above. Either move the assertion before the first set_union_alias call or adjust the comment so it matches the test setup.

Copilot uses AI. Check for mistakes.
Comment on lines +206 to +212
# Check for conflicting aliases
for existing_union, existing_alias in _ALIASED_UNIONS.items():
if set(existing_union) == set(args) and alias != repr(existing_alias):
union_str = repr(union)
raise RuntimeError(
f"`{union_str}` already has alias `{existing_alias!r}`."
)
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

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

Avoid using repr(existing_alias) for conflict detection. repr() isn’t a stable API and may change formatting across Python/typing_extensions versions, which could incorrectly treat re-registering the same alias name as a conflict. Compare against the alias name attribute instead (e.g., existing_alias.__name__).

Copilot uses AI. Check for mistakes.
Signed-off-by: nstarman <nstarman@users.noreply.github.com>
Signed-off-by: nstarman <nstarman@users.noreply.github.com>
Signed-off-by: nstarman <nstarman@users.noreply.github.com>
Signed-off-by: nstarman <nstarman@users.noreply.github.com>
@nstarman
Copy link
Collaborator Author

nstarman commented Feb 19, 2026

@leycec one part of this PR is to change the type hint repr machinery in plum.alias to use TypeAliasType objects that are never used a runtime, only for pretty printing. I was wondering if this functionality, which turns out to be quite simple, would be relevant to upstream into beartype itself. With these aliases type annotations can become visually much simpler.

#225 (comment)

@nstarman
Copy link
Collaborator Author

@wesselb, currently this PR uses the TypeAliasType repr directly, but we might want to preserve the old behaviour of transforming it to Union[{repr}]. LMK.

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.

Alternative for Union Aliases in Py3.14+ Problems with Union Aliases in Python 3.14

3 participants

Comments