Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pydantic/_internal/_typing_extra.py: 58%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

217 statements  

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

2 

3from __future__ import annotations 

4 

5import collections.abc 

6import re 

7import sys 

8import types 

9import typing 

10from functools import partial 

11from inspect import Signature, signature 

12from typing import TYPE_CHECKING, Any, Callable, cast 

13 

14import typing_extensions 

15from typing_extensions import deprecated, get_args, get_origin 

16from typing_inspection import typing_objects 

17from typing_inspection.introspection import is_union_origin 

18 

19from pydantic.version import version_short 

20 

21from ._namespace_utils import GlobalsNamespace, MappingNamespace, NsResolver, get_module_ns_of 

22 

23if sys.version_info < (3, 10): 

24 NoneType = type(None) 

25 EllipsisType = type(Ellipsis) 

26else: 

27 from types import EllipsisType as EllipsisType 

28 from types import NoneType as NoneType 

29 

30if sys.version_info >= (3, 14): 

31 import annotationlib 

32 

33if TYPE_CHECKING: 

34 from pydantic import BaseModel 

35 from pydantic.fields import FieldInfo 

36 

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

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

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

40# docs for performance). 

41 

42 

43_t_annotated = typing.Annotated 

44_te_annotated = typing_extensions.Annotated 

45 

46 

47def is_annotated(tp: Any, /) -> bool: 

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

49 

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

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

52 #> True 

53 ``` 

54 """ 

55 origin = get_origin(tp) 

56 return origin is _t_annotated or origin is _te_annotated 

57 

58 

59def annotated_type(tp: Any, /) -> Any | None: 

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

61 return tp.__origin__ if typing_objects.is_annotated(get_origin(tp)) else None 

62 

63 

64def unpack_type(tp: Any, /) -> Any | None: 

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

66 return get_args(tp)[0] if typing_objects.is_unpack(get_origin(tp)) else None 

67 

68 

69def is_hashable(tp: Any, /) -> bool: 

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

71 

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

73 is_hashable(Hashable) 

74 #> True 

75 ``` 

76 """ 

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

78 # hence the second check: 

79 return tp is collections.abc.Hashable or get_origin(tp) is collections.abc.Hashable 

80 

81 

82def is_callable(tp: Any, /) -> bool: 

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

84 

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

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

87 #> True 

88 is_callable(typing.Callable) 

89 #> True 

90 is_callable(collections.abc.Callable) 

91 #> True 

92 ``` 

93 """ 

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

95 # hence the second check: 

96 return tp is collections.abc.Callable or get_origin(tp) is collections.abc.Callable 

97 

98 

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

100 

101 

102def is_classvar_annotation(tp: Any, /) -> bool: 

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

104 

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

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

107 

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

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

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

111 as accurate as possible. 

112 """ 

113 if typing_objects.is_classvar(tp): 

114 return True 

115 

116 origin = get_origin(tp) 

117 

118 if typing_objects.is_classvar(origin): 

119 return True 

120 

121 if typing_objects.is_annotated(origin): 

122 annotated_type = tp.__origin__ 

123 if typing_objects.is_classvar(annotated_type) or typing_objects.is_classvar(get_origin(annotated_type)): 

124 return True 

125 

126 str_ann: str | None = None 

127 if isinstance(tp, typing.ForwardRef): 

128 str_ann = tp.__forward_arg__ 

129 if isinstance(tp, str): 

130 str_ann = tp 

131 

132 if str_ann is not None and _classvar_re.match(str_ann): 

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

134 # (see `dataclass._is_type`). 

135 return True 

136 

137 return False 

138 

139 

140_t_final = typing.Final 

141_te_final = typing_extensions.Final 

142 

143 

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

145def is_finalvar(tp: Any, /) -> bool: 

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

147 

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

149 is_finalvar(Final[int]) 

150 #> True 

151 is_finalvar(Final) 

152 #> True 

153 """ 

154 # Final is not necessarily parametrized: 

155 if tp is _t_final or tp is _te_final: 

156 return True 

157 origin = get_origin(tp) 

158 return origin is _t_final or origin is _te_final 

159 

160 

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

162 

163 

164def is_none_type(tp: Any, /) -> bool: 

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

166 

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

168 is_none_type(None) 

169 #> True 

170 is_none_type(NoneType) 

171 #> True 

172 is_none_type(Literal[None]) 

173 #> True 

174 is_none_type(type[None]) 

175 #> False 

176 """ 

177 return tp in _NONE_TYPES 

178 

179 

180def is_namedtuple(tp: Any, /) -> bool: 

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

182 

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

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

185 """ 

186 from ._utils import lenient_issubclass # circ. import 

187 

188 return lenient_issubclass(tp, tuple) and hasattr(tp, '_fields') 

189 

190 

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

192# pydantic-settings which relies on it: 

193origin_is_union = is_union_origin 

194 

195 

196def is_generic_alias(tp: Any, /) -> bool: 

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

198 

199 

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

201 

202if sys.version_info < (3, 10): 

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

204else: 

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

206 

207 

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

209typing_base: Any = typing._Final # pyright: ignore[reportAttributeAccessIssue] 

210 

211 

212### Annotation evaluations functions: 

213 

214 

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

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

217 

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

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

220 such annotations: 

221 

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

223 from typing import get_type_hints 

224 

225 def func() -> None: 

226 Alias = int 

227 

228 class C: 

229 a: 'Alias' 

230 

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

232 get_type_hints(C) 

233 ``` 

234 

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

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

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

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

239 

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

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

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

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

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

245 

246 Args: 

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

248 is called will be used. 

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

250 

251 Returns: 

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

253 """ 

254 frame = sys._getframe(parent_depth) 

255 

256 if frame.f_code.co_name.startswith('<generic parameters of'): 

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

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

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

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

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

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

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

264 

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

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

267 if force: 

268 return frame.f_locals 

269 

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

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

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

273 if frame.f_back is None or frame.f_code.co_name == '<module>': 

274 return None 

275 

276 return frame.f_locals 

277 

278 

279def _type_convert(arg: Any) -> Any: 

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

281 

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

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

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

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

286 to the `typing.get_type_hints` function. 

287 """ 

288 if arg is None: 

289 return NoneType 

290 if isinstance(arg, str): 

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

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

293 return _make_forward_ref(arg, is_argument=False, is_class=True) 

294 return arg 

295 

296 

297def safe_get_annotations(obj: Any) -> dict[str, Any]: 

298 """Get the annotations for the provided object, accounting for potential deferred forward references. 

299 

300 Starting with Python 3.14, accessing the `__annotations__` attribute might raise a `NameError` if 

301 a referenced symbol isn't defined yet. In this case, we return the annotation in the *forward ref* 

302 format. 

303 """ 

304 if sys.version_info >= (3, 14): 

305 return annotationlib.get_annotations(obj, format=annotationlib.Format.FORWARDREF) 

306 else: 

307 # TODO just do getattr(obj, '__annotations__', {}) when dropping support for Python 3.9: 

308 if isinstance(obj, type): 

309 return obj.__dict__.get('__annotations__', {}) 

310 else: 

311 return getattr(obj, '__annotations__', {}) 

312 

313 

314def get_model_type_hints( 

315 model_class: type[BaseModel], 

316 *, 

317 ns_resolver: NsResolver | None = None, 

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

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

320 

321 Args: 

322 model_class: The Pydantic model class to inspect. 

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

324 

325 Returns: 

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

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

328 indicating if whether the evaluation succeeded. 

329 """ 

330 hints: dict[str, Any] | dict[str, tuple[Any, bool]] = {} 

331 ns_resolver = ns_resolver or NsResolver() 

332 

333 for base in reversed(model_class.__mro__[:-1]): 

334 # For Python 3.14, we could also use `Format.VALUE` and pass the globals/locals 

335 # from the ns_resolver, but we want to be able to know which specific field failed 

336 # to evaluate: 

337 ann = safe_get_annotations(base) 

338 

339 if not ann: 

340 continue 

341 

342 with ns_resolver.push(base): 

343 base_model_fields: dict[str, FieldInfo] | None = base.__dict__.get('__pydantic_fields__') 

344 

345 for name, value in ann.items(): 

346 if name.startswith('_'): 

347 globalns, localns = ns_resolver.types_namespace 

348 

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

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

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

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

353 # syntax, etc). 

354 try: 

355 hints[name] = try_eval_type(value, globalns, localns) 

356 except Exception: 

357 hints[name] = (value, False) 

358 else: 

359 if base_model_fields is not None and name in base_model_fields: 

360 # Avoid unnecessarily evaluating annotations from parent models, as we'll end up 

361 # copying the `FieldInfo` instance from it anyway if we need to. 

362 # We use the `annotation` attribute here, but in reality could put anything here, 

363 # As we are guaranteed to not make use of it: 

364 hints[name] = (base_model_fields[name].annotation, True) 

365 else: 

366 globalns, localns = ns_resolver.types_namespace 

367 

368 hints[name] = try_eval_type(value, globalns, localns) 

369 

370 return hints 

371 

372 

373def get_cls_type_hints( 

374 obj: type[Any], 

375 *, 

376 ns_resolver: NsResolver | None = None, 

377) -> dict[str, Any]: 

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

379 

380 Args: 

381 obj: The class to inspect. 

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

383 """ 

384 hints: dict[str, Any] = {} 

385 ns_resolver = ns_resolver or NsResolver() 

386 

387 for base in reversed(obj.__mro__): 

388 # For Python 3.14, we could also use `Format.VALUE` and pass the globals/locals 

389 # from the ns_resolver, but we want to be able to know which specific field failed 

390 # to evaluate: 

391 ann = safe_get_annotations(base) 

392 

393 if not ann: 

394 continue 

395 

396 with ns_resolver.push(base): 

397 globalns, localns = ns_resolver.types_namespace 

398 for name, value in ann.items(): 

399 hints[name] = eval_type(value, globalns, localns) 

400 return hints 

401 

402 

403def try_eval_type( 

404 value: Any, 

405 globalns: GlobalsNamespace | None = None, 

406 localns: MappingNamespace | None = None, 

407) -> tuple[Any, bool]: 

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

409 

410 Args: 

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

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

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

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

415 

416 Returns: 

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

418 whether the evaluation succeeded or not. 

419 """ 

420 value = _type_convert(value) 

421 

422 try: 

423 return eval_type_backport(value, globalns, localns), True 

424 except NameError: 

425 return value, False 

426 

427 

428def eval_type( 

429 value: Any, 

430 globalns: GlobalsNamespace | None = None, 

431 localns: MappingNamespace | None = None, 

432) -> Any: 

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

434 

435 Args: 

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

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

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

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

440 """ 

441 value = _type_convert(value) 

442 return eval_type_backport(value, globalns, localns) 

443 

444 

445@deprecated( 

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

447 category=None, 

448) 

449def eval_type_lenient( 

450 value: Any, 

451 globalns: GlobalsNamespace | None = None, 

452 localns: MappingNamespace | None = None, 

453) -> Any: 

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

455 return ev 

456 

457 

458def eval_type_backport( 

459 value: Any, 

460 globalns: GlobalsNamespace | None = None, 

461 localns: MappingNamespace | None = None, 

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

463) -> Any: 

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

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

466 

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

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

469 current Python version. 

470 

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

472 """ 

473 try: 

474 return _eval_type_backport(value, globalns, localns, type_params) 

475 except TypeError as e: 

476 if 'Unable to evaluate type annotation' in str(e): 

477 raise 

478 

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

480 # Thus we assert here for type checking purposes: 

481 assert isinstance(value, typing.ForwardRef) 

482 

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

484 if sys.version_info >= (3, 11): 

485 e.add_note(message) 

486 raise 

487 else: 

488 raise TypeError(message) from e 

489 except RecursionError as e: 

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

491 # is used directly in some places. 

492 message = ( 

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

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

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

496 ) 

497 if sys.version_info >= (3, 11): 

498 e.add_note(message) 

499 raise 

500 else: 

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

502 

503 

504def _eval_type_backport( 

505 value: Any, 

506 globalns: GlobalsNamespace | None = None, 

507 localns: MappingNamespace | None = None, 

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

509) -> Any: 

510 try: 

511 return _eval_type(value, globalns, localns, type_params) 

512 except TypeError as e: 

513 if not (isinstance(value, typing.ForwardRef) and is_backport_fixable_error(e)): 

514 raise 

515 

516 try: 

517 from eval_type_backport import eval_type_backport 

518 except ImportError: 

519 raise TypeError( 

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

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

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

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

524 ) from e 

525 

526 return eval_type_backport( 

527 value, 

528 globalns, 

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

530 try_default=False, 

531 ) 

532 

533 

534def _eval_type( 

535 value: Any, 

536 globalns: GlobalsNamespace | None = None, 

537 localns: MappingNamespace | None = None, 

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

539) -> Any: 

540 if sys.version_info >= (3, 14): 

541 # Starting in 3.14, `_eval_type()` does *not* apply `_type_convert()` 

542 # anymore. This means the `None` -> `type(None)` conversion does not apply: 

543 evaluated = typing._eval_type( # type: ignore 

544 value, 

545 globalns, 

546 localns, 

547 type_params=type_params, 

548 # This is relevant when evaluating types from `TypedDict` classes, where string annotations 

549 # are automatically converted to `ForwardRef` instances with a module set. In this case, 

550 # Our `globalns` is irrelevant and we need to indicate `typing._eval_type()` that it should 

551 # infer it from the `ForwardRef.__forward_module__` attribute instead (`typing.get_type_hints()` 

552 # does the same). Note that this would probably be unnecessary if we properly iterated over the 

553 # `__orig_bases__` for TypedDicts in `get_cls_type_hints()`: 

554 prefer_fwd_module=True, 

555 ) 

556 if evaluated is None: 

557 evaluated = type(None) 

558 return evaluated 

559 elif sys.version_info >= (3, 13): 

560 return typing._eval_type( # type: ignore 

561 value, globalns, localns, type_params=type_params 

562 ) 

563 else: 

564 return typing._eval_type( # type: ignore 

565 value, globalns, localns 

566 ) 

567 

568 

569def is_backport_fixable_error(e: TypeError) -> bool: 

570 msg = str(e) 

571 

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

573 

574 

575def signature_no_eval(f: Callable[..., Any]) -> Signature: 

576 """Get the signature of a callable without evaluating any annotations.""" 

577 if sys.version_info >= (3, 14): 

578 from annotationlib import Format 

579 

580 return signature(f, annotation_format=Format.FORWARDREF) 

581 else: 

582 return signature(f) 

583 

584 

585def get_function_type_hints( 

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

587 *, 

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

589 globalns: GlobalsNamespace | None = None, 

590 localns: MappingNamespace | None = None, 

591) -> dict[str, Any]: 

592 """Return type hints for a function. 

593 

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

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

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

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

598 """ 

599 if isinstance(function, partial): 

600 annotations = safe_get_annotations(function.func) 

601 else: 

602 annotations = safe_get_annotations(function) 

603 

604 if globalns is None: 

605 globalns = get_module_ns_of(function) 

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

607 if localns is None: 

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

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

610 type_params = getattr(function, '__type_params__', ()) 

611 

612 type_hints = {} 

613 for name, value in annotations.items(): 

614 if include_keys is not None and name not in include_keys: 

615 continue 

616 if value is None: 

617 value = NoneType 

618 elif isinstance(value, str): 

619 value = _make_forward_ref(value) 

620 

621 type_hints[name] = eval_type_backport(value, globalns, localns, type_params) 

622 

623 return type_hints 

624 

625 

626# TODO use typing.ForwardRef directly when we stop supporting 3.9: 

627if sys.version_info < (3, 9, 8) or (3, 10) <= sys.version_info < (3, 10, 1): 

628 

629 def _make_forward_ref( 

630 arg: Any, 

631 is_argument: bool = True, 

632 *, 

633 is_class: bool = False, 

634 ) -> typing.ForwardRef: 

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

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

637 

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

639 The backport happened on 3.9.8, see: 

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

641 and on 3.10.1 for the 3.10 branch, see: 

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

643 

644 Implemented as EAFP with memory. 

645 """ 

646 return typing.ForwardRef(arg, is_argument) # pyright: ignore[reportCallIssue] 

647 

648else: 

649 _make_forward_ref = typing.ForwardRef # pyright: ignore[reportAssignmentType] 

650 

651 

652if sys.version_info >= (3, 10): 

653 get_type_hints = typing.get_type_hints 

654 

655else: 

656 """ 

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

658 the implementation in CPython 3.10.8. 

659 """ 

660 

661 @typing.no_type_check 

662 def get_type_hints( # noqa: C901 

663 obj: Any, 

664 globalns: dict[str, Any] | None = None, 

665 localns: dict[str, Any] | None = None, 

666 include_extras: bool = False, 

667 ) -> dict[str, Any]: # pragma: no cover 

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

669 * type annotations of the function definition above. 

670 * prefixing `typing.` where appropriate 

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

672 

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

674 

675 DO NOT CHANGE THIS METHOD UNLESS ABSOLUTELY NECESSARY. 

676 ====================================================== 

677 

678 Return type hints for an object. 

679 

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

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

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

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

684 

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

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

687 inherited members. 

688 

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

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

691 present. 

692 

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

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

695 search order is locals first, then globals. 

696 

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

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

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

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

701 order is globals first then locals. 

702 

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

704 locals. 

705 

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

707 locals, respectively. 

708 """ 

709 if getattr(obj, '__no_type_check__', None): 

710 return {} 

711 # Classes require a special treatment. 

712 if isinstance(obj, type): 

713 hints = {} 

714 for base in reversed(obj.__mro__): 

715 if globalns is None: 

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

717 else: 

718 base_globals = globalns 

719 ann = base.__dict__.get('__annotations__', {}) 

720 if isinstance(ann, types.GetSetDescriptorType): 

721 ann = {} 

722 base_locals = dict(vars(base)) if localns is None else localns 

723 if localns is None and globalns is None: 

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

725 # get_type_hints only evaluated the globalns of 

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

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

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

729 # This only affects ForwardRefs. 

730 base_globals, base_locals = base_locals, base_globals 

731 for name, value in ann.items(): 

732 if value is None: 

733 value = type(None) 

734 if isinstance(value, str): 

735 value = _make_forward_ref(value, is_argument=False, is_class=True) 

736 

737 value = eval_type_backport(value, base_globals, base_locals) 

738 hints[name] = value 

739 if not include_extras and hasattr(typing, '_strip_annotations'): 

740 return { 

741 k: typing._strip_annotations(t) # type: ignore 

742 for k, t in hints.items() 

743 } 

744 else: 

745 return hints 

746 

747 if globalns is None: 

748 if isinstance(obj, types.ModuleType): 

749 globalns = obj.__dict__ 

750 else: 

751 nsobj = obj 

752 # Find globalns for the unwrapped object. 

753 while hasattr(nsobj, '__wrapped__'): 

754 nsobj = nsobj.__wrapped__ 

755 globalns = getattr(nsobj, '__globals__', {}) 

756 if localns is None: 

757 localns = globalns 

758 elif localns is None: 

759 localns = globalns 

760 hints = getattr(obj, '__annotations__', None) 

761 if hints is None: 

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

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

764 return {} 

765 else: 

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

767 defaults = typing._get_defaults(obj) # type: ignore 

768 hints = dict(hints) 

769 for name, value in hints.items(): 

770 if value is None: 

771 value = type(None) 

772 if isinstance(value, str): 

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

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

775 

776 value = _make_forward_ref( 

777 value, 

778 is_argument=not isinstance(obj, types.ModuleType), 

779 is_class=False, 

780 ) 

781 value = eval_type_backport(value, globalns, localns) 

782 if name in defaults and defaults[name] is None: 

783 value = typing.Optional[value] 

784 hints[name] = value 

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