Coverage for /pythoncovmergedfiles/medio/medio/src/pydantic/pydantic/_internal/_typing_extra.py: 63%
158 statements
« prev ^ index » next coverage.py v7.2.3, created at 2023-04-27 07:38 +0000
« prev ^ index » next coverage.py v7.2.3, created at 2023-04-27 07:38 +0000
1"""
2Logic for interacting with type annotations, mostly extensions, shims and hacks to wrap python's typing module.
3"""
4from __future__ import annotations as _annotations
6import dataclasses
7import sys
8import types
9import typing
10from collections.abc import Callable
11from functools import partial
12from types import GetSetDescriptorType
13from typing import TYPE_CHECKING, Any, ForwardRef
15from typing_extensions import Annotated, Final, Literal, TypeGuard, get_args, get_origin
17if TYPE_CHECKING:
18 from ._dataclasses import StandardDataclass
20try:
21 from typing import _TypingBase # type: ignore[attr-defined]
22except ImportError:
23 from typing import _Final as _TypingBase # type: ignore[attr-defined]
25typing_base = _TypingBase
28if sys.version_info < (3, 9):
29 # python < 3.9 does not have GenericAlias (list[int], tuple[str, ...] and so on)
30 TypingGenericAlias = ()
31else:
32 from typing import GenericAlias as TypingGenericAlias # type: ignore
35if sys.version_info < (3, 11):
36 from typing_extensions import NotRequired, Required
37else:
38 from typing import NotRequired, Required # noqa: F401
41if sys.version_info < (3, 10):
43 def origin_is_union(tp: type[Any] | None) -> bool:
44 return tp is typing.Union
46 WithArgsTypes = (TypingGenericAlias,)
48else:
50 def origin_is_union(tp: type[Any] | None) -> bool:
51 return tp is typing.Union or tp is types.UnionType # noqa: E721
53 WithArgsTypes = typing._GenericAlias, types.GenericAlias, types.UnionType # type: ignore[attr-defined]
56if sys.version_info < (3, 10):
57 NoneType = type(None)
58 EllipsisType = type(Ellipsis)
59else:
60 from types import EllipsisType as EllipsisType # noqa: F401
61 from types import NoneType as NoneType
64NONE_TYPES: tuple[Any, Any, Any] = (None, NoneType, Literal[None])
67TypeVarType = Any # since mypy doesn't allow the use of TypeVar as a type
70if sys.version_info < (3, 8):
71 # Even though this implementation is slower, we need it for python 3.7:
72 # In python 3.7 "Literal" is not a builtin type and uses a different
73 # mechanism.
74 # for this reason `Literal[None] is Literal[None]` evaluates to `False`,
75 # breaking the faster implementation used for the other python versions.
77 def is_none_type(type_: Any) -> bool:
78 return type_ in NONE_TYPES
80elif sys.version_info[:2] == (3, 8):
82 def is_none_type(type_: Any) -> bool:
83 for none_type in NONE_TYPES:
84 if type_ is none_type:
85 return True
86 # With python 3.8, specifically 3.8.10, Literal "is" checks are very flakey
87 # can change on very subtle changes like use of types in other modules,
88 # hopefully this check avoids that issue.
89 if is_literal_type(type_): # pragma: no cover
90 return all_literal_values(type_) == [None]
91 return False
93else:
95 def is_none_type(type_: Any) -> bool:
96 for none_type in NONE_TYPES:
97 if type_ is none_type:
98 return True
99 return False
102def is_callable_type(type_: type[Any]) -> bool:
103 return type_ is Callable or get_origin(type_) is Callable
106def is_literal_type(type_: type[Any]) -> bool:
107 return Literal is not None and get_origin(type_) is Literal
110def literal_values(type_: type[Any]) -> tuple[Any, ...]:
111 return get_args(type_)
114def all_literal_values(type_: type[Any]) -> list[Any]:
115 """
116 This method is used to retrieve all Literal values as
117 Literal can be used recursively (see https://www.python.org/dev/peps/pep-0586)
118 e.g. `Literal[Literal[Literal[1, 2, 3], "foo"], 5, None]`
119 """
120 if not is_literal_type(type_):
121 return [type_]
123 values = literal_values(type_)
124 return list(x for value in values for x in all_literal_values(value))
127def is_annotated(ann_type: Any) -> bool:
128 from ._utils import lenient_issubclass
130 origin = get_origin(ann_type)
131 return origin is not None and lenient_issubclass(origin, Annotated)
134def is_namedtuple(type_: type[Any]) -> bool:
135 """
136 Check if a given class is a named tuple.
137 It can be either a `typing.NamedTuple` or `collections.namedtuple`
138 """
139 from ._utils import lenient_issubclass
141 return lenient_issubclass(type_, tuple) and hasattr(type_, '_fields')
144test_new_type = typing.NewType('test_new_type', str)
147def is_new_type(type_: type[Any]) -> bool:
148 """
149 Check whether type_ was created using typing.NewType.
151 Can't use isinstance because it fails <3.10.
152 """
153 return isinstance(type_, test_new_type.__class__) and hasattr(type_, '__supertype__') # type: ignore[arg-type]
156def _check_classvar(v: type[Any] | None) -> bool:
157 if v is None:
158 return False
160 return v.__class__ == typing.ClassVar.__class__ and getattr(v, '_name', None) == 'ClassVar'
163def is_classvar(ann_type: type[Any]) -> bool:
164 if _check_classvar(ann_type) or _check_classvar(get_origin(ann_type)):
165 return True
167 # this is an ugly workaround for class vars that contain forward references and are therefore themselves
168 # forward references, see #3679
169 if ann_type.__class__ == typing.ForwardRef and ann_type.__forward_arg__.startswith('ClassVar['): # type: ignore
170 return True
172 return False
175def _check_finalvar(v: type[Any] | None) -> bool:
176 """
177 Check if a given type is a `typing.Final` type.
178 """
179 if v is None:
180 return False
182 return v.__class__ == Final.__class__ and (sys.version_info < (3, 8) or getattr(v, '_name', None) == 'Final')
185def is_finalvar(ann_type: Any) -> bool:
186 return _check_finalvar(ann_type) or _check_finalvar(get_origin(ann_type))
189def parent_frame_namespace(*, parent_depth: int = 2) -> dict[str, Any] | None:
190 """
191 We allow use of items in parent namespace to get around the issue with `get_type_hints` only looking in the
192 global module namespace. See https://github.com/pydantic/pydantic/issues/2678#issuecomment-1008139014 -> Scope
193 and suggestion at the end of the next comment by @gvanrossum.
195 WARNING 1: it matters exactly where this is called. By default, this function will build a namespace from the
196 parent of where it is called.
198 WARNING 2: this only looks in the parent namespace, not other parents since (AFAIK) there's no way to collect a
199 dict of exactly what's in scope. Using `f_back` would work sometimes but would be very wrong and confusing in many
200 other cases. See https://discuss.python.org/t/is-there-a-way-to-access-parent-nested-namespaces/20659.
201 """
202 frame = sys._getframe(parent_depth)
203 # if f_back is None, it's the global module namespace and we don't need to include it here
204 if frame.f_back is None:
205 return None
206 else:
207 return frame.f_locals
210def add_module_globals(obj: Any, globalns: dict[str, Any] | None = None) -> dict[str, Any]:
211 module_name = getattr(obj, '__module__', None)
212 if module_name:
213 try:
214 module_globalns = sys.modules[module_name].__dict__
215 except KeyError:
216 # happens occasionally, see https://github.com/pydantic/pydantic/issues/2363
217 pass
218 else:
219 if globalns:
220 return {**module_globalns, **globalns}
221 else:
222 # copy module globals to make sure it can't be updated later
223 return module_globalns.copy()
225 return globalns or {}
228def get_cls_types_namespace(cls: type[Any], parent_namespace: dict[str, Any] | None = None) -> dict[str, Any]:
229 ns = add_module_globals(cls, parent_namespace)
230 ns[cls.__name__] = cls
231 return ns
234def get_cls_type_hints_lenient(obj: Any, globalns: dict[str, Any] | None = None) -> dict[str, Any]:
235 """
236 Collect annotations from a class, including those from parent classes.
238 Unlike `typing.get_type_hints`, this function will not error if a forward reference is not resolvable.
239 """
240 # TODO: Try handling typevars_map here
241 hints = {}
242 for base in reversed(obj.__mro__):
243 ann = base.__dict__.get('__annotations__')
244 localns = dict(vars(base))
245 if ann is not None and ann is not GetSetDescriptorType:
246 for name, value in ann.items():
247 hints[name] = eval_type_lenient(value, globalns, localns)
248 return hints
251def eval_type_lenient(value: Any, globalns: dict[str, Any] | None, localns: dict[str, Any] | None) -> Any:
252 """
253 Behaves like typing._eval_type, except it won't raise an error if a forward reference can't be resolved.
254 """
255 if value is None:
256 value = NoneType
257 elif isinstance(value, str):
258 value = _make_forward_ref(value, is_argument=False, is_class=True)
260 try:
261 return typing._eval_type(value, globalns, localns) # type: ignore
262 except NameError:
263 # the point of this function is to be tolerant to this case
264 return value
267def get_function_type_hints(function: Callable[..., Any]) -> dict[str, Any]:
268 """
269 Like `typing.get_type_hints`, but doesn't convert `X` to `Optional[X]` if the default value is `None`, also
270 copes with `partial`.
271 """
273 if isinstance(function, partial):
274 annotations = function.func.__annotations__
275 else:
276 annotations = function.__annotations__
278 globalns = add_module_globals(function)
279 type_hints = {}
280 for name, value in annotations.items():
281 if value is None:
282 value = NoneType
283 elif isinstance(value, str):
284 value = _make_forward_ref(value)
286 type_hints[name] = typing._eval_type(value, globalns, None) # type: ignore
288 return type_hints
291if sys.version_info < (3, 9):
293 def _make_forward_ref(
294 arg: Any,
295 is_argument: bool = True,
296 *,
297 is_class: bool = False,
298 ) -> typing.ForwardRef:
299 """
300 Wrapper for ForwardRef that accounts for the `is_class` argument missing in older versions.
301 The `module` argument is omitted as it breaks <3.9 and isn't used in the calls below.
303 See https://github.com/python/cpython/pull/28560 for some background
305 Implemented as EAFP with memory.
306 """
307 global _make_forward_ref
308 try:
309 res = typing.ForwardRef(arg, is_argument, is_class=is_class) # type: ignore
310 _make_forward_ref = typing.ForwardRef # type: ignore
311 return res
312 except TypeError:
313 return typing.ForwardRef(arg, is_argument)
315else:
316 _make_forward_ref = typing.ForwardRef
319if sys.version_info >= (3, 10):
320 get_type_hints = typing.get_type_hints
322else:
323 """
324 For older versions of python, we have a custom implementation of `get_type_hints` which is a close as possible to
325 the implementation in CPython 3.10.8.
326 """
328 @typing.no_type_check
329 def get_type_hints( # noqa: C901
330 obj: Any,
331 globalns: dict[str, Any] | None = None,
332 localns: dict[str, Any] | None = None,
333 include_extras: bool = False,
334 ) -> dict[str, Any]: # pragma: no cover
335 """
336 Taken verbatim from python 3.10.8 unchanged, except:
337 * type annotations of the function definition above.
338 * prefixing `typing.` where appropriate
339 * Use `_make_forward_ref` instead of `typing.ForwardRef` to handle the `is_class` argument
341 https://github.com/python/cpython/blob/aaaf5174241496afca7ce4d4584570190ff972fe/Lib/typing.py#L1773-L1875
343 DO NOT CHANGE THIS METHOD UNLESS ABSOLUTELY NECESSARY.
344 ======================================================
346 Return type hints for an object.
348 This is often the same as obj.__annotations__, but it handles
349 forward references encoded as string literals, adds Optional[t] if a
350 default value equal to None is set and recursively replaces all
351 'Annotated[T, ...]' with 'T' (unless 'include_extras=True').
353 The argument may be a module, class, method, or function. The annotations
354 are returned as a dictionary. For classes, annotations include also
355 inherited members.
357 TypeError is raised if the argument is not of a type that can contain
358 annotations, and an empty dictionary is returned if no annotations are
359 present.
361 BEWARE -- the behavior of globalns and localns is counterintuitive
362 (unless you are familiar with how eval() and exec() work). The
363 search order is locals first, then globals.
365 - If no dict arguments are passed, an attempt is made to use the
366 globals from obj (or the respective module's globals for classes),
367 and these are also used as the locals. If the object does not appear
368 to have globals, an empty dictionary is used. For classes, the search
369 order is globals first then locals.
371 - If one dict argument is passed, it is used for both globals and
372 locals.
374 - If two dict arguments are passed, they specify globals and
375 locals, respectively.
376 """
378 if getattr(obj, '__no_type_check__', None):
379 return {}
380 # Classes require a special treatment.
381 if isinstance(obj, type):
382 hints = {}
383 for base in reversed(obj.__mro__):
384 if globalns is None:
385 base_globals = getattr(sys.modules.get(base.__module__, None), '__dict__', {})
386 else:
387 base_globals = globalns
388 ann = base.__dict__.get('__annotations__', {})
389 if isinstance(ann, types.GetSetDescriptorType):
390 ann = {}
391 base_locals = dict(vars(base)) if localns is None else localns
392 if localns is None and globalns is None:
393 # This is surprising, but required. Before Python 3.10,
394 # get_type_hints only evaluated the globalns of
395 # a class. To maintain backwards compatibility, we reverse
396 # the globalns and localns order so that eval() looks into
397 # *base_globals* first rather than *base_locals*.
398 # This only affects ForwardRefs.
399 base_globals, base_locals = base_locals, base_globals
400 for name, value in ann.items():
401 if value is None:
402 value = type(None)
403 if isinstance(value, str):
404 value = _make_forward_ref(value, is_argument=False, is_class=True)
406 value = typing._eval_type(value, base_globals, base_locals) # type: ignore
407 hints[name] = value
408 return (
409 hints if include_extras else {k: typing._strip_annotations(t) for k, t in hints.items()} # type: ignore
410 )
412 if globalns is None:
413 if isinstance(obj, types.ModuleType):
414 globalns = obj.__dict__
415 else:
416 nsobj = obj
417 # Find globalns for the unwrapped object.
418 while hasattr(nsobj, '__wrapped__'):
419 nsobj = nsobj.__wrapped__
420 globalns = getattr(nsobj, '__globals__', {})
421 if localns is None:
422 localns = globalns
423 elif localns is None:
424 localns = globalns
425 hints = getattr(obj, '__annotations__', None)
426 if hints is None:
427 # Return empty annotations for something that _could_ have them.
428 if isinstance(obj, typing._allowed_types): # type: ignore
429 return {}
430 else:
431 raise TypeError('{!r} is not a module, class, method, ' 'or function.'.format(obj))
432 defaults = typing._get_defaults(obj) # type: ignore
433 hints = dict(hints)
434 for name, value in hints.items():
435 if value is None:
436 value = type(None)
437 if isinstance(value, str):
438 # class-level forward refs were handled above, this must be either
439 # a module-level annotation or a function argument annotation
441 value = _make_forward_ref(
442 value,
443 is_argument=not isinstance(obj, types.ModuleType),
444 is_class=False,
445 )
446 value = typing._eval_type(value, globalns, localns) # type: ignore
447 if name in defaults and defaults[name] is None:
448 value = typing.Optional[value]
449 hints[name] = value
450 return hints if include_extras else {k: typing._strip_annotations(t) for k, t in hints.items()} # type: ignore
453if sys.version_info < (3, 9):
455 def evaluate_fwd_ref(
456 ref: ForwardRef, globalns: dict[str, Any] | None = None, localns: dict[str, Any] | None = None
457 ) -> Any:
458 return ref._evaluate(globalns=globalns, localns=localns)
460else:
462 def evaluate_fwd_ref(
463 ref: ForwardRef, globalns: dict[str, Any] | None = None, localns: dict[str, Any] | None = None
464 ) -> Any:
465 return ref._evaluate(globalns=globalns, localns=localns, recursive_guard=frozenset())
468def is_dataclass(_cls: type[Any]) -> TypeGuard[type[StandardDataclass]]:
469 # The dataclasses.is_dataclass function doesn't seem to provide TypeGuard functionality,
470 # so I created this convenience function
471 return dataclasses.is_dataclass(_cls)