From fac50dab79083945d789afb45e54e5c048c4abd1 Mon Sep 17 00:00:00 2001 From: Jacob Hayes Date: Wed, 13 Jan 2021 17:32:56 -0600 Subject: [PATCH] Extend existing get_args/get_origin --- pydantic/typing.py | 94 ++++++++++++++++++++++------------------------ 1 file changed, 44 insertions(+), 50 deletions(-) diff --git a/pydantic/typing.py b/pydantic/typing.py index 15d4eff6593..a2f6779629e 100644 --- a/pydantic/typing.py +++ b/pydantic/typing.py @@ -80,6 +80,40 @@ def evaluate_forwardref(type_: ForwardRef, globalns: Any, localns: Any) -> Any: AnyCallable = TypingCallable[..., Any] NoArgAnyCallable = TypingCallable[[], Any] + +if sys.version_info >= (3, 9): + from typing import Annotated, _AnnotatedAlias +else: + try: + from typing_extensions import Annotated + + try: + from typing_extensions import _AnnotatedAlias + except ImportError: + # py3.6 typing_extensions doesn't have _AnnotatedAlias, but has AnnotatedMeta, which + # will satisfy our `isinstance` checks. + from typing_extensions import AnnotatedMeta as _AnnotatedAlias + except ImportError: + # Create mock Annotated/_AnnotatedAlias values distinct from `None`, which is a valid + # `get_origin` return value. + class _FalseMeta(type): + # Allow short circuiting with "Annotated[...] if Annotated else None". + def __bool__(cls): + return False + + # Give a nice suggestion for unguarded use + def __getitem__(cls, key): + raise RuntimeError( + 'Annotated is not supported in this python version, please `pip install typing-extensions`.' + ) + + class Annotated(metaclass=_FalseMeta): + pass + + class _AnnotatedAlias(metaclass=_FalseMeta): + pass + + if sys.version_info < (3, 8): # noqa: C901 if TYPE_CHECKING: from typing_extensions import Literal @@ -100,6 +134,8 @@ def get_args(t: Type[Any]) -> Tuple[Any, ...]: python 3.6). """ + if isinstance(t, _AnnotatedAlias): + return t.__args__ + t.__metadata__ return getattr(t, '__args__', ()) else: @@ -111,6 +147,8 @@ def get_args(t: Type[Any]) -> Tuple[Any, ...]: Mostly compatible with the python 3.8 `typing` module version and able to handle almost all use cases. """ + if isinstance(t, _AnnotatedAlias): + return t.__args__ + t.__metadata__ if isinstance(t, _GenericAlias): res = t.__args__ if t.__origin__ is Callable and res and res[0] is not Ellipsis: @@ -119,6 +157,8 @@ def get_args(t: Type[Any]) -> Tuple[Any, ...]: return getattr(t, '__args__', ()) def get_origin(t: Type[Any]) -> Optional[Type[Any]]: + if isinstance(t, _AnnotatedAlias): + return Annotated return getattr(t, '__origin__', None) @@ -132,6 +172,8 @@ def get_origin(tp: Type[Any]) -> Type[Any]: It should be useless once https://github.com/cython/cython/issues/3537 is solved and https://github.com/samuelcolvin/pydantic/pull/1753 is merged. """ + if isinstance(tp, _AnnotatedAlias): + return Annotated return typing_get_origin(tp) or getattr(tp, '__origin__', None) def generic_get_args(tp: Type[Any]) -> Tuple[Any, ...]: @@ -156,58 +198,10 @@ def get_args(tp: Type[Any]) -> Tuple[Any, ...]: get_args(Union[int, Tuple[T, int]][str]) == (int, Tuple[str, int]) get_args(Callable[[], T][int]) == ([], int) """ - # the fallback is needed for the same reasons as `get_origin` (see above) - return typing_get_args(tp) or getattr(tp, '__args__', ()) or generic_get_args(tp) - - -if sys.version_info >= (3, 9): - from typing import Annotated -else: - if TYPE_CHECKING: - from typing_extensions import Annotated, _AnnotatedAlias - else: # due to different mypy warnings raised during CI for python 3.7 and 3.8 - try: - from typing_extensions import Annotated - - try: - from typing_extensions import _AnnotatedAlias - except ImportError: - # py3.6 typing_extensions doesn't have _AnnotatedAlias, but has AnnotatedMeta, which - # will satisfy our `isinstance` checks. - from typing_extensions import AnnotatedMeta as _AnnotatedAlias - except ImportError: - # Create mock Annotated/_AnnotatedAlias values distinct from `None`, which is a valid - # `get_origin` return value. - class _FalseMeta(type): - # Allow short circuiting with "Annotated[...] if Annotated else None". - def __bool__(cls): - return False - - # Give a nice suggestion for unguarded use - def __getitem__(cls, key): - raise RuntimeError( - 'Annotated is not supported in this python version, please `pip install typing-extensions`.' - ) - - class Annotated(metaclass=_FalseMeta): - pass - - class _AnnotatedAlias(metaclass=_FalseMeta): - pass - - # Our custom get_{args,origin} for <3.8 and the builtin 3.8 get_{args,origin} don't recognize - # typing_extensions.Annotated, so wrap them to short-circuit. We still want to use our wrapped - # get_origins defined above for non-Annotated data. - - def get_args(tp: Type[Any], _get_args=get_args) -> Type[Any]: if isinstance(tp, _AnnotatedAlias): return tp.__args__ + tp.__metadata__ - return _get_args(tp) - - def get_origin(tp: Type[Any], _get_origin=get_origin) -> Type[Any]: - if isinstance(tp, _AnnotatedAlias): - return Annotated - return _get_origin(tp) + # the fallback is needed for the same reasons as `get_origin` (see above) + return typing_get_args(tp) or getattr(tp, '__args__', ()) or generic_get_args(tp) if TYPE_CHECKING: