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