typing_objects.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620
  1. """Low-level introspection utilities for [`typing`][] members.
  2. The provided functions in this module check against both the [`typing`][] and [`typing_extensions`][]
  3. variants, if they exists and are different.
  4. """
  5. # ruff: noqa: UP006
  6. import collections.abc
  7. import contextlib
  8. import re
  9. import sys
  10. import typing
  11. import warnings
  12. from textwrap import dedent
  13. from types import FunctionType, GenericAlias, NoneType
  14. from typing import Any, Final
  15. import typing_extensions
  16. from typing_extensions import LiteralString, TypeAliasType, TypeIs, deprecated
  17. __all__ = (
  18. 'DEPRECATED_ALIASES',
  19. 'DEPRECATED_ALIASES_IDS',
  20. 'NoneType',
  21. 'is_annotated',
  22. 'is_any',
  23. 'is_classvar',
  24. 'is_concatenate',
  25. 'is_deprecated',
  26. 'is_final',
  27. 'is_forwardref',
  28. 'is_generic',
  29. 'is_literal',
  30. 'is_literalstring',
  31. 'is_namedtuple',
  32. 'is_never',
  33. 'is_newtype',
  34. 'is_nodefault',
  35. 'is_noextraitems',
  36. 'is_noreturn',
  37. 'is_notrequired',
  38. 'is_paramspec',
  39. 'is_paramspecargs',
  40. 'is_paramspeckwargs',
  41. 'is_readonly',
  42. 'is_required',
  43. 'is_self',
  44. 'is_typealias',
  45. 'is_typealiastype',
  46. 'is_typeguard',
  47. 'is_typeis',
  48. 'is_typevar',
  49. 'is_typevartuple',
  50. 'is_union',
  51. 'is_unpack',
  52. )
  53. _IS_PY310 = sys.version_info[:2] == (3, 10)
  54. def _compile_identity_check_function(member: LiteralString, function_name: LiteralString) -> FunctionType:
  55. """Create a function checking that the function argument is the (unparameterized) typing `member`.
  56. The function will make sure to check against both the `typing` and `typing_extensions`
  57. variants as depending on the Python version, the `typing_extensions` variant might be different.
  58. For instance, on Python 3.9:
  59. ```pycon
  60. >>> from typing import Literal as t_Literal
  61. >>> from typing_extensions import Literal as te_Literal, get_origin
  62. >>> t_Literal is te_Literal
  63. False
  64. >>> get_origin(t_Literal[1])
  65. typing.Literal
  66. >>> get_origin(te_Literal[1])
  67. typing_extensions.Literal
  68. ```
  69. """
  70. in_typing = hasattr(typing, member)
  71. in_typing_extensions = hasattr(typing_extensions, member)
  72. globals_: dict[str, Any] = {'Any': Any}
  73. if in_typing and in_typing_extensions:
  74. # For performance reasons, cache the objects in `globals_` so the generated function avoids
  75. # repeated module attribute lookups (and `typing`'s module-level `__getattr__()`):
  76. t_obj = getattr(typing, member)
  77. te_obj = getattr(typing_extensions, member)
  78. if t_obj is te_obj:
  79. globals_['_obj'] = t_obj
  80. check_code = 'obj is _obj'
  81. else:
  82. globals_['_t_obj'] = t_obj
  83. globals_['_te_obj'] = te_obj
  84. check_code = 'obj is _t_obj or obj is _te_obj'
  85. elif in_typing and not in_typing_extensions:
  86. globals_['_obj'] = getattr(typing, member)
  87. check_code = 'obj is _obj'
  88. elif not in_typing and in_typing_extensions:
  89. globals_['_obj'] = getattr(typing_extensions, member)
  90. check_code = 'obj is _obj'
  91. else:
  92. check_code = 'False'
  93. func_code = dedent(f"""
  94. def {function_name}(obj: Any, /) -> bool:
  95. return {check_code}
  96. """)
  97. locals_: dict[str, Any] = {}
  98. exec(func_code, globals_, locals_)
  99. return locals_[function_name]
  100. def _compile_isinstance_check_function(member: LiteralString, function_name: LiteralString) -> FunctionType:
  101. """Create a function checking that the function is an instance of the typing `member`.
  102. The function will make sure to check against both the `typing` and `typing_extensions`
  103. variants as depending on the Python version, the `typing_extensions` variant might be different.
  104. """
  105. in_typing = hasattr(typing, member)
  106. in_typing_extensions = hasattr(typing_extensions, member)
  107. globals_: dict[str, Any] = {'Any': Any}
  108. if in_typing and in_typing_extensions:
  109. # For performance reasons, cache the objects in `globals_` so the generated function avoids
  110. # repeated module attribute lookups (and `typing`'s module-level `__getattr__()`):
  111. t_obj = getattr(typing, member)
  112. te_obj = getattr(typing_extensions, member)
  113. if t_obj is te_obj:
  114. globals_['_obj'] = t_obj
  115. check_code = 'isinstance(obj, _obj)'
  116. else:
  117. globals_['_objs'] = (t_obj, te_obj)
  118. check_code = 'isinstance(obj, _objs)'
  119. elif in_typing and not in_typing_extensions:
  120. globals_['_obj'] = getattr(typing, member)
  121. check_code = 'isinstance(obj, _obj)'
  122. elif not in_typing and in_typing_extensions:
  123. globals_['_obj'] = getattr(typing_extensions, member)
  124. check_code = 'isinstance(obj, _obj)'
  125. else:
  126. check_code = 'False'
  127. func_code = dedent(f"""
  128. def {function_name}(obj: Any, /) -> 'TypeIs[{member}]':
  129. return {check_code}
  130. """)
  131. locals_: dict[str, Any] = {}
  132. exec(func_code, globals_, locals_)
  133. return locals_[function_name]
  134. # Keep this ordered, as per `typing.__all__`:
  135. is_annotated = _compile_identity_check_function('Annotated', 'is_annotated')
  136. is_annotated.__doc__ = """
  137. Return whether the argument is the [`Annotated`][typing.Annotated] [special form][].
  138. ```pycon
  139. >>> is_annotated(Annotated)
  140. True
  141. >>> is_annotated(Annotated[int, ...])
  142. False
  143. ```
  144. """
  145. is_any = _compile_identity_check_function('Any', 'is_any')
  146. is_any.__doc__ = """
  147. Return whether the argument is the [`Any`][typing.Any] [special form][].
  148. ```pycon
  149. >>> is_any(Any)
  150. True
  151. ```
  152. """
  153. is_classvar = _compile_identity_check_function('ClassVar', 'is_classvar')
  154. is_classvar.__doc__ = """
  155. Return whether the argument is the [`ClassVar`][typing.ClassVar] [type qualifier][].
  156. ```pycon
  157. >>> is_classvar(ClassVar)
  158. True
  159. >>> is_classvar(ClassVar[int])
  160. >>> False
  161. ```
  162. """
  163. is_concatenate = _compile_identity_check_function('Concatenate', 'is_concatenate')
  164. is_concatenate.__doc__ = """
  165. Return whether the argument is the [`Concatenate`][typing.Concatenate] [special form][].
  166. ```pycon
  167. >>> is_concatenate(Concatenate)
  168. True
  169. >>> is_concatenate(Concatenate[int, P])
  170. False
  171. ```
  172. """
  173. is_final = _compile_identity_check_function('Final', 'is_final')
  174. is_final.__doc__ = """
  175. Return whether the argument is the [`Final`][typing.Final] [type qualifier][].
  176. ```pycon
  177. >>> is_final(Final)
  178. True
  179. >>> is_final(Final[int])
  180. False
  181. ```
  182. """
  183. # Unlikely to have a different version in `typing-extensions`, but keep it consistent.
  184. # Also note that starting in 3.14, this is an alias to `annotationlib.ForwardRef`, but
  185. # accessing it from `typing` doesn't seem to be deprecated.
  186. is_forwardref = _compile_isinstance_check_function('ForwardRef', 'is_forwardref')
  187. is_forwardref.__doc__ = """
  188. Return whether the argument is an instance of [`ForwardRef`][typing.ForwardRef].
  189. ```pycon
  190. >>> is_forwardref(ForwardRef('T'))
  191. True
  192. ```
  193. """
  194. is_generic = _compile_identity_check_function('Generic', 'is_generic')
  195. is_generic.__doc__ = """
  196. Return whether the argument is the [`Generic`][typing.Generic] [special form][].
  197. ```pycon
  198. >>> is_generic(Generic)
  199. True
  200. >>> is_generic(Generic[T])
  201. False
  202. ```
  203. """
  204. is_literal = _compile_identity_check_function('Literal', 'is_literal')
  205. is_literal.__doc__ = """
  206. Return whether the argument is the [`Literal`][typing.Literal] [special form][].
  207. ```pycon
  208. >>> is_literal(Literal)
  209. True
  210. >>> is_literal(Literal["a"])
  211. False
  212. ```
  213. """
  214. # `get_origin(Optional[int]) is Union`, so `is_optional()` isn't implemented.
  215. is_paramspec = _compile_isinstance_check_function('ParamSpec', 'is_paramspec')
  216. is_paramspec.__doc__ = """
  217. Return whether the argument is an instance of [`ParamSpec`][typing.ParamSpec].
  218. ```pycon
  219. >>> P = ParamSpec('P')
  220. >>> is_paramspec(P)
  221. True
  222. ```
  223. """
  224. # Protocol?
  225. is_typevar = _compile_isinstance_check_function('TypeVar', 'is_typevar')
  226. is_typevar.__doc__ = """
  227. Return whether the argument is an instance of [`TypeVar`][typing.TypeVar].
  228. ```pycon
  229. >>> T = TypeVar('T')
  230. >>> is_typevar(T)
  231. True
  232. ```
  233. """
  234. is_typevartuple = _compile_isinstance_check_function('TypeVarTuple', 'is_typevartuple')
  235. is_typevartuple.__doc__ = """
  236. Return whether the argument is an instance of [`TypeVarTuple`][typing.TypeVarTuple].
  237. ```pycon
  238. >>> Ts = TypeVarTuple('Ts')
  239. >>> is_typevartuple(Ts)
  240. True
  241. ```
  242. """
  243. is_union = _compile_identity_check_function('Union', 'is_union')
  244. is_union.__doc__ = """
  245. Return whether the argument is the [`Union`][typing.Union] [special form][].
  246. This function can also be used to check for the [`Optional`][typing.Optional] [special form][],
  247. as at runtime, `Optional[int]` is equivalent to `Union[int, None]`.
  248. ```pycon
  249. >>> is_union(Union)
  250. True
  251. >>> is_union(Union[int, str])
  252. False
  253. ```
  254. !!! warning
  255. This does not check for unions using the [new syntax][types-union] (e.g. `int | str`).
  256. """
  257. def is_namedtuple(obj: Any, /) -> bool:
  258. """Return whether the argument is a named tuple type.
  259. This includes [`NamedTuple`][typing.NamedTuple] subclasses and classes created from the
  260. [`collections.namedtuple`][] factory function.
  261. ```pycon
  262. >>> class User(NamedTuple):
  263. ... name: str
  264. ...
  265. >>> is_namedtuple(User)
  266. True
  267. >>> City = collections.namedtuple('City', [])
  268. >>> is_namedtuple(City)
  269. True
  270. >>> is_namedtuple(NamedTuple)
  271. False
  272. ```
  273. """
  274. return isinstance(obj, type) and issubclass(obj, tuple) and hasattr(obj, '_fields') # pyright: ignore[reportUnknownArgumentType]
  275. # TypedDict?
  276. # BinaryIO? IO? TextIO?
  277. is_literalstring = _compile_identity_check_function('LiteralString', 'is_literalstring')
  278. is_literalstring.__doc__ = """
  279. Return whether the argument is the [`LiteralString`][typing.LiteralString] [special form][].
  280. ```pycon
  281. >>> is_literalstring(LiteralString)
  282. True
  283. ```
  284. """
  285. is_never = _compile_identity_check_function('Never', 'is_never')
  286. is_never.__doc__ = """
  287. Return whether the argument is the [`Never`][typing.Never] [special form][].
  288. ```pycon
  289. >>> is_never(Never)
  290. True
  291. ```
  292. """
  293. is_newtype = _compile_isinstance_check_function('NewType', 'is_newtype')
  294. is_newtype.__doc__ = """
  295. Return whether the argument is a [`NewType`][typing.NewType].
  296. ```pycon
  297. >>> UserId = NewType("UserId", int)
  298. >>> is_newtype(UserId)
  299. True
  300. ```
  301. """
  302. is_nodefault = _compile_identity_check_function('NoDefault', 'is_nodefault')
  303. is_nodefault.__doc__ = """
  304. Return whether the argument is the [`NoDefault`][typing.NoDefault] sentinel object.
  305. ```pycon
  306. >>> is_nodefault(NoDefault)
  307. True
  308. ```
  309. """
  310. is_noextraitems = _compile_identity_check_function('NoExtraItems', 'is_noextraitems')
  311. is_noextraitems.__doc__ = """
  312. Return whether the argument is the `NoExtraItems` sentinel object.
  313. ```pycon
  314. >>> is_noextraitems(NoExtraItems)
  315. True
  316. ```
  317. """
  318. is_noreturn = _compile_identity_check_function('NoReturn', 'is_noreturn')
  319. is_noreturn.__doc__ = """
  320. Return whether the argument is the [`NoReturn`][typing.NoReturn] [special form][].
  321. ```pycon
  322. >>> is_noreturn(NoReturn)
  323. True
  324. >>> is_noreturn(Never)
  325. False
  326. ```
  327. """
  328. is_notrequired = _compile_identity_check_function('NotRequired', 'is_notrequired')
  329. is_notrequired.__doc__ = """
  330. Return whether the argument is the [`NotRequired`][typing.NotRequired] [special form][].
  331. ```pycon
  332. >>> is_notrequired(NotRequired)
  333. True
  334. ```
  335. """
  336. is_paramspecargs = _compile_isinstance_check_function('ParamSpecArgs', 'is_paramspecargs')
  337. is_paramspecargs.__doc__ = """
  338. Return whether the argument is an instance of [`ParamSpecArgs`][typing.ParamSpecArgs].
  339. ```pycon
  340. >>> P = ParamSpec('P')
  341. >>> is_paramspecargs(P.args)
  342. True
  343. ```
  344. """
  345. is_paramspeckwargs = _compile_isinstance_check_function('ParamSpecKwargs', 'is_paramspeckwargs')
  346. is_paramspeckwargs.__doc__ = """
  347. Return whether the argument is an instance of [`ParamSpecKwargs`][typing.ParamSpecKwargs].
  348. ```pycon
  349. >>> P = ParamSpec('P')
  350. >>> is_paramspeckwargs(P.kwargs)
  351. True
  352. ```
  353. """
  354. is_readonly = _compile_identity_check_function('ReadOnly', 'is_readonly')
  355. is_readonly.__doc__ = """
  356. Return whether the argument is the [`ReadOnly`][typing.ReadOnly] [special form][].
  357. ```pycon
  358. >>> is_readonly(ReadOnly)
  359. True
  360. ```
  361. """
  362. is_required = _compile_identity_check_function('Required', 'is_required')
  363. is_required.__doc__ = """
  364. Return whether the argument is the [`Required`][typing.Required] [special form][].
  365. ```pycon
  366. >>> is_required(Required)
  367. True
  368. ```
  369. """
  370. is_self = _compile_identity_check_function('Self', 'is_self')
  371. is_self.__doc__ = """
  372. Return whether the argument is the [`Self`][typing.Self] [special form][].
  373. ```pycon
  374. >>> is_self(Self)
  375. True
  376. ```
  377. """
  378. # TYPE_CHECKING?
  379. is_typealias = _compile_identity_check_function('TypeAlias', 'is_typealias')
  380. is_typealias.__doc__ = """
  381. Return whether the argument is the [`TypeAlias`][typing.TypeAlias] [special form][].
  382. ```pycon
  383. >>> is_typealias(TypeAlias)
  384. True
  385. ```
  386. """
  387. is_typeguard = _compile_identity_check_function('TypeGuard', 'is_typeguard')
  388. is_typeguard.__doc__ = """
  389. Return whether the argument is the [`TypeGuard`][typing.TypeGuard] [special form][].
  390. ```pycon
  391. >>> is_typeguard(TypeGuard)
  392. True
  393. ```
  394. """
  395. is_typeis = _compile_identity_check_function('TypeIs', 'is_typeis')
  396. is_typeis.__doc__ = """
  397. Return whether the argument is the [`TypeIs`][typing.TypeIs] [special form][].
  398. ```pycon
  399. >>> is_typeis(TypeIs)
  400. True
  401. ```
  402. """
  403. _is_typealiastype_inner = _compile_isinstance_check_function('TypeAliasType', '_is_typealiastype_inner')
  404. if _IS_PY310:
  405. # Parameterized PEP 695 type aliases are instances of `types.GenericAlias` in typing_extensions>=4.13.0.
  406. # On Python 3.10, with `Alias[int]` being such an instance of `GenericAlias`,
  407. # `isinstance(Alias[int], TypeAliasType)` returns `True`.
  408. # See https://github.com/python/cpython/issues/89828.
  409. def is_typealiastype(obj: Any, /) -> 'TypeIs[TypeAliasType]':
  410. return type(obj) is not GenericAlias and _is_typealiastype_inner(obj)
  411. else:
  412. is_typealiastype = _compile_isinstance_check_function('TypeAliasType', 'is_typealiastype')
  413. is_typealiastype.__doc__ = """
  414. Return whether the argument is a [`TypeAliasType`][typing.TypeAliasType] instance.
  415. ```pycon
  416. >>> type MyInt = int
  417. >>> is_typealiastype(MyInt)
  418. True
  419. >>> MyStr = TypeAliasType("MyStr", str)
  420. >>> is_typealiastype(MyStr):
  421. True
  422. >>> type MyList[T] = list[T]
  423. >>> is_typealiastype(MyList[int])
  424. False
  425. ```
  426. """
  427. is_unpack = _compile_identity_check_function('Unpack', 'is_unpack')
  428. is_unpack.__doc__ = """
  429. Return whether the argument is the [`Unpack`][typing.Unpack] [special form][].
  430. ```pycon
  431. >>> is_unpack(Unpack)
  432. True
  433. >>> is_unpack(Unpack[Ts])
  434. False
  435. ```
  436. """
  437. if sys.version_info >= (3, 13):
  438. _deprecated_types = (warnings.deprecated, typing_extensions.deprecated)
  439. def is_deprecated(obj: Any, /) -> 'TypeIs[deprecated]':
  440. return isinstance(obj, _deprecated_types)
  441. else:
  442. _deprecated_type = typing_extensions.deprecated
  443. def is_deprecated(obj: Any, /) -> 'TypeIs[deprecated]':
  444. return isinstance(obj, _deprecated_type)
  445. is_deprecated.__doc__ = """
  446. Return whether the argument is a [`deprecated`][warnings.deprecated] instance.
  447. This also includes the [`typing_extensions` backport][typing_extensions.deprecated].
  448. ```pycon
  449. >>> is_deprecated(warnings.deprecated('message'))
  450. True
  451. >>> is_deprecated(typing_extensions.deprecated('message'))
  452. True
  453. ```
  454. """
  455. # Aliases defined in the `typing` module using `typing._SpecialGenericAlias` (itself aliased as `alias()`):
  456. DEPRECATED_ALIASES: Final[dict[Any, type[Any]]] = {
  457. typing.Hashable: collections.abc.Hashable,
  458. typing.Awaitable: collections.abc.Awaitable,
  459. typing.Coroutine: collections.abc.Coroutine,
  460. typing.AsyncIterable: collections.abc.AsyncIterable,
  461. typing.AsyncIterator: collections.abc.AsyncIterator,
  462. typing.Iterable: collections.abc.Iterable,
  463. typing.Iterator: collections.abc.Iterator,
  464. typing.Reversible: collections.abc.Reversible,
  465. typing.Sized: collections.abc.Sized,
  466. typing.Container: collections.abc.Container,
  467. typing.Collection: collections.abc.Collection,
  468. # type ignore reason: https://github.com/python/typeshed/issues/6257:
  469. typing.Callable: collections.abc.Callable, # pyright: ignore[reportAssignmentType, reportUnknownMemberType]
  470. typing.AbstractSet: collections.abc.Set,
  471. typing.MutableSet: collections.abc.MutableSet,
  472. typing.Mapping: collections.abc.Mapping,
  473. typing.MutableMapping: collections.abc.MutableMapping,
  474. typing.Sequence: collections.abc.Sequence,
  475. typing.MutableSequence: collections.abc.MutableSequence,
  476. typing.Tuple: tuple,
  477. typing.List: list,
  478. typing.Deque: collections.deque,
  479. typing.Set: set,
  480. typing.FrozenSet: frozenset,
  481. typing.MappingView: collections.abc.MappingView,
  482. typing.KeysView: collections.abc.KeysView,
  483. typing.ItemsView: collections.abc.ItemsView,
  484. typing.ValuesView: collections.abc.ValuesView,
  485. typing.Dict: dict,
  486. typing.DefaultDict: collections.defaultdict,
  487. typing.OrderedDict: collections.OrderedDict,
  488. typing.Counter: collections.Counter,
  489. typing.ChainMap: collections.ChainMap,
  490. typing.Generator: collections.abc.Generator,
  491. typing.AsyncGenerator: collections.abc.AsyncGenerator,
  492. typing.Type: type,
  493. # Defined in `typing.__getattr__`:
  494. typing.Pattern: re.Pattern,
  495. typing.Match: re.Match,
  496. typing.ContextManager: contextlib.AbstractContextManager,
  497. typing.AsyncContextManager: contextlib.AbstractAsyncContextManager,
  498. # Skipped: `ByteString` (deprecated, removed in 3.14)
  499. }
  500. """A mapping between the deprecated typing aliases to their replacement, as per [PEP 585](https://peps.python.org/pep-0585/)."""
  501. DEPRECATED_ALIASES_IDS: Final[dict[int, type[Any]]] = {id(k): v for k, v in DEPRECATED_ALIASES.items()}
  502. """A mapping between the [identity][id] of the deprecated typing aliases to their replacement, as per [PEP 585](https://peps.python.org/pep-0585/)."""
  503. # Add the `typing_extensions` aliases:
  504. for alias, target in list(DEPRECATED_ALIASES.items()):
  505. if (te_alias := getattr(typing_extensions, alias.__name__, None)) is not None:
  506. DEPRECATED_ALIASES[te_alias] = target