Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/typing_inspection/typing_objects.py: 91%

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

140 statements  

1"""Low-level introspection utilities for [`typing`][] members. 

2 

3The provided functions in this module check against both the [`typing`][] and [`typing_extensions`][] 

4variants, if they exists and are different. 

5""" 

6# ruff: noqa: UP006 

7 

8import collections.abc 

9import contextlib 

10import re 

11import sys 

12import typing 

13import warnings 

14from textwrap import dedent 

15from types import FunctionType, GenericAlias, NoneType 

16from typing import Any, Final 

17 

18import typing_extensions 

19from typing_extensions import LiteralString, TypeAliasType, TypeIs, deprecated 

20 

21__all__ = ( 

22 'DEPRECATED_ALIASES', 

23 'DEPRECATED_ALIASES_IDS', 

24 'NoneType', 

25 'is_annotated', 

26 'is_any', 

27 'is_classvar', 

28 'is_concatenate', 

29 'is_deprecated', 

30 'is_final', 

31 'is_forwardref', 

32 'is_generic', 

33 'is_literal', 

34 'is_literalstring', 

35 'is_namedtuple', 

36 'is_never', 

37 'is_newtype', 

38 'is_nodefault', 

39 'is_noextraitems', 

40 'is_noreturn', 

41 'is_notrequired', 

42 'is_paramspec', 

43 'is_paramspecargs', 

44 'is_paramspeckwargs', 

45 'is_readonly', 

46 'is_required', 

47 'is_self', 

48 'is_typealias', 

49 'is_typealiastype', 

50 'is_typeguard', 

51 'is_typeis', 

52 'is_typevar', 

53 'is_typevartuple', 

54 'is_union', 

55 'is_unpack', 

56) 

57 

58_IS_PY310 = sys.version_info[:2] == (3, 10) 

59 

60 

61def _compile_identity_check_function(member: LiteralString, function_name: LiteralString) -> FunctionType: 

62 """Create a function checking that the function argument is the (unparameterized) typing `member`. 

63 

64 The function will make sure to check against both the `typing` and `typing_extensions` 

65 variants as depending on the Python version, the `typing_extensions` variant might be different. 

66 For instance, on Python 3.9: 

67 

68 ```pycon 

69 >>> from typing import Literal as t_Literal 

70 >>> from typing_extensions import Literal as te_Literal, get_origin 

71 

72 >>> t_Literal is te_Literal 

73 False 

74 >>> get_origin(t_Literal[1]) 

75 typing.Literal 

76 >>> get_origin(te_Literal[1]) 

77 typing_extensions.Literal 

78 ``` 

79 """ 

80 in_typing = hasattr(typing, member) 

81 in_typing_extensions = hasattr(typing_extensions, member) 

82 

83 globals_: dict[str, Any] = {'Any': Any} 

84 

85 if in_typing and in_typing_extensions: 

86 # For performance reasons, cache the objects in `globals_` so the generated function avoids 

87 # repeated module attribute lookups (and `typing`'s module-level `__getattr__()`): 

88 t_obj = getattr(typing, member) 

89 te_obj = getattr(typing_extensions, member) 

90 if t_obj is te_obj: 

91 globals_['_obj'] = t_obj 

92 check_code = 'obj is _obj' 

93 else: 

94 globals_['_t_obj'] = t_obj 

95 globals_['_te_obj'] = te_obj 

96 check_code = 'obj is _t_obj or obj is _te_obj' 

97 elif in_typing and not in_typing_extensions: 

98 globals_['_obj'] = getattr(typing, member) 

99 check_code = 'obj is _obj' 

100 elif not in_typing and in_typing_extensions: 

101 globals_['_obj'] = getattr(typing_extensions, member) 

102 check_code = 'obj is _obj' 

103 else: 

104 check_code = 'False' 

105 

106 func_code = dedent(f""" 

107 def {function_name}(obj: Any, /) -> bool: 

108 return {check_code} 

109 """) 

110 

111 locals_: dict[str, Any] = {} 

112 exec(func_code, globals_, locals_) 

113 return locals_[function_name] 

114 

115 

116def _compile_isinstance_check_function(member: LiteralString, function_name: LiteralString) -> FunctionType: 

117 """Create a function checking that the function is an instance of the typing `member`. 

118 

119 The function will make sure to check against both the `typing` and `typing_extensions` 

120 variants as depending on the Python version, the `typing_extensions` variant might be different. 

121 """ 

122 in_typing = hasattr(typing, member) 

123 in_typing_extensions = hasattr(typing_extensions, member) 

124 

125 globals_: dict[str, Any] = {'Any': Any} 

126 

127 if in_typing and in_typing_extensions: 

128 # For performance reasons, cache the objects in `globals_` so the generated function avoids 

129 # repeated module attribute lookups (and `typing`'s module-level `__getattr__()`): 

130 t_obj = getattr(typing, member) 

131 te_obj = getattr(typing_extensions, member) 

132 if t_obj is te_obj: 

133 globals_['_obj'] = t_obj 

134 check_code = 'isinstance(obj, _obj)' 

135 else: 

136 globals_['_objs'] = (t_obj, te_obj) 

137 check_code = 'isinstance(obj, _objs)' 

138 elif in_typing and not in_typing_extensions: 

139 globals_['_obj'] = getattr(typing, member) 

140 check_code = 'isinstance(obj, _obj)' 

141 elif not in_typing and in_typing_extensions: 

142 globals_['_obj'] = getattr(typing_extensions, member) 

143 check_code = 'isinstance(obj, _obj)' 

144 else: 

145 check_code = 'False' 

146 

147 func_code = dedent(f""" 

148 def {function_name}(obj: Any, /) -> 'TypeIs[{member}]': 

149 return {check_code} 

150 """) 

151 

152 locals_: dict[str, Any] = {} 

153 exec(func_code, globals_, locals_) 

154 return locals_[function_name] 

155 

156 

157# Keep this ordered, as per `typing.__all__`: 

158 

159is_annotated = _compile_identity_check_function('Annotated', 'is_annotated') 

160is_annotated.__doc__ = """ 

161Return whether the argument is the [`Annotated`][typing.Annotated] [special form][]. 

162 

163```pycon 

164>>> is_annotated(Annotated) 

165True 

166>>> is_annotated(Annotated[int, ...]) 

167False 

168``` 

169""" 

170 

171is_any = _compile_identity_check_function('Any', 'is_any') 

172is_any.__doc__ = """ 

173Return whether the argument is the [`Any`][typing.Any] [special form][]. 

174 

175```pycon 

176>>> is_any(Any) 

177True 

178``` 

179""" 

180 

181is_classvar = _compile_identity_check_function('ClassVar', 'is_classvar') 

182is_classvar.__doc__ = """ 

183Return whether the argument is the [`ClassVar`][typing.ClassVar] [type qualifier][]. 

184 

185```pycon 

186>>> is_classvar(ClassVar) 

187True 

188>>> is_classvar(ClassVar[int]) 

189>>> False 

190``` 

191""" 

192 

193is_concatenate = _compile_identity_check_function('Concatenate', 'is_concatenate') 

194is_concatenate.__doc__ = """ 

195Return whether the argument is the [`Concatenate`][typing.Concatenate] [special form][]. 

196 

197```pycon 

198>>> is_concatenate(Concatenate) 

199True 

200>>> is_concatenate(Concatenate[int, P]) 

201False 

202``` 

203""" 

204 

205is_final = _compile_identity_check_function('Final', 'is_final') 

206is_final.__doc__ = """ 

207Return whether the argument is the [`Final`][typing.Final] [type qualifier][]. 

208 

209```pycon 

210>>> is_final(Final) 

211True 

212>>> is_final(Final[int]) 

213False 

214``` 

215""" 

216 

217 

218# Unlikely to have a different version in `typing-extensions`, but keep it consistent. 

219# Also note that starting in 3.14, this is an alias to `annotationlib.ForwardRef`, but 

220# accessing it from `typing` doesn't seem to be deprecated. 

221is_forwardref = _compile_isinstance_check_function('ForwardRef', 'is_forwardref') 

222is_forwardref.__doc__ = """ 

223Return whether the argument is an instance of [`ForwardRef`][typing.ForwardRef]. 

224 

225```pycon 

226>>> is_forwardref(ForwardRef('T')) 

227True 

228``` 

229""" 

230 

231 

232is_generic = _compile_identity_check_function('Generic', 'is_generic') 

233is_generic.__doc__ = """ 

234Return whether the argument is the [`Generic`][typing.Generic] [special form][]. 

235 

236```pycon 

237>>> is_generic(Generic) 

238True 

239>>> is_generic(Generic[T]) 

240False 

241``` 

242""" 

243 

244is_literal = _compile_identity_check_function('Literal', 'is_literal') 

245is_literal.__doc__ = """ 

246Return whether the argument is the [`Literal`][typing.Literal] [special form][]. 

247 

248```pycon 

249>>> is_literal(Literal) 

250True 

251>>> is_literal(Literal["a"]) 

252False 

253``` 

254""" 

255 

256 

257# `get_origin(Optional[int]) is Union`, so `is_optional()` isn't implemented. 

258 

259is_paramspec = _compile_isinstance_check_function('ParamSpec', 'is_paramspec') 

260is_paramspec.__doc__ = """ 

261Return whether the argument is an instance of [`ParamSpec`][typing.ParamSpec]. 

262 

263```pycon 

264>>> P = ParamSpec('P') 

265>>> is_paramspec(P) 

266True 

267``` 

268""" 

269 

270# Protocol? 

271 

272is_typevar = _compile_isinstance_check_function('TypeVar', 'is_typevar') 

273is_typevar.__doc__ = """ 

274Return whether the argument is an instance of [`TypeVar`][typing.TypeVar]. 

275 

276```pycon 

277>>> T = TypeVar('T') 

278>>> is_typevar(T) 

279True 

280``` 

281""" 

282 

283is_typevartuple = _compile_isinstance_check_function('TypeVarTuple', 'is_typevartuple') 

284is_typevartuple.__doc__ = """ 

285Return whether the argument is an instance of [`TypeVarTuple`][typing.TypeVarTuple]. 

286 

287```pycon 

288>>> Ts = TypeVarTuple('Ts') 

289>>> is_typevartuple(Ts) 

290True 

291``` 

292""" 

293 

294is_union = _compile_identity_check_function('Union', 'is_union') 

295is_union.__doc__ = """ 

296Return whether the argument is the [`Union`][typing.Union] [special form][]. 

297 

298This function can also be used to check for the [`Optional`][typing.Optional] [special form][], 

299as at runtime, `Optional[int]` is equivalent to `Union[int, None]`. 

300 

301```pycon 

302>>> is_union(Union) 

303True 

304>>> is_union(Union[int, str]) 

305False 

306``` 

307 

308!!! warning 

309 This does not check for unions using the [new syntax][types-union] (e.g. `int | str`). 

310""" 

311 

312 

313def is_namedtuple(obj: Any, /) -> bool: 

314 """Return whether the argument is a named tuple type. 

315 

316 This includes [`NamedTuple`][typing.NamedTuple] subclasses and classes created from the 

317 [`collections.namedtuple`][] factory function. 

318 

319 ```pycon 

320 >>> class User(NamedTuple): 

321 ... name: str 

322 ... 

323 >>> is_namedtuple(User) 

324 True 

325 >>> City = collections.namedtuple('City', []) 

326 >>> is_namedtuple(City) 

327 True 

328 >>> is_namedtuple(NamedTuple) 

329 False 

330 ``` 

331 """ 

332 return isinstance(obj, type) and issubclass(obj, tuple) and hasattr(obj, '_fields') # pyright: ignore[reportUnknownArgumentType] 

333 

334 

335# TypedDict? 

336 

337# BinaryIO? IO? TextIO? 

338 

339is_literalstring = _compile_identity_check_function('LiteralString', 'is_literalstring') 

340is_literalstring.__doc__ = """ 

341Return whether the argument is the [`LiteralString`][typing.LiteralString] [special form][]. 

342 

343```pycon 

344>>> is_literalstring(LiteralString) 

345True 

346``` 

347""" 

348 

349is_never = _compile_identity_check_function('Never', 'is_never') 

350is_never.__doc__ = """ 

351Return whether the argument is the [`Never`][typing.Never] [special form][]. 

352 

353```pycon 

354>>> is_never(Never) 

355True 

356``` 

357""" 

358 

359is_newtype = _compile_isinstance_check_function('NewType', 'is_newtype') 

360is_newtype.__doc__ = """ 

361Return whether the argument is a [`NewType`][typing.NewType]. 

362 

363```pycon 

364>>> UserId = NewType("UserId", int) 

365>>> is_newtype(UserId) 

366True 

367``` 

368""" 

369 

370is_nodefault = _compile_identity_check_function('NoDefault', 'is_nodefault') 

371is_nodefault.__doc__ = """ 

372Return whether the argument is the [`NoDefault`][typing.NoDefault] sentinel object. 

373 

374```pycon 

375>>> is_nodefault(NoDefault) 

376True 

377``` 

378""" 

379 

380is_noextraitems = _compile_identity_check_function('NoExtraItems', 'is_noextraitems') 

381is_noextraitems.__doc__ = """ 

382Return whether the argument is the `NoExtraItems` sentinel object. 

383 

384```pycon 

385>>> is_noextraitems(NoExtraItems) 

386True 

387``` 

388""" 

389 

390is_noreturn = _compile_identity_check_function('NoReturn', 'is_noreturn') 

391is_noreturn.__doc__ = """ 

392Return whether the argument is the [`NoReturn`][typing.NoReturn] [special form][]. 

393 

394```pycon 

395>>> is_noreturn(NoReturn) 

396True 

397>>> is_noreturn(Never) 

398False 

399``` 

400""" 

401 

402is_notrequired = _compile_identity_check_function('NotRequired', 'is_notrequired') 

403is_notrequired.__doc__ = """ 

404Return whether the argument is the [`NotRequired`][typing.NotRequired] [special form][]. 

405 

406```pycon 

407>>> is_notrequired(NotRequired) 

408True 

409``` 

410""" 

411 

412is_paramspecargs = _compile_isinstance_check_function('ParamSpecArgs', 'is_paramspecargs') 

413is_paramspecargs.__doc__ = """ 

414Return whether the argument is an instance of [`ParamSpecArgs`][typing.ParamSpecArgs]. 

415 

416```pycon 

417>>> P = ParamSpec('P') 

418>>> is_paramspecargs(P.args) 

419True 

420``` 

421""" 

422 

423is_paramspeckwargs = _compile_isinstance_check_function('ParamSpecKwargs', 'is_paramspeckwargs') 

424is_paramspeckwargs.__doc__ = """ 

425Return whether the argument is an instance of [`ParamSpecKwargs`][typing.ParamSpecKwargs]. 

426 

427```pycon 

428>>> P = ParamSpec('P') 

429>>> is_paramspeckwargs(P.kwargs) 

430True 

431``` 

432""" 

433 

434is_readonly = _compile_identity_check_function('ReadOnly', 'is_readonly') 

435is_readonly.__doc__ = """ 

436Return whether the argument is the [`ReadOnly`][typing.ReadOnly] [special form][]. 

437 

438```pycon 

439>>> is_readonly(ReadOnly) 

440True 

441``` 

442""" 

443 

444is_required = _compile_identity_check_function('Required', 'is_required') 

445is_required.__doc__ = """ 

446Return whether the argument is the [`Required`][typing.Required] [special form][]. 

447 

448```pycon 

449>>> is_required(Required) 

450True 

451``` 

452""" 

453 

454is_self = _compile_identity_check_function('Self', 'is_self') 

455is_self.__doc__ = """ 

456Return whether the argument is the [`Self`][typing.Self] [special form][]. 

457 

458```pycon 

459>>> is_self(Self) 

460True 

461``` 

462""" 

463 

464# TYPE_CHECKING? 

465 

466is_typealias = _compile_identity_check_function('TypeAlias', 'is_typealias') 

467is_typealias.__doc__ = """ 

468Return whether the argument is the [`TypeAlias`][typing.TypeAlias] [special form][]. 

469 

470```pycon 

471>>> is_typealias(TypeAlias) 

472True 

473``` 

474""" 

475 

476is_typeguard = _compile_identity_check_function('TypeGuard', 'is_typeguard') 

477is_typeguard.__doc__ = """ 

478Return whether the argument is the [`TypeGuard`][typing.TypeGuard] [special form][]. 

479 

480```pycon 

481>>> is_typeguard(TypeGuard) 

482True 

483``` 

484""" 

485 

486is_typeis = _compile_identity_check_function('TypeIs', 'is_typeis') 

487is_typeis.__doc__ = """ 

488Return whether the argument is the [`TypeIs`][typing.TypeIs] [special form][]. 

489 

490```pycon 

491>>> is_typeis(TypeIs) 

492True 

493``` 

494""" 

495 

496_is_typealiastype_inner = _compile_isinstance_check_function('TypeAliasType', '_is_typealiastype_inner') 

497 

498 

499if _IS_PY310: 

500 # Parameterized PEP 695 type aliases are instances of `types.GenericAlias` in typing_extensions>=4.13.0. 

501 # On Python 3.10, with `Alias[int]` being such an instance of `GenericAlias`, 

502 # `isinstance(Alias[int], TypeAliasType)` returns `True`. 

503 # See https://github.com/python/cpython/issues/89828. 

504 def is_typealiastype(obj: Any, /) -> 'TypeIs[TypeAliasType]': 

505 return type(obj) is not GenericAlias and _is_typealiastype_inner(obj) 

506else: 

507 is_typealiastype = _compile_isinstance_check_function('TypeAliasType', 'is_typealiastype') 

508 

509is_typealiastype.__doc__ = """ 

510Return whether the argument is a [`TypeAliasType`][typing.TypeAliasType] instance. 

511 

512```pycon 

513>>> type MyInt = int 

514>>> is_typealiastype(MyInt) 

515True 

516>>> MyStr = TypeAliasType("MyStr", str) 

517>>> is_typealiastype(MyStr): 

518True 

519>>> type MyList[T] = list[T] 

520>>> is_typealiastype(MyList[int]) 

521False 

522``` 

523""" 

524 

525is_unpack = _compile_identity_check_function('Unpack', 'is_unpack') 

526is_unpack.__doc__ = """ 

527Return whether the argument is the [`Unpack`][typing.Unpack] [special form][]. 

528 

529```pycon 

530>>> is_unpack(Unpack) 

531True 

532>>> is_unpack(Unpack[Ts]) 

533False 

534``` 

535""" 

536 

537 

538if sys.version_info >= (3, 13): 

539 _deprecated_types = (warnings.deprecated, typing_extensions.deprecated) 

540 

541 def is_deprecated(obj: Any, /) -> 'TypeIs[deprecated]': 

542 return isinstance(obj, _deprecated_types) 

543 

544else: 

545 _deprecated_type = typing_extensions.deprecated 

546 

547 def is_deprecated(obj: Any, /) -> 'TypeIs[deprecated]': 

548 return isinstance(obj, _deprecated_type) 

549 

550 

551is_deprecated.__doc__ = """ 

552Return whether the argument is a [`deprecated`][warnings.deprecated] instance. 

553 

554This also includes the [`typing_extensions` backport][typing_extensions.deprecated]. 

555 

556```pycon 

557>>> is_deprecated(warnings.deprecated('message')) 

558True 

559>>> is_deprecated(typing_extensions.deprecated('message')) 

560True 

561``` 

562""" 

563 

564 

565# Aliases defined in the `typing` module using `typing._SpecialGenericAlias` (itself aliased as `alias()`): 

566DEPRECATED_ALIASES: Final[dict[Any, type[Any]]] = { 

567 typing.Hashable: collections.abc.Hashable, 

568 typing.Awaitable: collections.abc.Awaitable, 

569 typing.Coroutine: collections.abc.Coroutine, 

570 typing.AsyncIterable: collections.abc.AsyncIterable, 

571 typing.AsyncIterator: collections.abc.AsyncIterator, 

572 typing.Iterable: collections.abc.Iterable, 

573 typing.Iterator: collections.abc.Iterator, 

574 typing.Reversible: collections.abc.Reversible, 

575 typing.Sized: collections.abc.Sized, 

576 typing.Container: collections.abc.Container, 

577 typing.Collection: collections.abc.Collection, 

578 # type ignore reason: https://github.com/python/typeshed/issues/6257: 

579 typing.Callable: collections.abc.Callable, # pyright: ignore[reportAssignmentType, reportUnknownMemberType] 

580 typing.AbstractSet: collections.abc.Set, 

581 typing.MutableSet: collections.abc.MutableSet, 

582 typing.Mapping: collections.abc.Mapping, 

583 typing.MutableMapping: collections.abc.MutableMapping, 

584 typing.Sequence: collections.abc.Sequence, 

585 typing.MutableSequence: collections.abc.MutableSequence, 

586 typing.Tuple: tuple, 

587 typing.List: list, 

588 typing.Deque: collections.deque, 

589 typing.Set: set, 

590 typing.FrozenSet: frozenset, 

591 typing.MappingView: collections.abc.MappingView, 

592 typing.KeysView: collections.abc.KeysView, 

593 typing.ItemsView: collections.abc.ItemsView, 

594 typing.ValuesView: collections.abc.ValuesView, 

595 typing.Dict: dict, 

596 typing.DefaultDict: collections.defaultdict, 

597 typing.OrderedDict: collections.OrderedDict, 

598 typing.Counter: collections.Counter, 

599 typing.ChainMap: collections.ChainMap, 

600 typing.Generator: collections.abc.Generator, 

601 typing.AsyncGenerator: collections.abc.AsyncGenerator, 

602 typing.Type: type, 

603 # Defined in `typing.__getattr__`: 

604 typing.Pattern: re.Pattern, 

605 typing.Match: re.Match, 

606 typing.ContextManager: contextlib.AbstractContextManager, 

607 typing.AsyncContextManager: contextlib.AbstractAsyncContextManager, 

608 # Skipped: `ByteString` (deprecated, removed in 3.14) 

609} 

610"""A mapping between the deprecated typing aliases to their replacement, as per [PEP 585](https://peps.python.org/pep-0585/).""" 

611 

612 

613DEPRECATED_ALIASES_IDS: Final[dict[int, type[Any]]] = {id(k): v for k, v in DEPRECATED_ALIASES.items()} 

614"""A mapping between the [identity][id] of the deprecated typing aliases to their replacement, as per [PEP 585](https://peps.python.org/pep-0585/).""" 

615 

616 

617# Add the `typing_extensions` aliases: 

618for alias, target in list(DEPRECATED_ALIASES.items()): 

619 if (te_alias := getattr(typing_extensions, alias.__name__, None)) is not None: 

620 DEPRECATED_ALIASES[te_alias] = target