import sys from enum import Enum from typing import ( # type: ignore TYPE_CHECKING, AbstractSet, Any, ClassVar, Dict, Generator, List, Mapping, NewType, Optional, Sequence, Set, Tuple, Type, Union, _eval_type, cast, ) from typing_extensions import Annotated, Literal try: from typing import _TypingBase as typing_base # type: ignore except ImportError: from typing import _Final as typing_base # type: ignore try: from typing import GenericAlias # type: ignore except ImportError: # python < 3.9 does not have GenericAlias (list[int], tuple[str, ...] and so on) GenericAlias = () if sys.version_info < (3, 7): if TYPE_CHECKING: class ForwardRef: def __init__(self, arg: Any): pass def _eval_type(self, globalns: Any, localns: Any) -> Any: pass else: from typing import _ForwardRef as ForwardRef else: from typing import ForwardRef if sys.version_info < (3, 7): def evaluate_forwardref(type_: ForwardRef, globalns: Any, localns: Any) -> Any: return type_._eval_type(globalns, localns) elif sys.version_info < (3, 9): def evaluate_forwardref(type_: ForwardRef, globalns: Any, localns: Any) -> Any: return type_._evaluate(globalns, localns) else: def evaluate_forwardref(type_: ForwardRef, globalns: Any, localns: Any) -> Any: # Even though it is the right signature for python 3.9, mypy complains with # `error: Too many arguments for "_evaluate" of "ForwardRef"` hence the cast... return cast(Any, type_)._evaluate(globalns, localns, set()) if sys.version_info < (3, 7): from typing import Callable as Callable AnyCallable = Callable[..., Any] NoArgAnyCallable = Callable[[], Any] else: from collections.abc import Callable as Callable from typing import Callable as TypingCallable AnyCallable = TypingCallable[..., Any] NoArgAnyCallable = TypingCallable[[], Any] # Annotated[...] is implemented by returning an instance of one of these classes, depending on # python/typing_extensions version. AnnotatedTypeNames = {'AnnotatedMeta', '_AnnotatedAlias'} if sys.version_info < (3, 8): def get_origin(t: Type[Any]) -> Optional[Type[Any]]: if type(t).__name__ in AnnotatedTypeNames: return cast(Type[Any], Annotated) # mypy complains about _SpecialForm in py3.6 return getattr(t, '__origin__', None) else: from typing import get_origin as _typing_get_origin def get_origin(tp: Type[Any]) -> Type[Any]: """ We can't directly use `typing.get_origin` since we need a fallback to support custom generic classes like `ConstrainedList` 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 type(tp).__name__ in AnnotatedTypeNames: return cast(Type[Any], Annotated) # mypy complains about _SpecialForm return _typing_get_origin(tp) or getattr(tp, '__origin__', None) if sys.version_info < (3, 7): # noqa: C901 (ignore complexity) def get_args(t: Type[Any]) -> Tuple[Any, ...]: """Simplest get_args compatibility layer possible. The Python 3.6 typing module does not have `_GenericAlias` so this won't work for everything. In particular this will not support the `generics` module (we don't support generic models in python 3.6). """ if type(t).__name__ in AnnotatedTypeNames: return t.__args__ + t.__metadata__ return getattr(t, '__args__', ()) elif sys.version_info < (3, 8): # noqa: C901 from typing import _GenericAlias def get_args(t: Type[Any]) -> Tuple[Any, ...]: """Compatibility version of get_args for python 3.7. Mostly compatible with the python 3.8 `typing` module version and able to handle almost all use cases. """ if type(t).__name__ in AnnotatedTypeNames: 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: res = (list(res[:-1]), res[-1]) return res return getattr(t, '__args__', ()) else: from typing import get_args as _typing_get_args def _generic_get_args(tp: Type[Any]) -> Tuple[Any, ...]: """ In python 3.9, `typing.Dict`, `typing.List`, ... do have an empty `__args__` by default (instead of the generic ~T for example). In order to still support `Dict` for example and consider it as `Dict[Any, Any]`, we retrieve the `_nparams` value that tells us how many parameters it needs. """ if hasattr(tp, '_nparams'): return (Any,) * tp._nparams return () def get_args(tp: Type[Any]) -> Tuple[Any, ...]: """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 type(tp).__name__ in AnnotatedTypeNames: return tp.__args__ + tp.__metadata__ # 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: from .fields import ModelField TupleGenerator = Generator[Tuple[str, Any], None, None] DictStrAny = Dict[str, Any] DictAny = Dict[Any, Any] SetStr = Set[str] ListStr = List[str] IntStr = Union[int, str] AbstractSetIntStr = AbstractSet[IntStr] DictIntStrAny = Dict[IntStr, Any] MappingIntStrAny = Mapping[IntStr, Any] CallableGenerator = Generator[AnyCallable, None, None] ReprArgs = Sequence[Tuple[Optional[str], Any]] __all__ = ( 'ForwardRef', 'Callable', 'AnyCallable', 'NoArgAnyCallable', 'NoneType', 'NONE_TYPES', 'display_as_type', 'resolve_annotations', 'is_callable_type', 'is_literal_type', 'all_literal_values', 'is_namedtuple', 'is_typeddict', 'is_new_type', 'new_type_supertype', 'is_classvar', 'update_field_forward_refs', 'TupleGenerator', 'DictStrAny', 'DictAny', 'SetStr', 'ListStr', 'IntStr', 'AbstractSetIntStr', 'DictIntStrAny', 'CallableGenerator', 'ReprArgs', 'CallableGenerator', 'GenericAlias', 'get_args', 'get_origin', 'typing_base', ) NoneType = None.__class__ NONE_TYPES: Set[Any] = {None, NoneType, Literal[None]} def display_as_type(v: Type[Any]) -> str: if not isinstance(v, typing_base) and not isinstance(v, GenericAlias) and not isinstance(v, type): v = v.__class__ if isinstance(v, type) and issubclass(v, Enum): if issubclass(v, int): return 'int' elif issubclass(v, str): return 'str' else: return 'enum' if isinstance(v, GenericAlias): # Generic alias are constructs like `list[int]` return str(v).replace('typing.', '') try: return v.__name__ except AttributeError: # happens with typing objects return str(v).replace('typing.', '') def resolve_annotations(raw_annotations: Dict[str, Type[Any]], module_name: Optional[str]) -> Dict[str, Type[Any]]: """ Partially taken from typing.get_type_hints. Resolve string or ForwardRef annotations into type objects if possible. """ base_globals: Optional[Dict[str, Any]] = None if module_name: try: module = sys.modules[module_name] except KeyError: # happens occasionally, see https://github.com/samuelcolvin/pydantic/issues/2363 pass else: base_globals = module.__dict__ annotations = {} for name, value in raw_annotations.items(): if isinstance(value, str): if sys.version_info >= (3, 7): value = ForwardRef(value, is_argument=False) else: value = ForwardRef(value) try: value = _eval_type(value, base_globals, None) except NameError: # this is ok, it can be fixed with update_forward_refs pass annotations[name] = value return annotations def is_callable_type(type_: Type[Any]) -> bool: return type_ is Callable or get_origin(type_) is Callable if sys.version_info >= (3, 7): def is_literal_type(type_: Type[Any]) -> bool: return Literal is not None and get_origin(type_) is Literal def literal_values(type_: Type[Any]) -> Tuple[Any, ...]: return get_args(type_) else: def is_literal_type(type_: Type[Any]) -> bool: return Literal is not None and hasattr(type_, '__values__') and type_ == Literal[type_.__values__] def literal_values(type_: Type[Any]) -> Tuple[Any, ...]: return type_.__values__ def all_literal_values(type_: Type[Any]) -> Tuple[Any, ...]: """ This method is used to retrieve all Literal values as Literal can be used recursively (see https://www.python.org/dev/peps/pep-0586) e.g. `Literal[Literal[Literal[1, 2, 3], "foo"], 5, None]` """ if not is_literal_type(type_): return (type_,) values = literal_values(type_) return tuple(x for value in values for x in all_literal_values(value)) def is_namedtuple(type_: Type[Any]) -> bool: """ Check if a given class is a named tuple. It can be either a `typing.NamedTuple` or `collections.namedtuple` """ from .utils import lenient_issubclass return lenient_issubclass(type_, tuple) and hasattr(type_, '_fields') def is_typeddict(type_: Type[Any]) -> bool: """ Check if a given class is a typed dict (from `typing` or `typing_extensions`) In 3.10, there will be a public method (https://docs.python.org/3.10/library/typing.html#typing.is_typeddict) """ from .utils import lenient_issubclass return lenient_issubclass(type_, dict) and hasattr(type_, '__total__') test_type = NewType('test_type', str) def is_new_type(type_: Type[Any]) -> bool: """ Check whether type_ was created using typing.NewType """ return isinstance(type_, test_type.__class__) and hasattr(type_, '__supertype__') # type: ignore def new_type_supertype(type_: Type[Any]) -> Type[Any]: while hasattr(type_, '__supertype__'): type_ = type_.__supertype__ return type_ def _check_classvar(v: Optional[Type[Any]]) -> bool: if v is None: return False return v.__class__ == ClassVar.__class__ and (sys.version_info < (3, 7) or getattr(v, '_name', None) == 'ClassVar') def is_classvar(ann_type: Type[Any]) -> bool: return _check_classvar(ann_type) or _check_classvar(get_origin(ann_type)) def update_field_forward_refs(field: 'ModelField', globalns: Any, localns: Any) -> None: """ Try to update ForwardRefs on fields based on this ModelField, globalns and localns. """ if field.type_.__class__ == ForwardRef: field.type_ = evaluate_forwardref(field.type_, globalns, localns or None) field.prepare() if field.sub_fields: for sub_f in field.sub_fields: update_field_forward_refs(sub_f, globalns=globalns, localns=localns) def get_class(type_: Type[Any]) -> Union[None, bool, Type[Any]]: """ Tries to get the class of a Type[T] annotation. Returns True if Type is used without brackets. Otherwise returns None. """ try: origin = get_origin(type_) if origin is None: # Python 3.6 origin = type_ if issubclass(origin, Type): # type: ignore if not get_args(type_) or not isinstance(get_args(type_)[0], type): return True return get_args(type_)[0] except (AttributeError, TypeError): pass return None