From aec589d8abf26aa010c971666386b7edeb760852 Mon Sep 17 00:00:00 2001 From: Viicos <65306057+Viicos@users.noreply.github.com> Date: Sat, 22 Mar 2025 13:33:54 +0100 Subject: [PATCH] Fix compatibility with latest Python 3.14 release Adapt documentation and tests related to the `typing.Union` changes --- docs/usage.md | 8 +-- src/typing_inspection/introspection.py | 70 ++++++++++++++++++++++ tests/typing_objects/test_member_checks.py | 8 ++- 3 files changed, 81 insertions(+), 5 deletions(-) diff --git a/docs/usage.md b/docs/usage.md index c9ece27..7a538c6 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -4,18 +4,18 @@ The library is divided into two submodules: - [`typing_inspection.typing_objects`][]: provides functions to check if a variable is a [`typing`][] object: ```python - from typing_extensions import Union, get_origin + from typing_extensions import Literal, get_origin - from typing_inspection.typing_objects import is_union + from typing_inspection.typing_objects import is_literal - is_union(get_origin(Union[int, str])) # True + is_literal(get_origin(Literal[1, 2])) # True ``` !!! note You might be tempted to use a simple identity check: ```pycon - >>> get_origin(Union[int, str]) is typing.Union + >>> get_origin(Literal[1, 2]) is typing.Literal ``` However, [`typing_extensions`][] might provide a different version of the [`typing`][] objects. Instead, diff --git a/src/typing_inspection/introspection.py b/src/typing_inspection/introspection.py index 43cce1e..4f92527 100644 --- a/src/typing_inspection/introspection.py +++ b/src/typing_inspection/introspection.py @@ -23,6 +23,40 @@ 'is_union_origin', ) +if sys.version_info >= (3, 14): + + def is_union_origin(obj: Any, /) -> bool: + """Return whether the provided origin is the union form. + + ```pycon + >>> is_union_origin(typing.Union) + True + >>> is_union_origin(get_origin(int | str)) + True + >>> is_union_origin(types.UnionType) + True + ``` + + !!! note + Starting in Python 3.14, the [`typing.Union`][] special form [was changed](https://github.com/python/cpython/pull/105511) + to be an alias to [`types.UnionType`][]. As such, it is recommended to not use this function + anymore (provided that you only support Python 3.14 or greater), and instead perform + the check directly: + + ```python + import types + from typing import Union, get_origin + + typ = Union[int, str] + origin = get_origin(typ) + if origin is types.UnionType: + ... + ``` + """ + return obj is types.UnionType + return typing_objects.is_union(obj) or obj is types.UnionType + + if sys.version_info >= (3, 10): def is_union_origin(obj: Any, /) -> bool: @@ -33,7 +67,25 @@ def is_union_origin(obj: Any, /) -> bool: True >>> is_union_origin(get_origin(int | str)) True + >>> is_union_origin(types.UnionType) + True ``` + + !!! note + Starting in Python 3.14, the [`typing.Union`][] special form [was changed](https://github.com/python/cpython/pull/105511) + to be an alias to [`types.UnionType`][]. As such, it is recommended to not use this function + anymore (provided that you only support Python 3.14 or greater), and instead perform + the check directly: + + ```python + import types + from typing import Union, get_origin + + typ = Union[int, str] + origin = get_origin(typ) + if origin is types.UnionType: + ... + ``` """ return typing_objects.is_union(obj) or obj is types.UnionType @@ -47,7 +99,25 @@ def is_union_origin(obj: Any, /) -> bool: True >>> is_union_origin(get_origin(int | str)) True + >>> is_union_origin(types.UnionType) + True ``` + + !!! note + Starting in Python 3.14, the [`typing.Union`][] special form [was changed](https://github.com/python/cpython/pull/105511) + to be an alias to [`types.UnionType`][]. As such, it is recommended to not use this function + anymore (provided that you only support Python 3.14 or greater), and instead perform + the check directly: + + ```python + import types + from typing import Union, get_origin + + typ = Union[int, str] + origin = get_origin(typ) + if origin is types.UnionType: + ... + ``` """ return typing_objects.is_union(obj) diff --git a/tests/typing_objects/test_member_checks.py b/tests/typing_objects/test_member_checks.py index 86d9761..2cc5df0 100644 --- a/tests/typing_objects/test_member_checks.py +++ b/tests/typing_objects/test_member_checks.py @@ -189,6 +189,12 @@ def test_is_deprecated(deprecated: deprecated) -> None: # Misc. tests: -@pytest.mark.skipif(sys.version_info < (3, 10), reason='`types.UnionType` is only available in Python 3.10.') +@pytest.mark.skipif( + sys.version_info < (3, 10) or sys.version_info >= (3, 14), + reason=( + '`types.UnionType` is only available in Python 3.10. ' + 'In Python 3.14, `typing.Union` is an alias for `types.UnionType`.' + ), +) def test_is_union_does_not_match_uniontype() -> None: assert not typing_objects.is_union(types.UnionType)