| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607 |
- """Low-level introspection utilities for [`typing`][] members.
- The provided functions in this module check against both the [`typing`][] and [`typing_extensions`][]
- variants, if they exists and are different.
- """
- # ruff: noqa: UP006
- import collections.abc
- import contextlib
- import re
- import sys
- import typing
- import warnings
- from textwrap import dedent
- from types import FunctionType, GenericAlias
- from typing import Any, Final
- import typing_extensions
- from typing_extensions import LiteralString, TypeAliasType, TypeIs, deprecated
- __all__ = (
- 'DEPRECATED_ALIASES',
- 'NoneType',
- 'is_annotated',
- 'is_any',
- 'is_classvar',
- 'is_concatenate',
- 'is_deprecated',
- 'is_final',
- 'is_forwardref',
- 'is_generic',
- 'is_literal',
- 'is_literalstring',
- 'is_namedtuple',
- 'is_never',
- 'is_newtype',
- 'is_nodefault',
- 'is_noextraitems',
- 'is_noreturn',
- 'is_notrequired',
- 'is_paramspec',
- 'is_paramspecargs',
- 'is_paramspeckwargs',
- 'is_readonly',
- 'is_required',
- 'is_self',
- 'is_typealias',
- 'is_typealiastype',
- 'is_typeguard',
- 'is_typeis',
- 'is_typevar',
- 'is_typevartuple',
- 'is_union',
- 'is_unpack',
- )
- _IS_PY310 = sys.version_info[:2] == (3, 10)
- def _compile_identity_check_function(member: LiteralString, function_name: LiteralString) -> FunctionType:
- """Create a function checking that the function argument is the (unparameterized) typing `member`.
- The function will make sure to check against both the `typing` and `typing_extensions`
- variants as depending on the Python version, the `typing_extensions` variant might be different.
- For instance, on Python 3.9:
- ```pycon
- >>> from typing import Literal as t_Literal
- >>> from typing_extensions import Literal as te_Literal, get_origin
- >>> t_Literal is te_Literal
- False
- >>> get_origin(t_Literal[1])
- typing.Literal
- >>> get_origin(te_Literal[1])
- typing_extensions.Literal
- ```
- """
- in_typing = hasattr(typing, member)
- in_typing_extensions = hasattr(typing_extensions, member)
- if in_typing and in_typing_extensions:
- if getattr(typing, member) is getattr(typing_extensions, member):
- check_code = f'obj is typing.{member}'
- else:
- check_code = f'obj is typing.{member} or obj is typing_extensions.{member}'
- elif in_typing and not in_typing_extensions:
- check_code = f'obj is typing.{member}'
- elif not in_typing and in_typing_extensions:
- check_code = f'obj is typing_extensions.{member}'
- else:
- check_code = 'False'
- func_code = dedent(f"""
- def {function_name}(obj: Any, /) -> bool:
- return {check_code}
- """)
- locals_: dict[str, Any] = {}
- globals_: dict[str, Any] = {'Any': Any, 'typing': typing, 'typing_extensions': typing_extensions}
- exec(func_code, globals_, locals_)
- return locals_[function_name]
- def _compile_isinstance_check_function(member: LiteralString, function_name: LiteralString) -> FunctionType:
- """Create a function checking that the function is an instance of the typing `member`.
- The function will make sure to check against both the `typing` and `typing_extensions`
- variants as depending on the Python version, the `typing_extensions` variant might be different.
- """
- in_typing = hasattr(typing, member)
- in_typing_extensions = hasattr(typing_extensions, member)
- if in_typing and in_typing_extensions:
- if getattr(typing, member) is getattr(typing_extensions, member):
- check_code = f'isinstance(obj, typing.{member})'
- else:
- check_code = f'isinstance(obj, (typing.{member}, typing_extensions.{member}))'
- elif in_typing and not in_typing_extensions:
- check_code = f'isinstance(obj, typing.{member})'
- elif not in_typing and in_typing_extensions:
- check_code = f'isinstance(obj, typing_extensions.{member})'
- else:
- check_code = 'False'
- func_code = dedent(f"""
- def {function_name}(obj: Any, /) -> 'TypeIs[{member}]':
- return {check_code}
- """)
- locals_: dict[str, Any] = {}
- globals_: dict[str, Any] = {'Any': Any, 'typing': typing, 'typing_extensions': typing_extensions}
- exec(func_code, globals_, locals_)
- return locals_[function_name]
- if sys.version_info >= (3, 10):
- from types import NoneType
- else:
- NoneType = type(None)
- # Keep this ordered, as per `typing.__all__`:
- is_annotated = _compile_identity_check_function('Annotated', 'is_annotated')
- is_annotated.__doc__ = """
- Return whether the argument is the [`Annotated`][typing.Annotated] [special form][].
- ```pycon
- >>> is_annotated(Annotated)
- True
- >>> is_annotated(Annotated[int, ...])
- False
- ```
- """
- is_any = _compile_identity_check_function('Any', 'is_any')
- is_any.__doc__ = """
- Return whether the argument is the [`Any`][typing.Any] [special form][].
- ```pycon
- >>> is_any(Any)
- True
- ```
- """
- is_classvar = _compile_identity_check_function('ClassVar', 'is_classvar')
- is_classvar.__doc__ = """
- Return whether the argument is the [`ClassVar`][typing.ClassVar] [type qualifier][].
- ```pycon
- >>> is_classvar(ClassVar)
- True
- >>> is_classvar(ClassVar[int])
- >>> False
- ```
- """
- is_concatenate = _compile_identity_check_function('Concatenate', 'is_concatenate')
- is_concatenate.__doc__ = """
- Return whether the argument is the [`Concatenate`][typing.Concatenate] [special form][].
- ```pycon
- >>> is_concatenate(Concatenate)
- True
- >>> is_concatenate(Concatenate[int, P])
- False
- ```
- """
- is_final = _compile_identity_check_function('Final', 'is_final')
- is_final.__doc__ = """
- Return whether the argument is the [`Final`][typing.Final] [type qualifier][].
- ```pycon
- >>> is_final(Final)
- True
- >>> is_final(Final[int])
- False
- ```
- """
- # Unlikely to have a different version in `typing-extensions`, but keep it consistent.
- # Also note that starting in 3.14, this is an alias to `annotationlib.ForwardRef`, but
- # accessing it from `typing` doesn't seem to be deprecated.
- is_forwardref = _compile_isinstance_check_function('ForwardRef', 'is_forwardref')
- is_forwardref.__doc__ = """
- Return whether the argument is an instance of [`ForwardRef`][typing.ForwardRef].
- ```pycon
- >>> is_forwardref(ForwardRef('T'))
- True
- ```
- """
- is_generic = _compile_identity_check_function('Generic', 'is_generic')
- is_generic.__doc__ = """
- Return whether the argument is the [`Generic`][typing.Generic] [special form][].
- ```pycon
- >>> is_generic(Generic)
- True
- >>> is_generic(Generic[T])
- False
- ```
- """
- is_literal = _compile_identity_check_function('Literal', 'is_literal')
- is_literal.__doc__ = """
- Return whether the argument is the [`Literal`][typing.Literal] [special form][].
- ```pycon
- >>> is_literal(Literal)
- True
- >>> is_literal(Literal["a"])
- False
- ```
- """
- # `get_origin(Optional[int]) is Union`, so `is_optional()` isn't implemented.
- is_paramspec = _compile_isinstance_check_function('ParamSpec', 'is_paramspec')
- is_paramspec.__doc__ = """
- Return whether the argument is an instance of [`ParamSpec`][typing.ParamSpec].
- ```pycon
- >>> P = ParamSpec('P')
- >>> is_paramspec(P)
- True
- ```
- """
- # Protocol?
- is_typevar = _compile_isinstance_check_function('TypeVar', 'is_typevar')
- is_typevar.__doc__ = """
- Return whether the argument is an instance of [`TypeVar`][typing.TypeVar].
- ```pycon
- >>> T = TypeVar('T')
- >>> is_typevar(T)
- True
- ```
- """
- is_typevartuple = _compile_isinstance_check_function('TypeVarTuple', 'is_typevartuple')
- is_typevartuple.__doc__ = """
- Return whether the argument is an instance of [`TypeVarTuple`][typing.TypeVarTuple].
- ```pycon
- >>> Ts = TypeVarTuple('Ts')
- >>> is_typevartuple(Ts)
- True
- ```
- """
- is_union = _compile_identity_check_function('Union', 'is_union')
- is_union.__doc__ = """
- Return whether the argument is the [`Union`][typing.Union] [special form][].
- This function can also be used to check for the [`Optional`][typing.Optional] [special form][],
- as at runtime, `Optional[int]` is equivalent to `Union[int, None]`.
- ```pycon
- >>> is_union(Union)
- True
- >>> is_union(Union[int, str])
- False
- ```
- !!! warning
- This does not check for unions using the [new syntax][types-union] (e.g. `int | str`).
- """
- def is_namedtuple(obj: Any, /) -> bool:
- """Return whether the argument is a named tuple type.
- This includes [`NamedTuple`][typing.NamedTuple] subclasses and classes created from the
- [`collections.namedtuple`][] factory function.
- ```pycon
- >>> class User(NamedTuple):
- ... name: str
- ...
- >>> is_namedtuple(User)
- True
- >>> City = collections.namedtuple('City', [])
- >>> is_namedtuple(City)
- True
- >>> is_namedtuple(NamedTuple)
- False
- ```
- """
- return isinstance(obj, type) and issubclass(obj, tuple) and hasattr(obj, '_fields') # pyright: ignore[reportUnknownArgumentType]
- # TypedDict?
- # BinaryIO? IO? TextIO?
- is_literalstring = _compile_identity_check_function('LiteralString', 'is_literalstring')
- is_literalstring.__doc__ = """
- Return whether the argument is the [`LiteralString`][typing.LiteralString] [special form][].
- ```pycon
- >>> is_literalstring(LiteralString)
- True
- ```
- """
- is_never = _compile_identity_check_function('Never', 'is_never')
- is_never.__doc__ = """
- Return whether the argument is the [`Never`][typing.Never] [special form][].
- ```pycon
- >>> is_never(Never)
- True
- ```
- """
- if sys.version_info >= (3, 10):
- is_newtype = _compile_isinstance_check_function('NewType', 'is_newtype')
- else: # On Python 3.10, `NewType` is a function.
- def is_newtype(obj: Any, /) -> bool:
- return hasattr(obj, '__supertype__')
- is_newtype.__doc__ = """
- Return whether the argument is a [`NewType`][typing.NewType].
- ```pycon
- >>> UserId = NewType("UserId", int)
- >>> is_newtype(UserId)
- True
- ```
- """
- is_nodefault = _compile_identity_check_function('NoDefault', 'is_nodefault')
- is_nodefault.__doc__ = """
- Return whether the argument is the [`NoDefault`][typing.NoDefault] sentinel object.
- ```pycon
- >>> is_nodefault(NoDefault)
- True
- ```
- """
- is_noextraitems = _compile_identity_check_function('NoExtraItems', 'is_noextraitems')
- is_noextraitems.__doc__ = """
- Return whether the argument is the `NoExtraItems` sentinel object.
- ```pycon
- >>> is_noextraitems(NoExtraItems)
- True
- ```
- """
- is_noreturn = _compile_identity_check_function('NoReturn', 'is_noreturn')
- is_noreturn.__doc__ = """
- Return whether the argument is the [`NoReturn`][typing.NoReturn] [special form][].
- ```pycon
- >>> is_noreturn(NoReturn)
- True
- >>> is_noreturn(Never)
- False
- ```
- """
- is_notrequired = _compile_identity_check_function('NotRequired', 'is_notrequired')
- is_notrequired.__doc__ = """
- Return whether the argument is the [`NotRequired`][typing.NotRequired] [special form][].
- ```pycon
- >>> is_notrequired(NotRequired)
- True
- ```
- """
- is_paramspecargs = _compile_isinstance_check_function('ParamSpecArgs', 'is_paramspecargs')
- is_paramspecargs.__doc__ = """
- Return whether the argument is an instance of [`ParamSpecArgs`][typing.ParamSpecArgs].
- ```pycon
- >>> P = ParamSpec('P')
- >>> is_paramspecargs(P.args)
- True
- ```
- """
- is_paramspeckwargs = _compile_isinstance_check_function('ParamSpecKwargs', 'is_paramspeckwargs')
- is_paramspeckwargs.__doc__ = """
- Return whether the argument is an instance of [`ParamSpecKwargs`][typing.ParamSpecKwargs].
- ```pycon
- >>> P = ParamSpec('P')
- >>> is_paramspeckwargs(P.kwargs)
- True
- ```
- """
- is_readonly = _compile_identity_check_function('ReadOnly', 'is_readonly')
- is_readonly.__doc__ = """
- Return whether the argument is the [`ReadOnly`][typing.ReadOnly] [special form][].
- ```pycon
- >>> is_readonly(ReadOnly)
- True
- ```
- """
- is_required = _compile_identity_check_function('Required', 'is_required')
- is_required.__doc__ = """
- Return whether the argument is the [`Required`][typing.Required] [special form][].
- ```pycon
- >>> is_required(Required)
- True
- ```
- """
- is_self = _compile_identity_check_function('Self', 'is_self')
- is_self.__doc__ = """
- Return whether the argument is the [`Self`][typing.Self] [special form][].
- ```pycon
- >>> is_self(Self)
- True
- ```
- """
- # TYPE_CHECKING?
- is_typealias = _compile_identity_check_function('TypeAlias', 'is_typealias')
- is_typealias.__doc__ = """
- Return whether the argument is the [`TypeAlias`][typing.TypeAlias] [special form][].
- ```pycon
- >>> is_typealias(TypeAlias)
- True
- ```
- """
- is_typeguard = _compile_identity_check_function('TypeGuard', 'is_typeguard')
- is_typeguard.__doc__ = """
- Return whether the argument is the [`TypeGuard`][typing.TypeGuard] [special form][].
- ```pycon
- >>> is_typeguard(TypeGuard)
- True
- ```
- """
- is_typeis = _compile_identity_check_function('TypeIs', 'is_typeis')
- is_typeis.__doc__ = """
- Return whether the argument is the [`TypeIs`][typing.TypeIs] [special form][].
- ```pycon
- >>> is_typeis(TypeIs)
- True
- ```
- """
- _is_typealiastype_inner = _compile_isinstance_check_function('TypeAliasType', '_is_typealiastype_inner')
- if _IS_PY310:
- # Parameterized PEP 695 type aliases are instances of `types.GenericAlias` in typing_extensions>=4.13.0.
- # On Python 3.10, with `Alias[int]` being such an instance of `GenericAlias`,
- # `isinstance(Alias[int], TypeAliasType)` returns `True`.
- # See https://github.com/python/cpython/issues/89828.
- def is_typealiastype(obj: Any, /) -> 'TypeIs[TypeAliasType]':
- return type(obj) is not GenericAlias and _is_typealiastype_inner(obj)
- else:
- is_typealiastype = _compile_isinstance_check_function('TypeAliasType', 'is_typealiastype')
- is_typealiastype.__doc__ = """
- Return whether the argument is a [`TypeAliasType`][typing.TypeAliasType] instance.
- ```pycon
- >>> type MyInt = int
- >>> is_typealiastype(MyInt)
- True
- >>> MyStr = TypeAliasType("MyStr", str)
- >>> is_typealiastype(MyStr):
- True
- >>> type MyList[T] = list[T]
- >>> is_typealiastype(MyList[int])
- False
- ```
- """
- is_unpack = _compile_identity_check_function('Unpack', 'is_unpack')
- is_unpack.__doc__ = """
- Return whether the argument is the [`Unpack`][typing.Unpack] [special form][].
- ```pycon
- >>> is_unpack(Unpack)
- True
- >>> is_unpack(Unpack[Ts])
- False
- ```
- """
- if sys.version_info >= (3, 13):
- def is_deprecated(obj: Any, /) -> 'TypeIs[deprecated]':
- return isinstance(obj, (warnings.deprecated, typing_extensions.deprecated))
- else:
- def is_deprecated(obj: Any, /) -> 'TypeIs[deprecated]':
- return isinstance(obj, typing_extensions.deprecated)
- is_deprecated.__doc__ = """
- Return whether the argument is a [`deprecated`][warnings.deprecated] instance.
- This also includes the [`typing_extensions` backport][typing_extensions.deprecated].
- ```pycon
- >>> is_deprecated(warnings.deprecated('message'))
- True
- >>> is_deprecated(typing_extensions.deprecated('message'))
- True
- ```
- """
- # Aliases defined in the `typing` module using `typing._SpecialGenericAlias` (itself aliased as `alias()`):
- DEPRECATED_ALIASES: Final[dict[Any, type[Any]]] = {
- typing.Hashable: collections.abc.Hashable,
- typing.Awaitable: collections.abc.Awaitable,
- typing.Coroutine: collections.abc.Coroutine,
- typing.AsyncIterable: collections.abc.AsyncIterable,
- typing.AsyncIterator: collections.abc.AsyncIterator,
- typing.Iterable: collections.abc.Iterable,
- typing.Iterator: collections.abc.Iterator,
- typing.Reversible: collections.abc.Reversible,
- typing.Sized: collections.abc.Sized,
- typing.Container: collections.abc.Container,
- typing.Collection: collections.abc.Collection,
- # type ignore reason: https://github.com/python/typeshed/issues/6257:
- typing.Callable: collections.abc.Callable, # pyright: ignore[reportAssignmentType, reportUnknownMemberType]
- typing.AbstractSet: collections.abc.Set,
- typing.MutableSet: collections.abc.MutableSet,
- typing.Mapping: collections.abc.Mapping,
- typing.MutableMapping: collections.abc.MutableMapping,
- typing.Sequence: collections.abc.Sequence,
- typing.MutableSequence: collections.abc.MutableSequence,
- typing.Tuple: tuple,
- typing.List: list,
- typing.Deque: collections.deque,
- typing.Set: set,
- typing.FrozenSet: frozenset,
- typing.MappingView: collections.abc.MappingView,
- typing.KeysView: collections.abc.KeysView,
- typing.ItemsView: collections.abc.ItemsView,
- typing.ValuesView: collections.abc.ValuesView,
- typing.Dict: dict,
- typing.DefaultDict: collections.defaultdict,
- typing.OrderedDict: collections.OrderedDict,
- typing.Counter: collections.Counter,
- typing.ChainMap: collections.ChainMap,
- typing.Generator: collections.abc.Generator,
- typing.AsyncGenerator: collections.abc.AsyncGenerator,
- typing.Type: type,
- # Defined in `typing.__getattr__`:
- typing.Pattern: re.Pattern,
- typing.Match: re.Match,
- typing.ContextManager: contextlib.AbstractContextManager,
- typing.AsyncContextManager: contextlib.AbstractAsyncContextManager,
- # Skipped: `ByteString` (deprecated, removed in 3.14)
- }
- """A mapping between the deprecated typing aliases to their replacement, as per [PEP 585](https://peps.python.org/pep-0585/)."""
- # Add the `typing_extensions` aliases:
- for alias, target in list(DEPRECATED_ALIASES.items()):
- # Use `alias.__name__` when we drop support for Python 3.9
- if (te_alias := getattr(typing_extensions, alias._name, None)) is not None:
- DEPRECATED_ALIASES[te_alias] = target
|