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