Coverage for pydantic/_internal/_typing_extra.py: 95.09%

193 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-05-02 08:21 +0000

1"""Logic for interacting with type annotations, mostly extensions, shims and hacks to wrap Python's typing module.""" 

2 

3from __future__ import annotations 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

4 

5import collections.abc 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

6import re 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

7import sys 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

8import types 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

9import typing 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

10from functools import partial 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

11from typing import TYPE_CHECKING, Any, Callable, cast 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

12 

13import typing_extensions 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

14from typing_extensions import deprecated, get_args, get_origin 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

15from typing_inspection import typing_objects 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

16from typing_inspection.introspection import is_union_origin 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

17 

18from pydantic.version import version_short 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

19 

20from ._namespace_utils import GlobalsNamespace, MappingNamespace, NsResolver, get_module_ns_of 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

21 

22if sys.version_info < (3, 10): 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

23 NoneType = type(None) 1abgcdJef

24 EllipsisType = type(Ellipsis) 1abgIcdJef

25else: 

26 from types import EllipsisType as EllipsisType 1CDwxhijklIEFyzmnopqKLMNOPGHABrstuv

27 from types import NoneType as NoneType 1CDwxhijklIEFyzmnopqKLMNOPGHABrstuv

28 

29if TYPE_CHECKING: 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

30 from pydantic import BaseModel 

31 

32# As per https://typing-extensions.readthedocs.io/en/latest/#runtime-use-of-types, 

33# always check for both `typing` and `typing_extensions` variants of a typing construct. 

34# (this is implemented differently than the suggested approach in the `typing_extensions` 

35# docs for performance). 

36 

37 

38_t_annotated = typing.Annotated 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

39_te_annotated = typing_extensions.Annotated 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

40 

41 

42def is_annotated(tp: Any, /) -> bool: 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

43 """Return whether the provided argument is a `Annotated` special form. 

44 

45 ```python {test="skip" lint="skip"} 

46 is_annotated(Annotated[int, ...]) 

47 #> True 

48 ``` 

49 """ 

50 origin = get_origin(tp) 

51 return origin is _t_annotated or origin is _te_annotated 

52 

53 

54def annotated_type(tp: Any, /) -> Any | None: 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

55 """Return the type of the `Annotated` special form, or `None`.""" 

56 return tp.__origin__ if typing_objects.is_annotated(get_origin(tp)) else None 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv

57 

58 

59def unpack_type(tp: Any, /) -> Any | None: 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

60 """Return the type wrapped by the `Unpack` special form, or `None`.""" 

61 return get_args(tp)[0] if typing_objects.is_unpack(get_origin(tp)) else None 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv

62 

63 

64def is_hashable(tp: Any, /) -> bool: 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

65 """Return whether the provided argument is the `Hashable` class. 

66 

67 ```python {test="skip" lint="skip"} 

68 is_hashable(Hashable) 

69 #> True 

70 ``` 

71 """ 

72 # `get_origin` is documented as normalizing any typing-module aliases to `collections` classes, 

73 # hence the second check: 

74 return tp is collections.abc.Hashable or get_origin(tp) is collections.abc.Hashable 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

75 

76 

77def is_callable(tp: Any, /) -> bool: 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

78 """Return whether the provided argument is a `Callable`, parametrized or not. 

79 

80 ```python {test="skip" lint="skip"} 

81 is_callable(Callable[[int], str]) 

82 #> True 

83 is_callable(typing.Callable) 

84 #> True 

85 is_callable(collections.abc.Callable) 

86 #> True 

87 ``` 

88 """ 

89 # `get_origin` is documented as normalizing any typing-module aliases to `collections` classes, 

90 # hence the second check: 

91 return tp is collections.abc.Callable or get_origin(tp) is collections.abc.Callable 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

92 

93 

94_classvar_re = re.compile(r'((\w+\.)?Annotated\[)?(\w+\.)?ClassVar\[') 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

95 

96 

97def is_classvar_annotation(tp: Any, /) -> bool: 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

98 """Return whether the provided argument represents a class variable annotation. 

99 

100 Although not explicitly stated by the typing specification, `ClassVar` can be used 

101 inside `Annotated` and as such, this function checks for this specific scenario. 

102 

103 Because this function is used to detect class variables before evaluating forward references 

104 (or because evaluation failed), we also implement a naive regex match implementation. This is 

105 required because class variables are inspected before fields are collected, so we try to be 

106 as accurate as possible. 

107 """ 

108 if typing_objects.is_classvar(tp): 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

109 return True 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv

110 

111 origin = get_origin(tp) 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

112 

113 if typing_objects.is_classvar(origin): 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

114 return True 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

115 

116 if typing_objects.is_annotated(origin): 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

117 annotated_type = tp.__origin__ 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv

118 if typing_objects.is_classvar(annotated_type) or typing_objects.is_classvar(get_origin(annotated_type)): 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv

119 return True 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv

120 

121 str_ann: str | None = None 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

122 if isinstance(tp, typing.ForwardRef): 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

123 str_ann = tp.__forward_arg__ 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

124 if isinstance(tp, str): 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

125 str_ann = tp 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv

126 

127 if str_ann is not None and _classvar_re.match(str_ann): 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

128 # stdlib dataclasses do something similar, although a bit more advanced 

129 # (see `dataclass._is_type`). 

130 return True 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv

131 

132 return False 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

133 

134 

135_t_final = typing.Final 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

136_te_final = typing_extensions.Final 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

137 

138 

139# TODO implement `is_finalvar_annotation` as Final can be wrapped with other special forms: 

140def is_finalvar(tp: Any, /) -> bool: 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

141 """Return whether the provided argument is a `Final` special form, parametrized or not. 

142 

143 ```python {test="skip" lint="skip"} 

144 is_finalvar(Final[int]) 

145 #> True 

146 is_finalvar(Final) 

147 #> True 

148 """ 

149 # Final is not necessarily parametrized: 

150 if tp is _t_final or tp is _te_final: 150 ↛ 151line 150 didn't jump to line 151 because the condition on line 150 was never true1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

151 return True 

152 origin = get_origin(tp) 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

153 return origin is _t_final or origin is _te_final 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

154 

155 

156_NONE_TYPES: tuple[Any, ...] = (None, NoneType, typing.Literal[None], typing_extensions.Literal[None]) 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

157 

158 

159def is_none_type(tp: Any, /) -> bool: 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

160 """Return whether the argument represents the `None` type as part of an annotation. 

161 

162 ```python {test="skip" lint="skip"} 

163 is_none_type(None) 

164 #> True 

165 is_none_type(NoneType) 

166 #> True 

167 is_none_type(Literal[None]) 

168 #> True 

169 is_none_type(type[None]) 

170 #> False 

171 """ 

172 return tp in _NONE_TYPES 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv

173 

174 

175def is_namedtuple(tp: Any, /) -> bool: 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

176 """Return whether the provided argument is a named tuple class. 

177 

178 The class can be created using `typing.NamedTuple` or `collections.namedtuple`. 

179 Parametrized generic classes are *not* assumed to be named tuples. 

180 """ 

181 from ._utils import lenient_issubclass # circ. import 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

182 

183 return lenient_issubclass(tp, tuple) and hasattr(tp, '_fields') 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

184 

185 

186# TODO In 2.12, delete this export. It is currently defined only to not break 

187# pydantic-settings which relies on it: 

188origin_is_union = is_union_origin 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

189 

190 

191def is_generic_alias(tp: Any, /) -> bool: 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

192 return isinstance(tp, (types.GenericAlias, typing._GenericAlias)) # pyright: ignore[reportAttributeAccessIssue] 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

193 

194 

195# TODO: Ideally, we should avoid relying on the private `typing` constructs: 

196 

197if sys.version_info < (3, 10): 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

198 WithArgsTypes: tuple[Any, ...] = (typing._GenericAlias, types.GenericAlias) # pyright: ignore[reportAttributeAccessIssue] 1abgcdJef

199else: 

200 WithArgsTypes: tuple[Any, ...] = (typing._GenericAlias, types.GenericAlias, types.UnionType) # pyright: ignore[reportAttributeAccessIssue] 1CDwxhijklIEFyzmnopqKLMNOPGHABrstuv

201 

202 

203# Similarly, we shouldn't rely on this `_Final` class, which is even more private than `_GenericAlias`: 

204typing_base: Any = typing._Final # pyright: ignore[reportAttributeAccessIssue] 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

205 

206 

207### Annotation evaluations functions: 

208 

209 

210def parent_frame_namespace(*, parent_depth: int = 2, force: bool = False) -> dict[str, Any] | None: 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

211 """Fetch the local namespace of the parent frame where this function is called. 

212 

213 Using this function is mostly useful to resolve forward annotations pointing to members defined in a local namespace, 

214 such as assignments inside a function. Using the standard library tools, it is currently not possible to resolve 

215 such annotations: 

216 

217 ```python {lint="skip" test="skip"} 

218 from typing import get_type_hints 

219 

220 def func() -> None: 

221 Alias = int 

222 

223 class C: 

224 a: 'Alias' 

225 

226 # Raises a `NameError: 'Alias' is not defined` 

227 get_type_hints(C) 

228 ``` 

229 

230 Pydantic uses this function when a Pydantic model is being defined to fetch the parent frame locals. However, 

231 this only allows us to fetch the parent frame namespace and not other parents (e.g. a model defined in a function, 

232 itself defined in another function). Inspecting the next outer frames (using `f_back`) is not reliable enough 

233 (see https://discuss.python.org/t/20659). 

234 

235 Because this function is mostly used to better resolve forward annotations, nothing is returned if the parent frame's 

236 code object is defined at the module level. In this case, the locals of the frame will be the same as the module 

237 globals where the class is defined (see `_namespace_utils.get_module_ns_of`). However, if you still want to fetch 

238 the module globals (e.g. when rebuilding a model, where the frame where the rebuild call is performed might contain 

239 members that you want to use for forward annotations evaluation), you can use the `force` parameter. 

240 

241 Args: 

242 parent_depth: The depth at which to get the frame. Defaults to 2, meaning the parent frame where this function 

243 is called will be used. 

244 force: Whether to always return the frame locals, even if the frame's code object is defined at the module level. 

245 

246 Returns: 

247 The locals of the namespace, or `None` if it was skipped as per the described logic. 

248 """ 

249 frame = sys._getframe(parent_depth) 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

250 

251 if frame.f_code.co_name.startswith('<generic parameters of'): 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

252 # As `parent_frame_namespace` is mostly called in `ModelMetaclass.__new__`, 

253 # the parent frame can be the annotation scope if the PEP 695 generic syntax is used. 

254 # (see https://docs.python.org/3/reference/executionmodel.html#annotation-scopes, 

255 # https://docs.python.org/3/reference/compound_stmts.html#generic-classes). 

256 # In this case, the code name is set to `<generic parameters of MyClass>`, 

257 # and we need to skip this frame as it is irrelevant. 

258 frame = cast(types.FrameType, frame.f_back) # guaranteed to not be `None` 1hijklmnopqrstuv

259 

260 # note, we don't copy frame.f_locals here (or during the last return call), because we don't expect the namespace to be 

261 # modified down the line if this becomes a problem, we could implement some sort of frozen mapping structure to enforce this. 

262 if force: 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

263 return frame.f_locals 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

264 

265 # If either of the following conditions are true, the class is defined at the top module level. 

266 # To better understand why we need both of these checks, see 

267 # https://github.com/pydantic/pydantic/pull/10113#discussion_r1714981531. 

268 if frame.f_back is None or frame.f_code.co_name == '<module>': 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

269 return None 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

270 

271 return frame.f_locals 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

272 

273 

274def _type_convert(arg: Any) -> Any: 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

275 """Convert `None` to `NoneType` and strings to `ForwardRef` instances. 

276 

277 This is a backport of the private `typing._type_convert` function. When 

278 evaluating a type, `ForwardRef._evaluate` ends up being called, and is 

279 responsible for making this conversion. However, we still have to apply 

280 it for the first argument passed to our type evaluation functions, similarly 

281 to the `typing.get_type_hints` function. 

282 """ 

283 if arg is None: 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

284 return NoneType 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv

285 if isinstance(arg, str): 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

286 # Like `typing.get_type_hints`, assume the arg can be in any context, 

287 # hence the proper `is_argument` and `is_class` args: 

288 return _make_forward_ref(arg, is_argument=False, is_class=True) 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

289 return arg 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

290 

291 

292def get_model_type_hints( 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

293 obj: type[BaseModel], 

294 *, 

295 ns_resolver: NsResolver | None = None, 

296) -> dict[str, tuple[Any, bool]]: 

297 """Collect annotations from a Pydantic model class, including those from parent classes. 

298 

299 Args: 

300 obj: The Pydantic model to inspect. 

301 ns_resolver: A namespace resolver instance to use. Defaults to an empty instance. 

302 

303 Returns: 

304 A dictionary mapping annotation names to a two-tuple: the first element is the evaluated 

305 type or the original annotation if a `NameError` occurred, the second element is a boolean 

306 indicating if whether the evaluation succeeded. 

307 """ 

308 hints: dict[str, Any] | dict[str, tuple[Any, bool]] = {} 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

309 ns_resolver = ns_resolver or NsResolver() 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

310 

311 for base in reversed(obj.__mro__): 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

312 ann: dict[str, Any] | None = base.__dict__.get('__annotations__') 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

313 if not ann or isinstance(ann, types.GetSetDescriptorType): 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

314 continue 1CDwxhijklIEFyzmnopqKLMNOPGHABrstuv

315 with ns_resolver.push(base): 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

316 globalns, localns = ns_resolver.types_namespace 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

317 for name, value in ann.items(): 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

318 if name.startswith('_'): 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

319 # For private attributes, we only need the annotation to detect the `ClassVar` special form. 

320 # For this reason, we still try to evaluate it, but we also catch any possible exception (on 

321 # top of the `NameError`s caught in `try_eval_type`) that could happen so that users are free 

322 # to use any kind of forward annotation for private fields (e.g. circular imports, new typing 

323 # syntax, etc). 

324 try: 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv

325 hints[name] = try_eval_type(value, globalns, localns) 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv

326 except Exception: 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv

327 hints[name] = (value, False) 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv

328 else: 

329 hints[name] = try_eval_type(value, globalns, localns) 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

330 return hints 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

331 

332 

333def get_cls_type_hints( 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

334 obj: type[Any], 

335 *, 

336 ns_resolver: NsResolver | None = None, 

337) -> dict[str, Any]: 

338 """Collect annotations from a class, including those from parent classes. 

339 

340 Args: 

341 obj: The class to inspect. 

342 ns_resolver: A namespace resolver instance to use. Defaults to an empty instance. 

343 """ 

344 hints: dict[str, Any] | dict[str, tuple[Any, bool]] = {} 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv

345 ns_resolver = ns_resolver or NsResolver() 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv

346 

347 for base in reversed(obj.__mro__): 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv

348 ann: dict[str, Any] | None = base.__dict__.get('__annotations__') 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv

349 if not ann or isinstance(ann, types.GetSetDescriptorType): 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv

350 continue 1CDwxhijklIEFyzmnopqGHABrstuv

351 with ns_resolver.push(base): 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv

352 globalns, localns = ns_resolver.types_namespace 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv

353 for name, value in ann.items(): 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv

354 hints[name] = eval_type(value, globalns, localns) 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv

355 return hints 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv

356 

357 

358def try_eval_type( 1abCDwxhijklcdEFyzmnopqJKLMNOPefGHABrstuv

359 value: Any, 

360 globalns: GlobalsNamespace | None = None, 

361 localns: MappingNamespace | None = None, 

362) -> tuple[Any, bool]: 

363 """Try evaluating the annotation using the provided namespaces. 

364 

365 Args: 

366 value: The value to evaluate. If `None`, it will be replaced by `type[None]`. If an instance 

367 of `str`, it will be converted to a `ForwardRef`. 

368 localns: The global namespace to use during annotation evaluation. 

369 globalns: The local namespace to use during annotation evaluation. 

370 

371 Returns: 

372 A two-tuple containing the possibly evaluated type and a boolean indicating 

373 whether the evaluation succeeded or not. 

374 """ 

375 value = _type_convert(value) 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

376 

377 try: 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

378 return eval_type_backport(value, globalns, localns), True 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

379 except NameError: 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

380 return value, False 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

381 

382 

383def eval_type( 1abCDwxhijklcdEFyzmnopqJKLMNOPefGHABrstuv

384 value: Any, 

385 globalns: GlobalsNamespace | None = None, 

386 localns: MappingNamespace | None = None, 

387) -> Any: 

388 """Evaluate the annotation using the provided namespaces. 

389 

390 Args: 

391 value: The value to evaluate. If `None`, it will be replaced by `type[None]`. If an instance 

392 of `str`, it will be converted to a `ForwardRef`. 

393 localns: The global namespace to use during annotation evaluation. 

394 globalns: The local namespace to use during annotation evaluation. 

395 """ 

396 value = _type_convert(value) 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

397 return eval_type_backport(value, globalns, localns) 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

398 

399 

400@deprecated( 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

401 '`eval_type_lenient` is deprecated, use `try_eval_type` instead.', 

402 category=None, 

403) 

404def eval_type_lenient( 1abCDwxhijklcdEFyzmnopqJKLMNOPefGHABrstuv

405 value: Any, 

406 globalns: GlobalsNamespace | None = None, 

407 localns: MappingNamespace | None = None, 

408) -> Any: 

409 ev, _ = try_eval_type(value, globalns, localns) 

410 return ev 

411 

412 

413def eval_type_backport( 1abCDwxhijklcdEFyzmnopqJKLMNOPefGHABrstuv

414 value: Any, 

415 globalns: GlobalsNamespace | None = None, 

416 localns: MappingNamespace | None = None, 

417 type_params: tuple[Any, ...] | None = None, 

418) -> Any: 

419 """An enhanced version of `typing._eval_type` which will fall back to using the `eval_type_backport` 

420 package if it's installed to let older Python versions use newer typing constructs. 

421 

422 Specifically, this transforms `X | Y` into `typing.Union[X, Y]` and `list[X]` into `typing.List[X]` 

423 (as well as all the types made generic in PEP 585) if the original syntax is not supported in the 

424 current Python version. 

425 

426 This function will also display a helpful error if the value passed fails to evaluate. 

427 """ 

428 try: 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

429 return _eval_type_backport(value, globalns, localns, type_params) 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

430 except TypeError as e: 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

431 if 'Unable to evaluate type annotation' in str(e): 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv

432 raise 1abgcdef

433 

434 # If it is a `TypeError` and value isn't a `ForwardRef`, it would have failed during annotation definition. 

435 # Thus we assert here for type checking purposes: 

436 assert isinstance(value, typing.ForwardRef) 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv

437 

438 message = f'Unable to evaluate type annotation {value.__forward_arg__!r}.' 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv

439 if sys.version_info >= (3, 11): 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv

440 e.add_note(message) 1wxhijklyzmnopqABrstuv

441 raise 1wxhijklyzmnopqABrstuv

442 else: 

443 raise TypeError(message) from e 1abCDgIcdEFefGH

444 except RecursionError as e: 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

445 # TODO ideally recursion errors should be checked in `eval_type` above, but `eval_type_backport` 

446 # is used directly in some places. 

447 message = ( 1abCDwxhijklcdEFyzmnopqefGHABrstuv

448 "If you made use of an implicit recursive type alias (e.g. `MyType = list['MyType']), " 

449 'consider using PEP 695 type aliases instead. For more details, refer to the documentation: ' 

450 f'https://docs.pydantic.dev/{version_short()}/concepts/types/#named-recursive-types' 

451 ) 

452 if sys.version_info >= (3, 11): 1abCDwxhijklcdEFyzmnopqefGHABrstuv

453 e.add_note(message) 1wxhijklyzmnopqABrstuv

454 raise 1wxhijklyzmnopqABrstuv

455 else: 

456 raise RecursionError(f'{e.args[0]}\n{message}') 1abCDcdEFefGH

457 

458 

459def _eval_type_backport( 1abCDwxhijklcdEFyzmnopqJKLMNOPefGHABrstuv

460 value: Any, 

461 globalns: GlobalsNamespace | None = None, 

462 localns: MappingNamespace | None = None, 

463 type_params: tuple[Any, ...] | None = None, 

464) -> Any: 

465 try: 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

466 return _eval_type(value, globalns, localns, type_params) 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

467 except TypeError as e: 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

468 if not (isinstance(value, typing.ForwardRef) and is_backport_fixable_error(e)): 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv

469 raise 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv

470 

471 try: 1abgcdef

472 from eval_type_backport import eval_type_backport 1abgcdef

473 except ImportError: 1abgcdef

474 raise TypeError( 1abgcdef

475 f'Unable to evaluate type annotation {value.__forward_arg__!r}. If you are making use ' 

476 'of the new typing syntax (unions using `|` since Python 3.10 or builtins subscripting ' 

477 'since Python 3.9), you should either replace the use of new syntax with the existing ' 

478 '`typing` constructs or install the `eval_type_backport` package.' 

479 ) from e 

480 

481 return eval_type_backport( 1abgcdef

482 value, 

483 globalns, 

484 localns, # pyright: ignore[reportArgumentType], waiting on a new `eval_type_backport` release. 

485 try_default=False, 

486 ) 

487 

488 

489def _eval_type( 1abCDwxhijklcdEFyzmnopqJKLMNOPefGHABrstuv

490 value: Any, 

491 globalns: GlobalsNamespace | None = None, 

492 localns: MappingNamespace | None = None, 

493 type_params: tuple[Any, ...] | None = None, 

494) -> Any: 

495 if sys.version_info >= (3, 13): 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

496 return typing._eval_type( # type: ignore 1jklopqPtuv

497 value, globalns, localns, type_params=type_params 

498 ) 

499 else: 

500 return typing._eval_type( # type: ignore 1abCDwxhigIcdEFyzmnJKLMNOefGHABrs

501 value, globalns, localns 

502 ) 

503 

504 

505def is_backport_fixable_error(e: TypeError) -> bool: 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

506 msg = str(e) 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv

507 

508 return sys.version_info < (3, 10) and msg.startswith('unsupported operand type(s) for |: ') 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv

509 

510 

511def get_function_type_hints( 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

512 function: Callable[..., Any], 

513 *, 

514 include_keys: set[str] | None = None, 

515 globalns: GlobalsNamespace | None = None, 

516 localns: MappingNamespace | None = None, 

517) -> dict[str, Any]: 

518 """Return type hints for a function. 

519 

520 This is similar to the `typing.get_type_hints` function, with a few differences: 

521 - Support `functools.partial` by using the underlying `func` attribute. 

522 - Do not wrap type annotation of a parameter with `Optional` if it has a default value of `None` 

523 (related bug: https://github.com/python/cpython/issues/90353, only fixed in 3.11+). 

524 """ 

525 try: 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv

526 if isinstance(function, partial): 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv

527 annotations = function.func.__annotations__ 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv

528 else: 

529 annotations = function.__annotations__ 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv

530 except AttributeError: 

531 # Some functions (e.g. builtins) don't have annotations: 

532 return {} 

533 

534 if globalns is None: 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv

535 globalns = get_module_ns_of(function) 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv

536 type_params: tuple[Any, ...] | None = None 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv

537 if localns is None: 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv

538 # If localns was specified, it is assumed to already contain type params. This is because 

539 # Pydantic has more advanced logic to do so (see `_namespace_utils.ns_for_function`). 

540 type_params = getattr(function, '__type_params__', ()) 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv

541 

542 type_hints = {} 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv

543 for name, value in annotations.items(): 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv

544 if include_keys is not None and name not in include_keys: 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv

545 continue 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv

546 if value is None: 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv

547 value = NoneType 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv

548 elif isinstance(value, str): 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv

549 value = _make_forward_ref(value) 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv

550 

551 type_hints[name] = eval_type_backport(value, globalns, localns, type_params) 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv

552 

553 return type_hints 1abCDwxhijklgIcdEFyzmnopqefGHABrstuv

554 

555 

556if sys.version_info < (3, 9, 8) or (3, 10) <= sys.version_info < (3, 10, 1): 556 ↛ 558line 556 didn't jump to line 558 because the condition on line 556 was never true1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

557 

558 def _make_forward_ref( 

559 arg: Any, 

560 is_argument: bool = True, 

561 *, 

562 is_class: bool = False, 

563 ) -> typing.ForwardRef: 

564 """Wrapper for ForwardRef that accounts for the `is_class` argument missing in older versions. 

565 The `module` argument is omitted as it breaks <3.9.8, =3.10.0 and isn't used in the calls below. 

566 

567 See https://github.com/python/cpython/pull/28560 for some background. 

568 The backport happened on 3.9.8, see: 

569 https://github.com/pydantic/pydantic/discussions/6244#discussioncomment-6275458, 

570 and on 3.10.1 for the 3.10 branch, see: 

571 https://github.com/pydantic/pydantic/issues/6912 

572 

573 Implemented as EAFP with memory. 

574 """ 

575 return typing.ForwardRef(arg, is_argument) 

576 

577else: 

578 _make_forward_ref = typing.ForwardRef 1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

579 

580 

581if sys.version_info >= (3, 10): 581 ↛ 585line 581 didn't jump to line 585 because the condition on line 581 was always true1abCDwxhijklgIcdEFyzmnopqJKLMNOPefGHABrstuv

582 get_type_hints = typing.get_type_hints 1CDwxhijklIEFyzmnopqKLMNOPGHABrstuv

583 

584else: 

585 """ 

586 For older versions of python, we have a custom implementation of `get_type_hints` which is a close as possible to 

587 the implementation in CPython 3.10.8. 

588 """ 

589 

590 @typing.no_type_check 1abgcdJef

591 def get_type_hints( # noqa: C901 1abcdJef

592 obj: Any, 1g

593 globalns: dict[str, Any] | None = None, 1g

594 localns: dict[str, Any] | None = None, 1g

595 include_extras: bool = False, 1g

596 ) -> dict[str, Any]: # pragma: no cover 1g

597 """Taken verbatim from python 3.10.8 unchanged, except: 

598 * type annotations of the function definition above. 

599 * prefixing `typing.` where appropriate 

600 * Use `_make_forward_ref` instead of `typing.ForwardRef` to handle the `is_class` argument. 

601 

602 https://github.com/python/cpython/blob/aaaf5174241496afca7ce4d4584570190ff972fe/Lib/typing.py#L1773-L1875 

603 

604 DO NOT CHANGE THIS METHOD UNLESS ABSOLUTELY NECESSARY. 

605 ====================================================== 

606 

607 Return type hints for an object. 

608 

609 This is often the same as obj.__annotations__, but it handles 

610 forward references encoded as string literals, adds Optional[t] if a 

611 default value equal to None is set and recursively replaces all 

612 'Annotated[T, ...]' with 'T' (unless 'include_extras=True'). 

613 

614 The argument may be a module, class, method, or function. The annotations 

615 are returned as a dictionary. For classes, annotations include also 

616 inherited members. 

617 

618 TypeError is raised if the argument is not of a type that can contain 

619 annotations, and an empty dictionary is returned if no annotations are 

620 present. 

621 

622 BEWARE -- the behavior of globalns and localns is counterintuitive 

623 (unless you are familiar with how eval() and exec() work). The 

624 search order is locals first, then globals. 

625 

626 - If no dict arguments are passed, an attempt is made to use the 

627 globals from obj (or the respective module's globals for classes), 

628 and these are also used as the locals. If the object does not appear 

629 to have globals, an empty dictionary is used. For classes, the search 

630 order is globals first then locals. 

631 

632 - If one dict argument is passed, it is used for both globals and 

633 locals. 

634 

635 - If two dict arguments are passed, they specify globals and 

636 locals, respectively. 

637 """ 

638 if getattr(obj, '__no_type_check__', None): 1abgcdef

639 return {} 

640 # Classes require a special treatment. 

641 if isinstance(obj, type): 1abgcdef

642 hints = {} 1abgcdef

643 for base in reversed(obj.__mro__): 1abgcdef

644 if globalns is None: 1abgcdef

645 base_globals = getattr(sys.modules.get(base.__module__, None), '__dict__', {}) 1abgcdef

646 else: 

647 base_globals = globalns 

648 ann = base.__dict__.get('__annotations__', {}) 1abgcdef

649 if isinstance(ann, types.GetSetDescriptorType): 1abgcdef

650 ann = {} 

651 base_locals = dict(vars(base)) if localns is None else localns 1abgcdef

652 if localns is None and globalns is None: 1abgcdef

653 # This is surprising, but required. Before Python 3.10, 

654 # get_type_hints only evaluated the globalns of 

655 # a class. To maintain backwards compatibility, we reverse 

656 # the globalns and localns order so that eval() looks into 

657 # *base_globals* first rather than *base_locals*. 

658 # This only affects ForwardRefs. 

659 base_globals, base_locals = base_locals, base_globals 

660 for name, value in ann.items(): 1abgcdef

661 if value is None: 1abgcdef

662 value = type(None) 

663 if isinstance(value, str): 1abgcdef

664 value = _make_forward_ref(value, is_argument=False, is_class=True) 1abgcdef

665 

666 value = eval_type_backport(value, base_globals, base_locals) 1abgcdef

667 hints[name] = value 1abgcdef

668 if not include_extras and hasattr(typing, '_strip_annotations'): 1abgcdef

669 return { 1abgcdef

670 k: typing._strip_annotations(t) # type: ignore 1abgcdef

671 for k, t in hints.items() 1abgcdef

672 } 

673 else: 

674 return hints 

675 

676 if globalns is None: 1abgcdef

677 if isinstance(obj, types.ModuleType): 1abgcdef

678 globalns = obj.__dict__ 

679 else: 

680 nsobj = obj 1abgcdef

681 # Find globalns for the unwrapped object. 

682 while hasattr(nsobj, '__wrapped__'): 1abgcdef

683 nsobj = nsobj.__wrapped__ 

684 globalns = getattr(nsobj, '__globals__', {}) 1abgcdef

685 if localns is None: 1abgcdef

686 localns = globalns 1abgcdef

687 elif localns is None: 

688 localns = globalns 

689 hints = getattr(obj, '__annotations__', None) 1abgcdef

690 if hints is None: 1abgcdef

691 # Return empty annotations for something that _could_ have them. 

692 if isinstance(obj, typing._allowed_types): # type: ignore 

693 return {} 

694 else: 

695 raise TypeError(f'{obj!r} is not a module, class, method, or function.') 

696 defaults = typing._get_defaults(obj) # type: ignore 1abgcdef

697 hints = dict(hints) 1abgcdef

698 for name, value in hints.items(): 1abgcdef

699 if value is None: 1abgcdef

700 value = type(None) 

701 if isinstance(value, str): 1abgcdef

702 # class-level forward refs were handled above, this must be either 

703 # a module-level annotation or a function argument annotation 

704 

705 value = _make_forward_ref( 1abgcdef

706 value, 1abgcdef

707 is_argument=not isinstance(obj, types.ModuleType), 1abgcdef

708 is_class=False, 1abgcdef

709 ) 

710 value = eval_type_backport(value, globalns, localns) 1abgcdef

711 if name in defaults and defaults[name] is None: 1abgcdef

712 value = typing.Optional[value] 

713 hints[name] = value 1abgcdef

714 return hints if include_extras else {k: typing._strip_annotations(t) for k, t in hints.items()} # type: ignore 1abgcdef