1import sys
2import typing
3from collections.abc import Callable
4from os import PathLike
5from typing import ( # type: ignore
6 TYPE_CHECKING,
7 AbstractSet,
8 Any,
9 Callable as TypingCallable,
10 ClassVar,
11 Dict,
12 ForwardRef,
13 Generator,
14 Iterable,
15 List,
16 Mapping,
17 NewType,
18 Optional,
19 Sequence,
20 Set,
21 Tuple,
22 Type,
23 TypeVar,
24 Union,
25 _eval_type,
26 cast,
27 get_type_hints,
28)
29
30from typing_extensions import (
31 Annotated,
32 Final,
33 Literal,
34 NotRequired as TypedDictNotRequired,
35 Required as TypedDictRequired,
36)
37
38try:
39 from typing import _TypingBase as typing_base # type: ignore
40except ImportError:
41 from typing import _Final as typing_base # type: ignore
42
43try:
44 from typing import GenericAlias as TypingGenericAlias # type: ignore
45except ImportError:
46 # python < 3.9 does not have GenericAlias (list[int], tuple[str, ...] and so on)
47 TypingGenericAlias = ()
48
49try:
50 from types import UnionType as TypesUnionType # type: ignore
51except ImportError:
52 # python < 3.10 does not have UnionType (str | int, byte | bool and so on)
53 TypesUnionType = ()
54
55
56if sys.version_info < (3, 9):
57
58 def evaluate_forwardref(type_: ForwardRef, globalns: Any, localns: Any) -> Any:
59 return type_._evaluate(globalns, localns)
60
61elif sys.version_info < (3, 12, 4):
62
63 def evaluate_forwardref(type_: ForwardRef, globalns: Any, localns: Any) -> Any:
64 # Even though it is the right signature for python 3.9, mypy complains with
65 # `error: Too many arguments for "_evaluate" of "ForwardRef"` hence the cast...
66 # Python 3.13/3.12.4+ made `recursive_guard` a kwarg, so name it explicitly to avoid:
67 # TypeError: ForwardRef._evaluate() missing 1 required keyword-only argument: 'recursive_guard'
68 return cast(Any, type_)._evaluate(globalns, localns, recursive_guard=set())
69
70else:
71
72 def evaluate_forwardref(type_: ForwardRef, globalns: Any, localns: Any) -> Any:
73 # Pydantic 1.x will not support PEP 695 syntax, but provide `type_params` to avoid
74 # warnings:
75 return cast(Any, type_)._evaluate(globalns, localns, type_params=(), recursive_guard=set())
76
77
78if sys.version_info < (3, 9):
79 # Ensure we always get all the whole `Annotated` hint, not just the annotated type.
80 # For 3.7 to 3.8, `get_type_hints` doesn't recognize `typing_extensions.Annotated`,
81 # so it already returns the full annotation
82 get_all_type_hints = get_type_hints
83
84else:
85
86 def get_all_type_hints(obj: Any, globalns: Any = None, localns: Any = None) -> Any:
87 return get_type_hints(obj, globalns, localns, include_extras=True)
88
89
90_T = TypeVar('_T')
91
92AnyCallable = TypingCallable[..., Any]
93NoArgAnyCallable = TypingCallable[[], Any]
94
95# workaround for https://github.com/python/mypy/issues/9496
96AnyArgTCallable = TypingCallable[..., _T]
97
98
99# Annotated[...] is implemented by returning an instance of one of these classes, depending on
100# python/typing_extensions version.
101AnnotatedTypeNames = {'AnnotatedMeta', '_AnnotatedAlias'}
102
103
104LITERAL_TYPES: Set[Any] = {Literal}
105if hasattr(typing, 'Literal'):
106 LITERAL_TYPES.add(typing.Literal)
107
108
109if sys.version_info < (3, 8):
110
111 def get_origin(t: Type[Any]) -> Optional[Type[Any]]:
112 if type(t).__name__ in AnnotatedTypeNames:
113 # weirdly this is a runtime requirement, as well as for mypy
114 return cast(Type[Any], Annotated)
115 return getattr(t, '__origin__', None)
116
117else:
118 from typing import get_origin as _typing_get_origin
119
120 def get_origin(tp: Type[Any]) -> Optional[Type[Any]]:
121 """
122 We can't directly use `typing.get_origin` since we need a fallback to support
123 custom generic classes like `ConstrainedList`
124 It should be useless once https://github.com/cython/cython/issues/3537 is
125 solved and https://github.com/pydantic/pydantic/pull/1753 is merged.
126 """
127 if type(tp).__name__ in AnnotatedTypeNames:
128 return cast(Type[Any], Annotated) # mypy complains about _SpecialForm
129 return _typing_get_origin(tp) or getattr(tp, '__origin__', None)
130
131
132if sys.version_info < (3, 8):
133 from typing import _GenericAlias
134
135 def get_args(t: Type[Any]) -> Tuple[Any, ...]:
136 """Compatibility version of get_args for python 3.7.
137
138 Mostly compatible with the python 3.8 `typing` module version
139 and able to handle almost all use cases.
140 """
141 if type(t).__name__ in AnnotatedTypeNames:
142 return t.__args__ + t.__metadata__
143 if isinstance(t, _GenericAlias):
144 res = t.__args__
145 if t.__origin__ is Callable and res and res[0] is not Ellipsis:
146 res = (list(res[:-1]), res[-1])
147 return res
148 return getattr(t, '__args__', ())
149
150else:
151 from typing import get_args as _typing_get_args
152
153 def _generic_get_args(tp: Type[Any]) -> Tuple[Any, ...]:
154 """
155 In python 3.9, `typing.Dict`, `typing.List`, ...
156 do have an empty `__args__` by default (instead of the generic ~T for example).
157 In order to still support `Dict` for example and consider it as `Dict[Any, Any]`,
158 we retrieve the `_nparams` value that tells us how many parameters it needs.
159 """
160 if hasattr(tp, '_nparams'):
161 return (Any,) * tp._nparams
162 # Special case for `tuple[()]`, which used to return ((),) with `typing.Tuple`
163 # in python 3.10- but now returns () for `tuple` and `Tuple`.
164 # This will probably be clarified in pydantic v2
165 try:
166 if tp == Tuple[()] or sys.version_info >= (3, 9) and tp == tuple[()]: # type: ignore[misc]
167 return ((),)
168 # there is a TypeError when compiled with cython
169 except TypeError: # pragma: no cover
170 pass
171 return ()
172
173 def get_args(tp: Type[Any]) -> Tuple[Any, ...]:
174 """Get type arguments with all substitutions performed.
175
176 For unions, basic simplifications used by Union constructor are performed.
177 Examples::
178 get_args(Dict[str, int]) == (str, int)
179 get_args(int) == ()
180 get_args(Union[int, Union[T, int], str][int]) == (int, str)
181 get_args(Union[int, Tuple[T, int]][str]) == (int, Tuple[str, int])
182 get_args(Callable[[], T][int]) == ([], int)
183 """
184 if type(tp).__name__ in AnnotatedTypeNames:
185 return tp.__args__ + tp.__metadata__
186 # the fallback is needed for the same reasons as `get_origin` (see above)
187 return _typing_get_args(tp) or getattr(tp, '__args__', ()) or _generic_get_args(tp)
188
189
190if sys.version_info < (3, 9):
191
192 def convert_generics(tp: Type[Any]) -> Type[Any]:
193 """Python 3.9 and older only supports generics from `typing` module.
194 They convert strings to ForwardRef automatically.
195
196 Examples::
197 typing.List['Hero'] == typing.List[ForwardRef('Hero')]
198 """
199 return tp
200
201else:
202 from typing import _UnionGenericAlias # type: ignore
203
204 from typing_extensions import _AnnotatedAlias
205
206 def convert_generics(tp: Type[Any]) -> Type[Any]:
207 """
208 Recursively searches for `str` type hints and replaces them with ForwardRef.
209
210 Examples::
211 convert_generics(list['Hero']) == list[ForwardRef('Hero')]
212 convert_generics(dict['Hero', 'Team']) == dict[ForwardRef('Hero'), ForwardRef('Team')]
213 convert_generics(typing.Dict['Hero', 'Team']) == typing.Dict[ForwardRef('Hero'), ForwardRef('Team')]
214 convert_generics(list[str | 'Hero'] | int) == list[str | ForwardRef('Hero')] | int
215 """
216 origin = get_origin(tp)
217 if not origin or not hasattr(tp, '__args__'):
218 return tp
219
220 args = get_args(tp)
221
222 # typing.Annotated needs special treatment
223 if origin is Annotated:
224 return _AnnotatedAlias(convert_generics(args[0]), args[1:])
225
226 # recursively replace `str` instances inside of `GenericAlias` with `ForwardRef(arg)`
227 converted = tuple(
228 ForwardRef(arg) if isinstance(arg, str) and isinstance(tp, TypingGenericAlias) else convert_generics(arg)
229 for arg in args
230 )
231
232 if converted == args:
233 return tp
234 elif isinstance(tp, TypingGenericAlias):
235 return TypingGenericAlias(origin, converted)
236 elif isinstance(tp, TypesUnionType):
237 # recreate types.UnionType (PEP604, Python >= 3.10)
238 return _UnionGenericAlias(origin, converted)
239 else:
240 try:
241 setattr(tp, '__args__', converted)
242 except AttributeError:
243 pass
244 return tp
245
246
247if sys.version_info < (3, 10):
248
249 def is_union(tp: Optional[Type[Any]]) -> bool:
250 return tp is Union
251
252 WithArgsTypes = (TypingGenericAlias,)
253
254else:
255 import types
256 import typing
257
258 def is_union(tp: Optional[Type[Any]]) -> bool:
259 return tp is Union or tp is types.UnionType # noqa: E721
260
261 WithArgsTypes = (typing._GenericAlias, types.GenericAlias, types.UnionType)
262
263
264StrPath = Union[str, PathLike]
265
266
267if TYPE_CHECKING:
268 from pydantic.v1.fields import ModelField
269
270 TupleGenerator = Generator[Tuple[str, Any], None, None]
271 DictStrAny = Dict[str, Any]
272 DictAny = Dict[Any, Any]
273 SetStr = Set[str]
274 ListStr = List[str]
275 IntStr = Union[int, str]
276 AbstractSetIntStr = AbstractSet[IntStr]
277 DictIntStrAny = Dict[IntStr, Any]
278 MappingIntStrAny = Mapping[IntStr, Any]
279 CallableGenerator = Generator[AnyCallable, None, None]
280 ReprArgs = Sequence[Tuple[Optional[str], Any]]
281
282 MYPY = False
283 if MYPY:
284 AnyClassMethod = classmethod[Any]
285 else:
286 # classmethod[TargetType, CallableParamSpecType, CallableReturnType]
287 AnyClassMethod = classmethod[Any, Any, Any]
288
289__all__ = (
290 'AnyCallable',
291 'NoArgAnyCallable',
292 'NoneType',
293 'is_none_type',
294 'display_as_type',
295 'resolve_annotations',
296 'is_callable_type',
297 'is_literal_type',
298 'all_literal_values',
299 'is_namedtuple',
300 'is_typeddict',
301 'is_typeddict_special',
302 'is_new_type',
303 'new_type_supertype',
304 'is_classvar',
305 'is_finalvar',
306 'update_field_forward_refs',
307 'update_model_forward_refs',
308 'TupleGenerator',
309 'DictStrAny',
310 'DictAny',
311 'SetStr',
312 'ListStr',
313 'IntStr',
314 'AbstractSetIntStr',
315 'DictIntStrAny',
316 'CallableGenerator',
317 'ReprArgs',
318 'AnyClassMethod',
319 'CallableGenerator',
320 'WithArgsTypes',
321 'get_args',
322 'get_origin',
323 'get_sub_types',
324 'typing_base',
325 'get_all_type_hints',
326 'is_union',
327 'StrPath',
328 'MappingIntStrAny',
329)
330
331
332NoneType = None.__class__
333
334
335NONE_TYPES: Tuple[Any, Any, Any] = (None, NoneType, Literal[None])
336
337
338if sys.version_info < (3, 8):
339 # Even though this implementation is slower, we need it for python 3.7:
340 # In python 3.7 "Literal" is not a builtin type and uses a different
341 # mechanism.
342 # for this reason `Literal[None] is Literal[None]` evaluates to `False`,
343 # breaking the faster implementation used for the other python versions.
344
345 def is_none_type(type_: Any) -> bool:
346 return type_ in NONE_TYPES
347
348elif sys.version_info[:2] == (3, 8):
349
350 def is_none_type(type_: Any) -> bool:
351 for none_type in NONE_TYPES:
352 if type_ is none_type:
353 return True
354 # With python 3.8, specifically 3.8.10, Literal "is" check sare very flakey
355 # can change on very subtle changes like use of types in other modules,
356 # hopefully this check avoids that issue.
357 if is_literal_type(type_): # pragma: no cover
358 return all_literal_values(type_) == (None,)
359 return False
360
361else:
362
363 def is_none_type(type_: Any) -> bool:
364 return type_ in NONE_TYPES
365
366
367def display_as_type(v: Type[Any]) -> str:
368 if not isinstance(v, typing_base) and not isinstance(v, WithArgsTypes) and not isinstance(v, type):
369 v = v.__class__
370
371 if is_union(get_origin(v)):
372 return f'Union[{", ".join(map(display_as_type, get_args(v)))}]'
373
374 if isinstance(v, WithArgsTypes):
375 # Generic alias are constructs like `list[int]`
376 return str(v).replace('typing.', '')
377
378 try:
379 return v.__name__
380 except AttributeError:
381 # happens with typing objects
382 return str(v).replace('typing.', '')
383
384
385def resolve_annotations(raw_annotations: Dict[str, Type[Any]], module_name: Optional[str]) -> Dict[str, Type[Any]]:
386 """
387 Partially taken from typing.get_type_hints.
388
389 Resolve string or ForwardRef annotations into type objects if possible.
390 """
391 base_globals: Optional[Dict[str, Any]] = None
392 if module_name:
393 try:
394 module = sys.modules[module_name]
395 except KeyError:
396 # happens occasionally, see https://github.com/pydantic/pydantic/issues/2363
397 pass
398 else:
399 base_globals = module.__dict__
400
401 annotations = {}
402 for name, value in raw_annotations.items():
403 if isinstance(value, str):
404 if (3, 10) > sys.version_info >= (3, 9, 8) or sys.version_info >= (3, 10, 1):
405 value = ForwardRef(value, is_argument=False, is_class=True)
406 else:
407 value = ForwardRef(value, is_argument=False)
408 try:
409 if sys.version_info >= (3, 13):
410 value = _eval_type(value, base_globals, None, type_params=())
411 else:
412 value = _eval_type(value, base_globals, None)
413 except NameError:
414 # this is ok, it can be fixed with update_forward_refs
415 pass
416 annotations[name] = value
417 return annotations
418
419
420def is_callable_type(type_: Type[Any]) -> bool:
421 return type_ is Callable or get_origin(type_) is Callable
422
423
424def is_literal_type(type_: Type[Any]) -> bool:
425 return Literal is not None and get_origin(type_) in LITERAL_TYPES
426
427
428def literal_values(type_: Type[Any]) -> Tuple[Any, ...]:
429 return get_args(type_)
430
431
432def all_literal_values(type_: Type[Any]) -> Tuple[Any, ...]:
433 """
434 This method is used to retrieve all Literal values as
435 Literal can be used recursively (see https://www.python.org/dev/peps/pep-0586)
436 e.g. `Literal[Literal[Literal[1, 2, 3], "foo"], 5, None]`
437 """
438 if not is_literal_type(type_):
439 return (type_,)
440
441 values = literal_values(type_)
442 return tuple(x for value in values for x in all_literal_values(value))
443
444
445def is_namedtuple(type_: Type[Any]) -> bool:
446 """
447 Check if a given class is a named tuple.
448 It can be either a `typing.NamedTuple` or `collections.namedtuple`
449 """
450 from pydantic.v1.utils import lenient_issubclass
451
452 return lenient_issubclass(type_, tuple) and hasattr(type_, '_fields')
453
454
455def is_typeddict(type_: Type[Any]) -> bool:
456 """
457 Check if a given class is a typed dict (from `typing` or `typing_extensions`)
458 In 3.10, there will be a public method (https://docs.python.org/3.10/library/typing.html#typing.is_typeddict)
459 """
460 from pydantic.v1.utils import lenient_issubclass
461
462 return lenient_issubclass(type_, dict) and hasattr(type_, '__total__')
463
464
465def _check_typeddict_special(type_: Any) -> bool:
466 return type_ is TypedDictRequired or type_ is TypedDictNotRequired
467
468
469def is_typeddict_special(type_: Any) -> bool:
470 """
471 Check if type is a TypedDict special form (Required or NotRequired).
472 """
473 return _check_typeddict_special(type_) or _check_typeddict_special(get_origin(type_))
474
475
476test_type = NewType('test_type', str)
477
478
479def is_new_type(type_: Type[Any]) -> bool:
480 """
481 Check whether type_ was created using typing.NewType
482 """
483 return isinstance(type_, test_type.__class__) and hasattr(type_, '__supertype__') # type: ignore
484
485
486def new_type_supertype(type_: Type[Any]) -> Type[Any]:
487 while hasattr(type_, '__supertype__'):
488 type_ = type_.__supertype__
489 return type_
490
491
492def _check_classvar(v: Optional[Type[Any]]) -> bool:
493 if v is None:
494 return False
495
496 return v.__class__ == ClassVar.__class__ and getattr(v, '_name', None) == 'ClassVar'
497
498
499def _check_finalvar(v: Optional[Type[Any]]) -> bool:
500 """
501 Check if a given type is a `typing.Final` type.
502 """
503 if v is None:
504 return False
505
506 return v.__class__ == Final.__class__ and (sys.version_info < (3, 8) or getattr(v, '_name', None) == 'Final')
507
508
509def is_classvar(ann_type: Type[Any]) -> bool:
510 if _check_classvar(ann_type) or _check_classvar(get_origin(ann_type)):
511 return True
512
513 # this is an ugly workaround for class vars that contain forward references and are therefore themselves
514 # forward references, see #3679
515 if ann_type.__class__ == ForwardRef and ann_type.__forward_arg__.startswith('ClassVar['):
516 return True
517
518 return False
519
520
521def is_finalvar(ann_type: Type[Any]) -> bool:
522 return _check_finalvar(ann_type) or _check_finalvar(get_origin(ann_type))
523
524
525def update_field_forward_refs(field: 'ModelField', globalns: Any, localns: Any) -> None:
526 """
527 Try to update ForwardRefs on fields based on this ModelField, globalns and localns.
528 """
529 prepare = False
530 if field.type_.__class__ == ForwardRef:
531 prepare = True
532 field.type_ = evaluate_forwardref(field.type_, globalns, localns or None)
533 if field.outer_type_.__class__ == ForwardRef:
534 prepare = True
535 field.outer_type_ = evaluate_forwardref(field.outer_type_, globalns, localns or None)
536 if prepare:
537 field.prepare()
538
539 if field.sub_fields:
540 for sub_f in field.sub_fields:
541 update_field_forward_refs(sub_f, globalns=globalns, localns=localns)
542
543 if field.discriminator_key is not None:
544 field.prepare_discriminated_union_sub_fields()
545
546
547def update_model_forward_refs(
548 model: Type[Any],
549 fields: Iterable['ModelField'],
550 json_encoders: Dict[Union[Type[Any], str, ForwardRef], AnyCallable],
551 localns: 'DictStrAny',
552 exc_to_suppress: Tuple[Type[BaseException], ...] = (),
553) -> None:
554 """
555 Try to update model fields ForwardRefs based on model and localns.
556 """
557 if model.__module__ in sys.modules:
558 globalns = sys.modules[model.__module__].__dict__.copy()
559 else:
560 globalns = {}
561
562 globalns.setdefault(model.__name__, model)
563
564 for f in fields:
565 try:
566 update_field_forward_refs(f, globalns=globalns, localns=localns)
567 except exc_to_suppress:
568 pass
569
570 for key in set(json_encoders.keys()):
571 if isinstance(key, str):
572 fr: ForwardRef = ForwardRef(key)
573 elif isinstance(key, ForwardRef):
574 fr = key
575 else:
576 continue
577
578 try:
579 new_key = evaluate_forwardref(fr, globalns, localns or None)
580 except exc_to_suppress: # pragma: no cover
581 continue
582
583 json_encoders[new_key] = json_encoders.pop(key)
584
585
586def get_class(type_: Type[Any]) -> Union[None, bool, Type[Any]]:
587 """
588 Tries to get the class of a Type[T] annotation. Returns True if Type is used
589 without brackets. Otherwise returns None.
590 """
591 if type_ is type:
592 return True
593
594 if get_origin(type_) is None:
595 return None
596
597 args = get_args(type_)
598 if not args or not isinstance(args[0], type):
599 return True
600 else:
601 return args[0]
602
603
604def get_sub_types(tp: Any) -> List[Any]:
605 """
606 Return all the types that are allowed by type `tp`
607 `tp` can be a `Union` of allowed types or an `Annotated` type
608 """
609 origin = get_origin(tp)
610 if origin is Annotated:
611 return get_sub_types(get_args(tp)[0])
612 elif is_union(origin):
613 return [x for t in get_args(tp) for x in get_sub_types(t)]
614 else:
615 return [tp]