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

129 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 

16from typing import Any, Final 

17 

18import typing_extensions 

19from typing_extensions import LiteralString, TypeAliasType, TypeIs, deprecated 

20 

21__all__ = ( 

22 'DEPRECATED_ALIASES', 

23 'NoneType', 

24 'is_annotated', 

25 'is_any', 

26 'is_classvar', 

27 'is_concatenate', 

28 'is_deprecated', 

29 'is_final', 

30 'is_forwardref', 

31 'is_generic', 

32 'is_literal', 

33 'is_literalstring', 

34 'is_namedtuple', 

35 'is_never', 

36 'is_newtype', 

37 'is_nodefault', 

38 'is_noextraitems', 

39 'is_noreturn', 

40 'is_notrequired', 

41 'is_paramspec', 

42 'is_paramspecargs', 

43 'is_paramspeckwargs', 

44 'is_readonly', 

45 'is_required', 

46 'is_self', 

47 'is_typealias', 

48 'is_typealiastype', 

49 'is_typeguard', 

50 'is_typeis', 

51 'is_typevar', 

52 'is_typevartuple', 

53 'is_union', 

54 'is_unpack', 

55) 

56 

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

58 

59 

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

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

62 

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

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

65 For instance, on Python 3.9: 

66 

67 ```pycon 

68 >>> from typing import Literal as t_Literal 

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

70 

71 >>> t_Literal is te_Literal 

72 False 

73 >>> get_origin(t_Literal[1]) 

74 typing.Literal 

75 >>> get_origin(te_Literal[1]) 

76 typing_extensions.Literal 

77 ``` 

78 """ 

79 in_typing = hasattr(typing, member) 

80 in_typing_extensions = hasattr(typing_extensions, member) 

81 

82 if in_typing and in_typing_extensions: 

83 if getattr(typing, member) is getattr(typing_extensions, member): 

84 check_code = f'obj is typing.{member}' 

85 else: 

86 check_code = f'obj is typing.{member} or obj is typing_extensions.{member}' 

87 elif in_typing and not in_typing_extensions: 

88 check_code = f'obj is typing.{member}' 

89 elif not in_typing and in_typing_extensions: 

90 check_code = f'obj is typing_extensions.{member}' 

91 else: 

92 check_code = 'False' 

93 

94 func_code = dedent(f""" 

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

96 return {check_code} 

97 """) 

98 

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

100 globals_: dict[str, Any] = {'Any': Any, 'typing': typing, 'typing_extensions': typing_extensions} 

101 exec(func_code, globals_, locals_) 

102 return locals_[function_name] 

103 

104 

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

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

107 

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

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

110 """ 

111 in_typing = hasattr(typing, member) 

112 in_typing_extensions = hasattr(typing_extensions, member) 

113 

114 if in_typing and in_typing_extensions: 

115 if getattr(typing, member) is getattr(typing_extensions, member): 

116 check_code = f'isinstance(obj, typing.{member})' 

117 else: 

118 check_code = f'isinstance(obj, (typing.{member}, typing_extensions.{member}))' 

119 elif in_typing and not in_typing_extensions: 

120 check_code = f'isinstance(obj, typing.{member})' 

121 elif not in_typing and in_typing_extensions: 

122 check_code = f'isinstance(obj, typing_extensions.{member})' 

123 else: 

124 check_code = 'False' 

125 

126 func_code = dedent(f""" 

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

128 return {check_code} 

129 """) 

130 

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

132 globals_: dict[str, Any] = {'Any': Any, 'typing': typing, 'typing_extensions': typing_extensions} 

133 exec(func_code, globals_, locals_) 

134 return locals_[function_name] 

135 

136 

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

138 from types import NoneType 

139else: 

140 NoneType = type(None) 

141 

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

143 

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

145is_annotated.__doc__ = """ 

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

147 

148```pycon 

149>>> is_annotated(Annotated) 

150True 

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

152False 

153``` 

154""" 

155 

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

157is_any.__doc__ = """ 

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

159 

160```pycon 

161>>> is_any(Any) 

162True 

163``` 

164""" 

165 

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

167is_classvar.__doc__ = """ 

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

169 

170```pycon 

171>>> is_classvar(ClassVar) 

172True 

173>>> is_classvar(ClassVar[int]) 

174>>> False 

175``` 

176""" 

177 

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

179is_concatenate.__doc__ = """ 

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

181 

182```pycon 

183>>> is_concatenate(Concatenate) 

184True 

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

186False 

187``` 

188""" 

189 

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

191is_final.__doc__ = """ 

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

193 

194```pycon 

195>>> is_final(Final) 

196True 

197>>> is_final(Final[int]) 

198False 

199``` 

200""" 

201 

202 

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

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

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

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

207is_forwardref.__doc__ = """ 

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

209 

210```pycon 

211>>> is_forwardref(ForwardRef('T')) 

212True 

213``` 

214""" 

215 

216 

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

218is_generic.__doc__ = """ 

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

220 

221```pycon 

222>>> is_generic(Generic) 

223True 

224>>> is_generic(Generic[T]) 

225False 

226``` 

227""" 

228 

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

230is_literal.__doc__ = """ 

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

232 

233```pycon 

234>>> is_literal(Literal) 

235True 

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

237False 

238``` 

239""" 

240 

241 

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

243 

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

245is_paramspec.__doc__ = """ 

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

247 

248```pycon 

249>>> P = ParamSpec('P') 

250>>> is_paramspec(P) 

251True 

252``` 

253""" 

254 

255# Protocol? 

256 

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

258is_typevar.__doc__ = """ 

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

260 

261```pycon 

262>>> T = TypeVar('T') 

263>>> is_typevar(T) 

264True 

265``` 

266""" 

267 

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

269is_typevartuple.__doc__ = """ 

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

271 

272```pycon 

273>>> Ts = TypeVarTuple('Ts') 

274>>> is_typevartuple(Ts) 

275True 

276``` 

277""" 

278 

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

280is_union.__doc__ = """ 

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

282 

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

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

285 

286```pycon 

287>>> is_union(Union) 

288True 

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

290False 

291``` 

292 

293!!! warning 

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

295""" 

296 

297 

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

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

300 

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

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

303 

304 ```pycon 

305 >>> class User(NamedTuple): 

306 ... name: str 

307 ... 

308 >>> is_namedtuple(User) 

309 True 

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

311 >>> is_namedtuple(City) 

312 True 

313 >>> is_namedtuple(NamedTuple) 

314 False 

315 ``` 

316 """ 

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

318 

319 

320# TypedDict? 

321 

322# BinaryIO? IO? TextIO? 

323 

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

325is_literalstring.__doc__ = """ 

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

327 

328```pycon 

329>>> is_literalstring(LiteralString) 

330True 

331``` 

332""" 

333 

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

335is_never.__doc__ = """ 

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

337 

338```pycon 

339>>> is_never(Never) 

340True 

341``` 

342""" 

343 

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

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

346else: # On Python 3.10, `NewType` is a function. 

347 

348 def is_newtype(obj: Any, /) -> bool: 

349 return hasattr(obj, '__supertype__') 

350 

351 

352is_newtype.__doc__ = """ 

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

354 

355```pycon 

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

357>>> is_newtype(UserId) 

358True 

359``` 

360""" 

361 

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

363is_nodefault.__doc__ = """ 

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

365 

366```pycon 

367>>> is_nodefault(NoDefault) 

368True 

369``` 

370""" 

371 

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

373is_noextraitems.__doc__ = """ 

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

375 

376```pycon 

377>>> is_noextraitems(NoExtraItems) 

378True 

379``` 

380""" 

381 

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

383is_noreturn.__doc__ = """ 

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

385 

386```pycon 

387>>> is_noreturn(NoReturn) 

388True 

389>>> is_noreturn(Never) 

390False 

391``` 

392""" 

393 

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

395is_notrequired.__doc__ = """ 

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

397 

398```pycon 

399>>> is_notrequired(NotRequired) 

400True 

401``` 

402""" 

403 

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

405is_paramspecargs.__doc__ = """ 

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

407 

408```pycon 

409>>> P = ParamSpec('P') 

410>>> is_paramspecargs(P.args) 

411True 

412``` 

413""" 

414 

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

416is_paramspeckwargs.__doc__ = """ 

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

418 

419```pycon 

420>>> P = ParamSpec('P') 

421>>> is_paramspeckwargs(P.kwargs) 

422True 

423``` 

424""" 

425 

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

427is_readonly.__doc__ = """ 

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

429 

430```pycon 

431>>> is_readonly(ReadOnly) 

432True 

433``` 

434""" 

435 

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

437is_required.__doc__ = """ 

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

439 

440```pycon 

441>>> is_required(Required) 

442True 

443``` 

444""" 

445 

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

447is_self.__doc__ = """ 

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

449 

450```pycon 

451>>> is_self(Self) 

452True 

453``` 

454""" 

455 

456# TYPE_CHECKING? 

457 

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

459is_typealias.__doc__ = """ 

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

461 

462```pycon 

463>>> is_typealias(TypeAlias) 

464True 

465``` 

466""" 

467 

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

469is_typeguard.__doc__ = """ 

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

471 

472```pycon 

473>>> is_typeguard(TypeGuard) 

474True 

475``` 

476""" 

477 

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

479is_typeis.__doc__ = """ 

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

481 

482```pycon 

483>>> is_typeis(TypeIs) 

484True 

485``` 

486""" 

487 

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

489 

490 

491if _IS_PY310: 

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

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

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

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

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

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

498else: 

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

500 

501is_typealiastype.__doc__ = """ 

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

503 

504```pycon 

505>>> type MyInt = int 

506>>> is_typealiastype(MyInt) 

507True 

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

509>>> is_typealiastype(MyStr): 

510True 

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

512>>> is_typealiastype(MyList[int]) 

513False 

514``` 

515""" 

516 

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

518is_unpack.__doc__ = """ 

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

520 

521```pycon 

522>>> is_unpack(Unpack) 

523True 

524>>> is_unpack(Unpack[Ts]) 

525False 

526``` 

527""" 

528 

529 

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

531 

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

533 return isinstance(obj, (warnings.deprecated, typing_extensions.deprecated)) 

534 

535else: 

536 

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

538 return isinstance(obj, typing_extensions.deprecated) 

539 

540 

541is_deprecated.__doc__ = """ 

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

543 

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

545 

546```pycon 

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

548True 

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

550True 

551``` 

552""" 

553 

554 

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

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

557 typing.Hashable: collections.abc.Hashable, 

558 typing.Awaitable: collections.abc.Awaitable, 

559 typing.Coroutine: collections.abc.Coroutine, 

560 typing.AsyncIterable: collections.abc.AsyncIterable, 

561 typing.AsyncIterator: collections.abc.AsyncIterator, 

562 typing.Iterable: collections.abc.Iterable, 

563 typing.Iterator: collections.abc.Iterator, 

564 typing.Reversible: collections.abc.Reversible, 

565 typing.Sized: collections.abc.Sized, 

566 typing.Container: collections.abc.Container, 

567 typing.Collection: collections.abc.Collection, 

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

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

570 typing.AbstractSet: collections.abc.Set, 

571 typing.MutableSet: collections.abc.MutableSet, 

572 typing.Mapping: collections.abc.Mapping, 

573 typing.MutableMapping: collections.abc.MutableMapping, 

574 typing.Sequence: collections.abc.Sequence, 

575 typing.MutableSequence: collections.abc.MutableSequence, 

576 typing.Tuple: tuple, 

577 typing.List: list, 

578 typing.Deque: collections.deque, 

579 typing.Set: set, 

580 typing.FrozenSet: frozenset, 

581 typing.MappingView: collections.abc.MappingView, 

582 typing.KeysView: collections.abc.KeysView, 

583 typing.ItemsView: collections.abc.ItemsView, 

584 typing.ValuesView: collections.abc.ValuesView, 

585 typing.Dict: dict, 

586 typing.DefaultDict: collections.defaultdict, 

587 typing.OrderedDict: collections.OrderedDict, 

588 typing.Counter: collections.Counter, 

589 typing.ChainMap: collections.ChainMap, 

590 typing.Generator: collections.abc.Generator, 

591 typing.AsyncGenerator: collections.abc.AsyncGenerator, 

592 typing.Type: type, 

593 # Defined in `typing.__getattr__`: 

594 typing.Pattern: re.Pattern, 

595 typing.Match: re.Match, 

596 typing.ContextManager: contextlib.AbstractContextManager, 

597 typing.AsyncContextManager: contextlib.AbstractAsyncContextManager, 

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

599} 

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

601 

602 

603# Add the `typing_extensions` aliases: 

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

605 # Use `alias.__name__` when we drop support for Python 3.9 

606 if (te_alias := getattr(typing_extensions, alias._name, None)) is not None: 

607 DEPRECATED_ALIASES[te_alias] = target