Enable py3.14 and deprecate some alias functionality#225
Enable py3.14 and deprecate some alias functionality#225nstarman wants to merge 4 commits intobeartype:masterfrom
Conversation
Pull Request Test Coverage Report for Build 22191117846Details
💛 - Coveralls |
99374d0 to
baee207
Compare
|
@wesselb I don't know how to resolve this serious problem. |
|
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. 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+. |
|
Fully functional until py3.14 then they turn into deprecated no-ops. |
|
Tests need adjustment |
|
I can work on this after #179 |
|
@nstarman that would be amazing! :) |
23b77fd to
76e1a75
Compare
There was a problem hiding this comment.
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
TypeAliasTypeon 3.14+ and apply alias-aware formatting inrepr_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.
| 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" |
There was a problem hiding this comment.
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.
| 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" |
| Union aliases must be activated explicitly, because the feature | ||
| monkeypatches `Union.__str__` and `Union.__repr__`. |
There was a problem hiding this comment.
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).
| 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. |
| >>> 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] |
There was a problem hiding this comment.
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).
| 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 | ||
|
|
There was a problem hiding this comment.
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.
| # 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}`." | ||
| ) |
There was a problem hiding this comment.
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__).
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>
|
@leycec one part of this PR is to change the type hint repr machinery in |
|
@wesselb, currently this PR uses the TypeAliasType repr directly, but we might want to preserve the old behaviour of transforming it to |

Aliases are now always turned on. The alias machinery dynamically creates
TypeAliasTypeobjects that are ONLY used in string representations, never in the actual type.Fixes #224
Fixes #227