From 27506111d554fa01c409288b2a20de1c5c4d3953 Mon Sep 17 00:00:00 2001 From: Jakub Stasiak Date: Fri, 7 Feb 2020 19:12:46 +0100 Subject: [PATCH] Backport get_origin() and get_args() (#698) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The implementations come from CPython commit 427c84f13f77 with one small change – the get_origin's docstring mentions Annotated as it's also supported. get_origin() and get_args() introduced in [1] and modified in [2] to support Annotated. [1] https://github.com/python/cpython/pull/13685 [2] https://github.com/python/cpython/pull/18260 * Define our own get_origin()/get_args() in typing_extensions on Python 3.8 Otherwise typing_extensions.get_origin() would not recognize typing_extensions.Annotated on 3.8. --- .../src_py3/test_typing_extensions.py | 2 + .../src_py3/typing_extensions.py | 52 ++++++++++++++++++- 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/typing_extensions/src_py3/test_typing_extensions.py b/typing_extensions/src_py3/test_typing_extensions.py index e39612c6..502571ed 100644 --- a/typing_extensions/src_py3/test_typing_extensions.py +++ b/typing_extensions/src_py3/test_typing_extensions.py @@ -1833,6 +1833,8 @@ def test_typing_extensions_defers_when_possible(self): 'Final', 'get_type_hints' } + if sys.version_info[:2] == (3, 8): + exclude |= {'get_args', 'get_origin'} for item in typing_extensions.__all__: if item not in exclude and hasattr(typing, item): self.assertIs( diff --git a/typing_extensions/src_py3/typing_extensions.py b/typing_extensions/src_py3/typing_extensions.py index dceb35e4..11dfbb03 100644 --- a/typing_extensions/src_py3/typing_extensions.py +++ b/typing_extensions/src_py3/typing_extensions.py @@ -151,7 +151,7 @@ def _check_methods_in_mro(C, *methods): HAVE_ANNOTATED = PEP_560 or SUBS_TREE if PEP_560: - __all__.append("get_type_hints") + __all__.extend(["get_args", "get_origin", "get_type_hints"]) if HAVE_ANNOTATED: __all__.append("Annotated") @@ -1992,3 +1992,53 @@ class Annotated(metaclass=AnnotatedMeta): OptimizedList = Annotated[List[T], runtime.Optimize()] OptimizedList[int] == Annotated[List[int], runtime.Optimize()] """ + +# Python 3.8 has get_origin() and get_args() but those implementations aren't +# Annotated-aware, so we can't use those, only Python 3.9 versions will do. +if sys.version_info[:2] >= (3, 9): + get_origin = typing.get_origin + get_args = typing.get_args +elif PEP_560: + from typing import _GenericAlias # noqa + + def get_origin(tp): + """Get the unsubscripted version of a type. + + This supports generic types, Callable, Tuple, Union, Literal, Final, ClassVar + and Annotated. Return None for unsupported types. Examples:: + + get_origin(Literal[42]) is Literal + get_origin(int) is None + get_origin(ClassVar[int]) is ClassVar + get_origin(Generic) is Generic + get_origin(Generic[T]) is Generic + get_origin(Union[T, int]) is Union + get_origin(List[Tuple[T, T]][int]) == list + """ + if isinstance(tp, _AnnotatedAlias): + return Annotated + if isinstance(tp, _GenericAlias): + return tp.__origin__ + if tp is Generic: + return Generic + return None + + def get_args(tp): + """Get type arguments with all substitutions performed. + + For unions, basic simplifications used by Union constructor are performed. + Examples:: + get_args(Dict[str, int]) == (str, int) + get_args(int) == () + get_args(Union[int, Union[T, int], str][int]) == (int, str) + get_args(Union[int, Tuple[T, int]][str]) == (int, Tuple[str, int]) + get_args(Callable[[], T][int]) == ([], int) + """ + if isinstance(tp, _AnnotatedAlias): + return (tp.__origin__,) + tp.__metadata__ + if isinstance(tp, _GenericAlias): + res = tp.__args__ + if get_origin(tp) is collections.abc.Callable and res[0] is not Ellipsis: + res = (list(res[:-1]), res[-1]) + return res + return ()