_generics.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521
  1. from __future__ import annotations
  2. import sys
  3. import types
  4. import typing
  5. from collections import ChainMap
  6. from contextlib import contextmanager
  7. from contextvars import ContextVar
  8. from types import prepare_class
  9. from typing import TYPE_CHECKING, Any, Iterator, List, Mapping, MutableMapping, Tuple, TypeVar
  10. from weakref import WeakValueDictionary
  11. import typing_extensions
  12. from ._core_utils import get_type_ref
  13. from ._forward_ref import PydanticRecursiveRef
  14. from ._typing_extra import TypeVarType, typing_base
  15. from ._utils import all_identical, is_model_class
  16. if sys.version_info >= (3, 10):
  17. from typing import _UnionGenericAlias # type: ignore[attr-defined]
  18. if TYPE_CHECKING:
  19. from ..main import BaseModel
  20. GenericTypesCacheKey = Tuple[Any, Any, Tuple[Any, ...]]
  21. # Note: We want to remove LimitedDict, but to do this, we'd need to improve the handling of generics caching.
  22. # Right now, to handle recursive generics, we some types must remain cached for brief periods without references.
  23. # By chaining the WeakValuesDict with a LimitedDict, we have a way to retain caching for all types with references,
  24. # while also retaining a limited number of types even without references. This is generally enough to build
  25. # specific recursive generic models without losing required items out of the cache.
  26. KT = TypeVar('KT')
  27. VT = TypeVar('VT')
  28. _LIMITED_DICT_SIZE = 100
  29. if TYPE_CHECKING:
  30. class LimitedDict(dict, MutableMapping[KT, VT]):
  31. def __init__(self, size_limit: int = _LIMITED_DICT_SIZE):
  32. ...
  33. else:
  34. class LimitedDict(dict):
  35. """Limit the size/length of a dict used for caching to avoid unlimited increase in memory usage.
  36. Since the dict is ordered, and we always remove elements from the beginning, this is effectively a FIFO cache.
  37. """
  38. def __init__(self, size_limit: int = _LIMITED_DICT_SIZE):
  39. self.size_limit = size_limit
  40. super().__init__()
  41. def __setitem__(self, __key: Any, __value: Any) -> None:
  42. super().__setitem__(__key, __value)
  43. if len(self) > self.size_limit:
  44. excess = len(self) - self.size_limit + self.size_limit // 10
  45. to_remove = list(self.keys())[:excess]
  46. for key in to_remove:
  47. del self[key]
  48. def __class_getitem__(cls, *args: Any) -> Any:
  49. # to avoid errors with 3.7
  50. return cls
  51. # weak dictionaries allow the dynamically created parametrized versions of generic models to get collected
  52. # once they are no longer referenced by the caller.
  53. if sys.version_info >= (3, 9): # Typing for weak dictionaries available at 3.9
  54. GenericTypesCache = WeakValueDictionary[GenericTypesCacheKey, 'type[BaseModel]']
  55. else:
  56. GenericTypesCache = WeakValueDictionary
  57. if TYPE_CHECKING:
  58. class DeepChainMap(ChainMap[KT, VT]): # type: ignore
  59. ...
  60. else:
  61. class DeepChainMap(ChainMap):
  62. """Variant of ChainMap that allows direct updates to inner scopes.
  63. Taken from https://docs.python.org/3/library/collections.html#collections.ChainMap,
  64. with some light modifications for this use case.
  65. """
  66. def clear(self) -> None:
  67. for mapping in self.maps:
  68. mapping.clear()
  69. def __setitem__(self, key: KT, value: VT) -> None:
  70. for mapping in self.maps:
  71. mapping[key] = value
  72. def __delitem__(self, key: KT) -> None:
  73. hit = False
  74. for mapping in self.maps:
  75. if key in mapping:
  76. del mapping[key]
  77. hit = True
  78. if not hit:
  79. raise KeyError(key)
  80. # Despite the fact that LimitedDict _seems_ no longer necessary, I'm very nervous to actually remove it
  81. # and discover later on that we need to re-add all this infrastructure...
  82. # _GENERIC_TYPES_CACHE = DeepChainMap(GenericTypesCache(), LimitedDict())
  83. _GENERIC_TYPES_CACHE = GenericTypesCache()
  84. class PydanticGenericMetadata(typing_extensions.TypedDict):
  85. origin: type[BaseModel] | None # analogous to typing._GenericAlias.__origin__
  86. args: tuple[Any, ...] # analogous to typing._GenericAlias.__args__
  87. parameters: tuple[type[Any], ...] # analogous to typing.Generic.__parameters__
  88. def create_generic_submodel(
  89. model_name: str, origin: type[BaseModel], args: tuple[Any, ...], params: tuple[Any, ...]
  90. ) -> type[BaseModel]:
  91. """Dynamically create a submodel of a provided (generic) BaseModel.
  92. This is used when producing concrete parametrizations of generic models. This function
  93. only *creates* the new subclass; the schema/validators/serialization must be updated to
  94. reflect a concrete parametrization elsewhere.
  95. Args:
  96. model_name: The name of the newly created model.
  97. origin: The base class for the new model to inherit from.
  98. args: A tuple of generic metadata arguments.
  99. params: A tuple of generic metadata parameters.
  100. Returns:
  101. The created submodel.
  102. """
  103. namespace: dict[str, Any] = {'__module__': origin.__module__}
  104. bases = (origin,)
  105. meta, ns, kwds = prepare_class(model_name, bases)
  106. namespace.update(ns)
  107. created_model = meta(
  108. model_name,
  109. bases,
  110. namespace,
  111. __pydantic_generic_metadata__={
  112. 'origin': origin,
  113. 'args': args,
  114. 'parameters': params,
  115. },
  116. __pydantic_reset_parent_namespace__=False,
  117. **kwds,
  118. )
  119. model_module, called_globally = _get_caller_frame_info(depth=3)
  120. if called_globally: # create global reference and therefore allow pickling
  121. object_by_reference = None
  122. reference_name = model_name
  123. reference_module_globals = sys.modules[created_model.__module__].__dict__
  124. while object_by_reference is not created_model:
  125. object_by_reference = reference_module_globals.setdefault(reference_name, created_model)
  126. reference_name += '_'
  127. return created_model
  128. def _get_caller_frame_info(depth: int = 2) -> tuple[str | None, bool]:
  129. """Used inside a function to check whether it was called globally.
  130. Args:
  131. depth: The depth to get the frame.
  132. Returns:
  133. A tuple contains `module_nam` and `called_globally`.
  134. Raises:
  135. RuntimeError: If the function is not called inside a function.
  136. """
  137. try:
  138. previous_caller_frame = sys._getframe(depth)
  139. except ValueError as e:
  140. raise RuntimeError('This function must be used inside another function') from e
  141. except AttributeError: # sys module does not have _getframe function, so there's nothing we can do about it
  142. return None, False
  143. frame_globals = previous_caller_frame.f_globals
  144. return frame_globals.get('__name__'), previous_caller_frame.f_locals is frame_globals
  145. DictValues: type[Any] = {}.values().__class__
  146. def iter_contained_typevars(v: Any) -> Iterator[TypeVarType]:
  147. """Recursively iterate through all subtypes and type args of `v` and yield any typevars that are found.
  148. This is inspired as an alternative to directly accessing the `__parameters__` attribute of a GenericAlias,
  149. since __parameters__ of (nested) generic BaseModel subclasses won't show up in that list.
  150. """
  151. if isinstance(v, TypeVar):
  152. yield v
  153. elif is_model_class(v):
  154. yield from v.__pydantic_generic_metadata__['parameters']
  155. elif isinstance(v, (DictValues, list)):
  156. for var in v:
  157. yield from iter_contained_typevars(var)
  158. else:
  159. args = get_args(v)
  160. for arg in args:
  161. yield from iter_contained_typevars(arg)
  162. def get_args(v: Any) -> Any:
  163. pydantic_generic_metadata: PydanticGenericMetadata | None = getattr(v, '__pydantic_generic_metadata__', None)
  164. if pydantic_generic_metadata:
  165. return pydantic_generic_metadata.get('args')
  166. return typing_extensions.get_args(v)
  167. def get_origin(v: Any) -> Any:
  168. pydantic_generic_metadata: PydanticGenericMetadata | None = getattr(v, '__pydantic_generic_metadata__', None)
  169. if pydantic_generic_metadata:
  170. return pydantic_generic_metadata.get('origin')
  171. return typing_extensions.get_origin(v)
  172. def get_standard_typevars_map(cls: type[Any]) -> dict[TypeVarType, Any] | None:
  173. """Package a generic type's typevars and parametrization (if present) into a dictionary compatible with the
  174. `replace_types` function. Specifically, this works with standard typing generics and typing._GenericAlias.
  175. """
  176. origin = get_origin(cls)
  177. if origin is None:
  178. return None
  179. if not hasattr(origin, '__parameters__'):
  180. return None
  181. # In this case, we know that cls is a _GenericAlias, and origin is the generic type
  182. # So it is safe to access cls.__args__ and origin.__parameters__
  183. args: tuple[Any, ...] = cls.__args__ # type: ignore
  184. parameters: tuple[TypeVarType, ...] = origin.__parameters__
  185. return dict(zip(parameters, args))
  186. def get_model_typevars_map(cls: type[BaseModel]) -> dict[TypeVarType, Any] | None:
  187. """Package a generic BaseModel's typevars and concrete parametrization (if present) into a dictionary compatible
  188. with the `replace_types` function.
  189. Since BaseModel.__class_getitem__ does not produce a typing._GenericAlias, and the BaseModel generic info is
  190. stored in the __pydantic_generic_metadata__ attribute, we need special handling here.
  191. """
  192. # TODO: This could be unified with `get_standard_typevars_map` if we stored the generic metadata
  193. # in the __origin__, __args__, and __parameters__ attributes of the model.
  194. generic_metadata = cls.__pydantic_generic_metadata__
  195. origin = generic_metadata['origin']
  196. args = generic_metadata['args']
  197. return dict(zip(iter_contained_typevars(origin), args))
  198. def replace_types(type_: Any, type_map: Mapping[Any, Any] | None) -> Any:
  199. """Return type with all occurrences of `type_map` keys recursively replaced with their values.
  200. Args:
  201. type_: The class or generic alias.
  202. type_map: Mapping from `TypeVar` instance to concrete types.
  203. Returns:
  204. A new type representing the basic structure of `type_` with all
  205. `typevar_map` keys recursively replaced.
  206. Example:
  207. ```py
  208. from typing import List, Tuple, Union
  209. from pydantic._internal._generics import replace_types
  210. replace_types(Tuple[str, Union[List[str], float]], {str: int})
  211. #> Tuple[int, Union[List[int], float]]
  212. ```
  213. """
  214. if not type_map:
  215. return type_
  216. type_args = get_args(type_)
  217. origin_type = get_origin(type_)
  218. if origin_type is typing_extensions.Annotated:
  219. annotated_type, *annotations = type_args
  220. annotated = replace_types(annotated_type, type_map)
  221. for annotation in annotations:
  222. annotated = typing_extensions.Annotated[annotated, annotation]
  223. return annotated
  224. # Having type args is a good indicator that this is a typing module
  225. # class instantiation or a generic alias of some sort.
  226. if type_args:
  227. resolved_type_args = tuple(replace_types(arg, type_map) for arg in type_args)
  228. if all_identical(type_args, resolved_type_args):
  229. # If all arguments are the same, there is no need to modify the
  230. # type or create a new object at all
  231. return type_
  232. if (
  233. origin_type is not None
  234. and isinstance(type_, typing_base)
  235. and not isinstance(origin_type, typing_base)
  236. and getattr(type_, '_name', None) is not None
  237. ):
  238. # In python < 3.9 generic aliases don't exist so any of these like `list`,
  239. # `type` or `collections.abc.Callable` need to be translated.
  240. # See: https://www.python.org/dev/peps/pep-0585
  241. origin_type = getattr(typing, type_._name)
  242. assert origin_type is not None
  243. # PEP-604 syntax (Ex.: list | str) is represented with a types.UnionType object that does not have __getitem__.
  244. # We also cannot use isinstance() since we have to compare types.
  245. if sys.version_info >= (3, 10) and origin_type is types.UnionType:
  246. return _UnionGenericAlias(origin_type, resolved_type_args)
  247. # NotRequired[T] and Required[T] don't support tuple type resolved_type_args, hence the condition below
  248. return origin_type[resolved_type_args[0] if len(resolved_type_args) == 1 else resolved_type_args]
  249. # We handle pydantic generic models separately as they don't have the same
  250. # semantics as "typing" classes or generic aliases
  251. if not origin_type and is_model_class(type_):
  252. parameters = type_.__pydantic_generic_metadata__['parameters']
  253. if not parameters:
  254. return type_
  255. resolved_type_args = tuple(replace_types(t, type_map) for t in parameters)
  256. if all_identical(parameters, resolved_type_args):
  257. return type_
  258. return type_[resolved_type_args]
  259. # Handle special case for typehints that can have lists as arguments.
  260. # `typing.Callable[[int, str], int]` is an example for this.
  261. if isinstance(type_, (List, list)):
  262. resolved_list = list(replace_types(element, type_map) for element in type_)
  263. if all_identical(type_, resolved_list):
  264. return type_
  265. return resolved_list
  266. # If all else fails, we try to resolve the type directly and otherwise just
  267. # return the input with no modifications.
  268. return type_map.get(type_, type_)
  269. def has_instance_in_type(type_: Any, isinstance_target: Any) -> bool:
  270. """Checks if the type, or any of its arbitrary nested args, satisfy
  271. `isinstance(<type>, isinstance_target)`.
  272. """
  273. if isinstance(type_, isinstance_target):
  274. return True
  275. type_args = get_args(type_)
  276. origin_type = get_origin(type_)
  277. if origin_type is typing_extensions.Annotated:
  278. annotated_type, *annotations = type_args
  279. return has_instance_in_type(annotated_type, isinstance_target)
  280. # Having type args is a good indicator that this is a typing module
  281. # class instantiation or a generic alias of some sort.
  282. if any(has_instance_in_type(a, isinstance_target) for a in type_args):
  283. return True
  284. # Handle special case for typehints that can have lists as arguments.
  285. # `typing.Callable[[int, str], int]` is an example for this.
  286. if isinstance(type_, (List, list)) and not isinstance(type_, typing_extensions.ParamSpec):
  287. if any(has_instance_in_type(element, isinstance_target) for element in type_):
  288. return True
  289. return False
  290. def check_parameters_count(cls: type[BaseModel], parameters: tuple[Any, ...]) -> None:
  291. """Check the generic model parameters count is equal.
  292. Args:
  293. cls: The generic model.
  294. parameters: A tuple of passed parameters to the generic model.
  295. Raises:
  296. TypeError: If the passed parameters count is not equal to generic model parameters count.
  297. """
  298. actual = len(parameters)
  299. expected = len(cls.__pydantic_generic_metadata__['parameters'])
  300. if actual != expected:
  301. description = 'many' if actual > expected else 'few'
  302. raise TypeError(f'Too {description} parameters for {cls}; actual {actual}, expected {expected}')
  303. _generic_recursion_cache: ContextVar[set[str] | None] = ContextVar('_generic_recursion_cache', default=None)
  304. @contextmanager
  305. def generic_recursion_self_type(
  306. origin: type[BaseModel], args: tuple[Any, ...]
  307. ) -> Iterator[PydanticRecursiveRef | None]:
  308. """This contextmanager should be placed around the recursive calls used to build a generic type,
  309. and accept as arguments the generic origin type and the type arguments being passed to it.
  310. If the same origin and arguments are observed twice, it implies that a self-reference placeholder
  311. can be used while building the core schema, and will produce a schema_ref that will be valid in the
  312. final parent schema.
  313. """
  314. previously_seen_type_refs = _generic_recursion_cache.get()
  315. if previously_seen_type_refs is None:
  316. previously_seen_type_refs = set()
  317. token = _generic_recursion_cache.set(previously_seen_type_refs)
  318. else:
  319. token = None
  320. try:
  321. type_ref = get_type_ref(origin, args_override=args)
  322. if type_ref in previously_seen_type_refs:
  323. self_type = PydanticRecursiveRef(type_ref=type_ref)
  324. yield self_type
  325. else:
  326. previously_seen_type_refs.add(type_ref)
  327. yield None
  328. finally:
  329. if token:
  330. _generic_recursion_cache.reset(token)
  331. def recursively_defined_type_refs() -> set[str]:
  332. visited = _generic_recursion_cache.get()
  333. if not visited:
  334. return set() # not in a generic recursion, so there are no types
  335. return visited.copy() # don't allow modifications
  336. def get_cached_generic_type_early(parent: type[BaseModel], typevar_values: Any) -> type[BaseModel] | None:
  337. """The use of a two-stage cache lookup approach was necessary to have the highest performance possible for
  338. repeated calls to `__class_getitem__` on generic types (which may happen in tighter loops during runtime),
  339. while still ensuring that certain alternative parametrizations ultimately resolve to the same type.
  340. As a concrete example, this approach was necessary to make Model[List[T]][int] equal to Model[List[int]].
  341. The approach could be modified to not use two different cache keys at different points, but the
  342. _early_cache_key is optimized to be as quick to compute as possible (for repeated-access speed), and the
  343. _late_cache_key is optimized to be as "correct" as possible, so that two types that will ultimately be the
  344. same after resolving the type arguments will always produce cache hits.
  345. If we wanted to move to only using a single cache key per type, we would either need to always use the
  346. slower/more computationally intensive logic associated with _late_cache_key, or would need to accept
  347. that Model[List[T]][int] is a different type than Model[List[T]][int]. Because we rely on subclass relationships
  348. during validation, I think it is worthwhile to ensure that types that are functionally equivalent are actually
  349. equal.
  350. """
  351. return _GENERIC_TYPES_CACHE.get(_early_cache_key(parent, typevar_values))
  352. def get_cached_generic_type_late(
  353. parent: type[BaseModel], typevar_values: Any, origin: type[BaseModel], args: tuple[Any, ...]
  354. ) -> type[BaseModel] | None:
  355. """See the docstring of `get_cached_generic_type_early` for more information about the two-stage cache lookup."""
  356. cached = _GENERIC_TYPES_CACHE.get(_late_cache_key(origin, args, typevar_values))
  357. if cached is not None:
  358. set_cached_generic_type(parent, typevar_values, cached, origin, args)
  359. return cached
  360. def set_cached_generic_type(
  361. parent: type[BaseModel],
  362. typevar_values: tuple[Any, ...],
  363. type_: type[BaseModel],
  364. origin: type[BaseModel] | None = None,
  365. args: tuple[Any, ...] | None = None,
  366. ) -> None:
  367. """See the docstring of `get_cached_generic_type_early` for more information about why items are cached with
  368. two different keys.
  369. """
  370. _GENERIC_TYPES_CACHE[_early_cache_key(parent, typevar_values)] = type_
  371. if len(typevar_values) == 1:
  372. _GENERIC_TYPES_CACHE[_early_cache_key(parent, typevar_values[0])] = type_
  373. if origin and args:
  374. _GENERIC_TYPES_CACHE[_late_cache_key(origin, args, typevar_values)] = type_
  375. def _union_orderings_key(typevar_values: Any) -> Any:
  376. """This is intended to help differentiate between Union types with the same arguments in different order.
  377. Thanks to caching internal to the `typing` module, it is not possible to distinguish between
  378. List[Union[int, float]] and List[Union[float, int]] (and similarly for other "parent" origins besides List)
  379. because `typing` considers Union[int, float] to be equal to Union[float, int].
  380. However, you _can_ distinguish between (top-level) Union[int, float] vs. Union[float, int].
  381. Because we parse items as the first Union type that is successful, we get slightly more consistent behavior
  382. if we make an effort to distinguish the ordering of items in a union. It would be best if we could _always_
  383. get the exact-correct order of items in the union, but that would require a change to the `typing` module itself.
  384. (See https://github.com/python/cpython/issues/86483 for reference.)
  385. """
  386. if isinstance(typevar_values, tuple):
  387. args_data = []
  388. for value in typevar_values:
  389. args_data.append(_union_orderings_key(value))
  390. return tuple(args_data)
  391. elif typing_extensions.get_origin(typevar_values) is typing.Union:
  392. return get_args(typevar_values)
  393. else:
  394. return ()
  395. def _early_cache_key(cls: type[BaseModel], typevar_values: Any) -> GenericTypesCacheKey:
  396. """This is intended for minimal computational overhead during lookups of cached types.
  397. Note that this is overly simplistic, and it's possible that two different cls/typevar_values
  398. inputs would ultimately result in the same type being created in BaseModel.__class_getitem__.
  399. To handle this, we have a fallback _late_cache_key that is checked later if the _early_cache_key
  400. lookup fails, and should result in a cache hit _precisely_ when the inputs to __class_getitem__
  401. would result in the same type.
  402. """
  403. return cls, typevar_values, _union_orderings_key(typevar_values)
  404. def _late_cache_key(origin: type[BaseModel], args: tuple[Any, ...], typevar_values: Any) -> GenericTypesCacheKey:
  405. """This is intended for use later in the process of creating a new type, when we have more information
  406. about the exact args that will be passed. If it turns out that a different set of inputs to
  407. __class_getitem__ resulted in the same inputs to the generic type creation process, we can still
  408. return the cached type, and update the cache with the _early_cache_key as well.
  409. """
  410. # The _union_orderings_key is placed at the start here to ensure there cannot be a collision with an
  411. # _early_cache_key, as that function will always produce a BaseModel subclass as the first item in the key,
  412. # whereas this function will always produce a tuple as the first item in the key.
  413. return _union_orderings_key(typevar_values), origin, args