Runtime inspection utilities for Python typing module

Overview

Typing Inspect

Build Status

The typing_inspect module defines experimental API for runtime inspection of types defined in the Python standard typing module. Works with typing version 3.7.4 and later. Example usage:

from typing import Generic, TypeVar, Iterable, Mapping, Union
from typing_inspect import is_generic_type

T = TypeVar('T')

class MyCollection(Generic[T]):
    content: T

assert is_generic_type(Mapping)
assert is_generic_type(Iterable[int])
assert is_generic_type(MyCollection[T])

assert not is_generic_type(int)
assert not is_generic_type(Union[int, T])

Note: The API is still experimental, if you have ideas/suggestions please open an issue on tracker. Currently typing_inspect only supports latest version of typing. This limitation may be lifted if such requests will appear frequently.

Currently provided functions (see functions docstrings for examples of usage):

  • is_generic_type(tp): Test if tp is a generic type. This includes Generic itself, but excludes special typing constructs such as Union, Tuple, Callable, ClassVar.
  • is_callable_type(tp): Test tp is a generic callable type, including subclasses excluding non-generic types and callables.
  • is_tuple_type(tp): Test if tp is a generic tuple type, including subclasses excluding non-generic classes.
  • is_union_type(tp): Test if tp is a union type.
  • is_optional_type(tp): Test if tp is an optional type (either type(None) or a direct union to it such as in Optional[int]). Nesting and TypeVars are not unfolded/inspected in this process.
  • is_literal_type(tp): Test if tp is a literal type.
  • is_final_type(tp): Test if tp is a final type.
  • is_typevar(tp): Test if tp represents a type variable.
  • is_new_type(tp): Test if tp represents a distinct type.
  • is_classvar(tp): Test if tp represents a class variable.
  • get_origin(tp): Get the unsubscripted version of tp. Supports generic types, Union, Callable, and Tuple. Returns None for unsupported types.
  • get_last_origin(tp): Get the last base of (multiply) subscripted type tp. Supports generic types, Union, Callable, and Tuple. Returns None for unsupported types.
  • get_parameters(tp): Return type parameters of a parameterizable type tp as a tuple in lexicographic order. Parameterizable types are generic types, unions, tuple types and callable types.
  • get_args(tp, evaluate=False): Get type arguments of tp with all substitutions performed. For unions, basic simplifications used by Union constructor are performed. If evaluate is False (default), report result as nested tuple, this matches the internal representation of types. If evaluate is True, then all type parameters are applied (this could be time and memory expensive).
  • get_last_args(tp): Get last arguments of (multiply) subscripted type tp. Parameters for Callable are flattened.
  • get_generic_type(obj): Get the generic type of obj if possible, or its runtime class otherwise.
  • get_generic_bases(tp): Get generic base types of tp or empty tuple if not possible.
  • typed_dict_keys(td): Get TypedDict keys and their types, or None if td is not a typed dict.
Comments
  • New method is_nonable(t)

    New method is_nonable(t)

    This method would return True if

    • t is the NoneType (t is type(None))
    • t is a (possibly nested) Union to NoneType (this can be the result of using the Optional keyword or of an explicit Union[type(None), ....]
    • t is a TypeVar with either a bound or a constraints set, and one of them is nonable
    opened by smarie 9
  • Fix for Python 3.9+.

    Fix for Python 3.9+.

    opened by Apteryks 6
  • `is_generic_type(List)` is False in 3.9

    `is_generic_type(List)` is False in 3.9

    In [7]: sys.version                                                                                                                                                                  
    Out[7]: '3.9.0b1 (v3.9.0b1:97fe9cfd9f, May 18 2020, 20:39:28) \n[Clang 6.0 (clang-600.0.57)]'
    
    In [8]: from typing import List                                                                                                                                                      
    
    In [9]: typing_inspect.is_generic_type(List)                                                                                                                                         
    Out[9]: False
    

    Apparently in 3.9 List became an instance of _SpecialGenericAlias instead of _GenericAlias, which is what typing_inspect checks for.

    opened by JelleZijlstra 6
  • An API to manage most base collection types

    An API to manage most base collection types

    Collection types are an important special case of Generic types. In parsyfiles I ended up writing functions to easily access

    • the type of the key in a Mapping
    • the type of the items in Mapping (values, not keys), and Iterable (but I should probably use Container instead)

    The indent was to at least support Dict[KT, VT], List[VT], Set[VT], and Tuple[VT1, VT2, VT3...].

    Maybe such as API could be of some interest for typing_inspect users ?

    For details about how I used it, please see _extract_collection_base_type in https://github.com/smarie/python-parsyfiles/blob/master/parsyfiles/type_inspection_tools.py. Some tests are available here

    However the way I did it is probably not the best for an api, probably it would be better to provide individual functions such as get_mapping_inner_types and get_container_inner_types. An interesting thing to keep, though, is the capability to detect underspecified types such as Dict (and not Dict[A, B]) and to throw a TypeInformationRequiredError. See above file for details.

    opened by smarie 6
  • is_member_of_type

    is_member_of_type

    The most useful introspection methods I'd like to use from the typing module are basically the functions that were removed in https://github.com/python/typing/pull/283: checking whether a concrete object is compatible with a given type. This would be useful for simple runtime type assertions, for exploring the implications of PEP484 in the REPL, and annotations-based dispatch.

    Please let me know what you think; I'd be very interested in helping to implement this. I can also submit a PR if that makes discussion easier.

    Objections

    Should this be in external tools?

    @1st1 objected to this on the grounds that it should be left to external tools, but it seems like implementing a reference for cases which are unambiguous according to PEP484 would be quite useful, and even helpful to building external tools of that kind.

    For cases where something other than recursive isinstance calls are required (e.g. type variables), a helpful error can be raised at runtime.

    Mutable values

    There was an objection to __instancecheck__ and __subclasscheck__ by BDFL-delegate @markshannon here:

    For example,
    List[int] and List[str] and mutually incompatible types, yet
    isinstance([], List[int]) and isinstance([], List[str))
    both return true.
    

    This is a bit tricky for mutable types, since [] is a member of List[X] for all X, but the same list object might cease to be in List[X] when elements are added to it. For immutable types, the answer is unambiguous, so Tuple[*] could always deliver an unambiguous answer.

    For List[], Dict[] and other mutable types, my preference would be to check the types of the elements right now, when the method is called, and not at some future time when it may have been mutated. This would be a distinction between mypy and the runtime type checking, but this behavior seems more in keeping with the principle of least surprise, and would accord with the main intended use cases (REPL-based exploration of the type system, and simple runtime type assertions).

    Implementation

    I think the implementation would be roughly like the following:

    def is_member_of_type(obj, tp):
        if has_type_variables(tp):
            raise ValueError('Type variables cannot be determined at runtime')
        if is_union(tp):
            return any(is_member_of_type(obj, s) for s in tp.__args__)
        elif is_tuple(tp):
            ...
        elif ...:
            ...
        else:
            return isinstance(obj, tp)
    

    Include issubtype to match issubclass?

    issubtype could make sense as an addition, but it would involve introducing much more of the sorts of logic a typechecker should implement, e.g. the following:

    >>> from typing import *
    >>> class MyTuple(NamedTuple):
    ...     foo: int
    ...     bar: str
    ... 
    >>> issubtype(MyTuple, Tuple[int, str])
    True
    

    For this reason, I think it would make more sense to leave issubtype out of typing_inspect.

    opened by lucaswiman 6
  • Provide isinstance and issubclass equivalents

    Provide isinstance and issubclass equivalents

    In one of my projects I had to implement this function, I guess it could be useful to share in typing_inspect, and also to provide the equivalent for subclass tests, what do you think ? (I did not find any equivalent in typing nor in typing_inspect)

    from typing import Any, Type
    from typing_inspect import is_union_type, get_args, get_origin
    
    def robust_isinstance(inst: Any, typ: Type):
        """
        Similar to isinstance, but if 'typ' is a parametrized generic Type, it is first transformed into 
        its base generic class so that the instance check works. It is also robust to Union and Any.
    
        :param inst: the instance to check against the type
        :param typ: the type
        :return:
        """
        if typ is Any:
            return True
        elif is_union_type(typ):
            typs = get_args(typ)
            if len(typs) > 0:
                return any(robust_isinstance(inst, t) for t in typs)
            else:
                return False
        else:
            return isinstance(inst, get_base_generic_type(typ))
    

    Note that we could also provide a boolean parameter to enable finer-grain subclass/isinstance checks for example for parametrized generic types. For example to make this return False: robust_isinstance([1], List[str])

    enhancement 
    opened by smarie 5
  • Breaks `marshmallow_dataclass.NewType`

    Breaks `marshmallow_dataclass.NewType`

    In is_new_type there is an explicit check for the module name being either typing or typing_extensions, so types created via marshmallow_dataclass.NewType are no longer recognised, and therefore cannot be used in marshmallow dataclasses.

    This check on __module__ makes it impossible to define and use any custom NewType implementations, even if they are intended to be accepted as NewType.

    As the pre-3.10 implementations of typing.NewType define only two extra attributes __name__ and __supertype__, I'd suggest a check on these two (besides __qualname__), and on their types:

    ... isinstance(getattr(tp, '__supertype__', None), type) and isinstance(getattr(tp, '__name__'), str) and ...
    

    This would check everything that we can be sure of, and wouldn't block the custom NewType-implementations.

    opened by gabor-akeero 4
  • Making the is_new_type check more robust then just checking __supertype__ existance

    Making the is_new_type check more robust then just checking __supertype__ existance

    Currently everthing which has an attribute called __supertype__ will be accepted as ‚NewType‘ according to the function is_new_type.

    I find that a bit dangerous, since a user might expect that is_new_type is as reliant as a isinstance call. For example, these are false positives:

    >>>> import typing_inspect
    >>>> class MyClassWrapper:
    ....     def __init__(self):
    ....         self.__supertype__ = 'foobar'
    ....         
    >>>> mcw = MyClassWrapper()
    >>>> typing_inspect.is_new_type(mcw)
    True
    
    >>>> class MyClassWrapper:
    ....     __supertype__ = str
    ....     
    >>>> typing_inspect.is_new_type(MyClassWrapper)
    True
    

    We cannot do an isinstance check because NewType is a function, but we can add more conditions, which makes it harder for an false positive:

    Now with this MR it is additionally checked that the __qualname__ must be 'NewType..new_type', and that the module where the symbol is defined has the name 'typing'. A user could still fullfill all those conditions in a custom class, but the probability bacame quite low (espacially if it is not on purpose).

    Furtheremore support for python 3.10¹, where NewType is not a function any more but a class, is added. This check is implemented by isincance() and should work always correctly.

    The symbol NewType (i.e. not the call if it NewType()) is now also cosidered as a NewType by doing a tp is NewType check. This is in alignment with other functions, such as is_classvar() which does an tp is ClassVar or is_union_type() which do a tp is Union.

    ¹ To be specific, the first RC of python 3.10, i.e. in all bata versions NewType is still a function. Please also note that the class NewType in python 3.10.0rc has a different __qualname__ then the function NewType before.

    opened by raabf 4
  • Support Python 3.9

    Support Python 3.9

    typing_inspect doesn't really work with Python 3.9 right now. There are issues like #60 and #64, but a variety of things need to be updated. My initial survey (just guessing from poking at implementations; it would be better to have someone review/expand this list who is more intimately familiar with the code-level changes):

    • The typing module in 3.9 introduces a new "_BaseGenericAlias" class that might more correctly correspond to typing_inspect's current uses of "_GenericAlias."
    • Add support and tests for the new plain type hints (e.g. "list[int]" rather than "List[int]"). There seems to be a new "GenericAlias" class (that extends no other classes) for these kind of annotations.
    • Get the current test suite to pass under 3.9.
    • Add 3.9 to the travis config for regression testing.

    Any volunteers willing to give it a try? @ilevkivskyi - any additional guidance? What's wrong/missing in my above list?

    opened by pschanely 4
  • Broken with Python 3.6.3

    Broken with Python 3.6.3

    On python 3.6.3 import of private '_gorg' is failing. See https://travis-ci.org/lhupfeldt/multiconf/jobs/284627902 This module should be merged into typing or typing should provide public APIs for the methods needed by this module.

    opened by lhupfeldt 4
  • Get hierarchy of arguments

    Get hierarchy of arguments

    If I want to compare two types which are like:

    class Parent:
      pass
    
    class Child(Parent):
      pass
    
    class Foo(Sequence[Child]):
      pass
    
    compare(Foo, Sequence[Parent])
    

    It seems hard to do any comparison:

    • inspect.getmro of Foo does not return Sequence[str]
    • I can use get_generic_bases to manually traverse parent classes, but doing that in MRO order is tricky (#6)
    • but what would ideally be is that I would have a method which would return the hierarchy of types and their arguments, so that I could find out that Foo inherits from Sequence[Child], and that there is an argument Child and then compare that with Sequence[Parent]; it seems pretty tricky with current typing_inspect to get from Foo to Child as an argument, no?
    opened by mitar 4
  • `typed_dict_keys` fails to return data on 3.10

    `typed_dict_keys` fails to return data on 3.10

    I presume this issue has something to do with typing_extensions, but not exactly sure how.

    $ pip install typing-inspect==0.8.0
    $ python
    Python 3.10.8 (main, Dec 11 2022, 10:51:27) [Clang 11.0.3 (clang-1103.0.32.29)] on darwin
    Type "help", "copyright", "credits" or "license" for more information.
    >>> from typing_inspect import typed_dict_keys
    >>> from typing import TypedDict
    >>> class A(TypedDict):
    ...   x: bool
    ... 
    >>> typed_dict_keys(A)
    >>> 
    

    On Python 3.9 and 3.11, I get the expected return value of {'x': <class 'bool'>}

    bug 
    opened by pschanely 1
  • `get_origin()` doesn't support PEP 585 generic types?

    `get_origin()` doesn't support PEP 585 generic types?

    Hi,

    I'm seeing different behavior with get_origin(typing.List) and get_origin(list):

    [ins] In [1]: from typing_extensions import get_origin
    
    [ins] In [2]: import typing
    
    [ins] In [3]: get_origin(list[int])
    Out[3]: list
    
    [ins] In [4]: get_origin(list)
    
    [ins] In [5]: get_origin(typing.List)
    Out[5]: list
    
    opened by rouge8 2
  • `typing_inspect.get_args` `Annotated` metadata args

    `typing_inspect.get_args` `Annotated` metadata args

    Is typing_inspect.get_args expected to also return metadata args for Annotated?

    Python 3.9.5:

    >>> import typing
    >>>
    >>> A = typing.Annotated[int, "some_metadata"]
    >>>
    >>> typing.get_args(A)
    (<class 'int'>, 'some_metadata')
    

    Python 3.8.6:

    >>> import typing_extensions  # 3.10.0.2
    >>> import typing_inspect  # 0.7.1
    >>> 
    >>> A = typing_extensions.Annotated[int, "some_metadata"]
    >>>
    >>> typing_inspect.get_args(A)
    (<class 'int'>,)
    
    opened by anders-kiaer 1
  • Fix for Python 3.9.0/1 does not work with typing_extensions 3.7.4.

    Fix for Python 3.9.0/1 does not work with typing_extensions 3.7.4.

    The current version of typing_inspect supports typing_extensions 3.7.4. Running on Python 3.9.0/1 with that version of typing_extensions will result in the following error:

    venv/lib/python3.9/site-packages/typing_inspect.py:17: in <module>
        from typing_extensions import _TypedDictMeta as _TypedDictMeta_TE
    E   ImportError: cannot import name '_TypedDictMeta' from 'typing_extensions'
    

    The problem stems from typing_extensions 3.7.4 lacking the required Python < 3.9.2 fixes.

    opened by kmonson 1
  • Add a simple is_type(t) method

    Add a simple is_type(t) method

    It should determine if t is meaningful to use in an isinstance check.

    We've got some code in 3.6 that was simply doing:

    isinstance(t, type)

    for this purpose. It worked. typing.List as well as all your basic types passed correctly.

    It blows up in 3.7 onwards because the type of typing.List and friends has changed from type to typing._GenericAlias or typing._SpecialGenericAlias.

    Not having a simple way to answer the question of is something is a type that is meaningfully usable for isinstance checks from other things is frustrating. (I'd argue that such a basic thing should even go in the Python stdlib, but this module may be the best playground for that to start in)

    opened by gpshead 2
  • `is_generic_type` is True for a non-generic Protocol

    `is_generic_type` is True for a non-generic Protocol

    is_generic_type returns True for a non-generic Protocol:

    Tested on Python 3.8.2 and Python 3.6.10:

    from typing import Generic
    
    from typing_extensions import Protocol, runtime
    from typing_inspect import NEW_TYPING, get_parameters, is_generic_type
    
    
    @runtime
    class SomeProtocol(Protocol):
        def a_method(self) -> str:
            ...
    
    
    assert is_generic_type(SomeProtocol)
    
    if NEW_TYPING:
        assert isinstance(SomeProtocol, type)
        assert issubclass(SomeProtocol, Generic)
    else:
        from typing import GenericMeta
        assert isinstance(SomeProtocol, GenericMeta)
    
    
    # typing._check_generic raises 
    # TypeError: <class '__main__.SomeProtocol'> is not a generic class
    SomeProtocol[int] 
    

    implementation of is_generic_type

    opened by kiran 3
Owner
Ivan Levkivskyi
Ivan Levkivskyi
Mute your mic while you're typing. An app for Ubuntu.

Hushboard Mute your microphone while typing, for Ubuntu. Install from kryogenix.org/code/hushboard/. Installation We recommend you install Hushboard t

Stuart Langridge 142 Jan 5, 2023
Speed up your typing by some exercises in the multi-platform(Windows/Ubuntu).

Introduction This project purpose is speed up your typing by some exercises in the multi-platform(Windows/Ubuntu). Build Environment Software Environm

lyfer233 1 Mar 24, 2022
Runtime fault injection platform by Daniele Rizzieri (2021)

GDBitflip [v1.04] Runtime fault injection platform by Daniele Rizzieri (2021) This platform executes N times a binary and during each execution it inj

Daniele Rizzieri 1 Dec 7, 2021
Runtime fault injection platform by Daniele Rizzieri

GDBitflip [v1.04] Runtime fault injection platform by Daniele Rizzieri (2021) This platform executes N times a binary and during each execution it inj

Daniele Rizzieri 1 Dec 7, 2021
Runtime profiler for Streamlit, powered by pyinstrument

streamlit-profiler ???? Runtime profiler for Streamlit, powered by pyinstrument. streamlit-profiler is a Streamlit component that helps you find out w

Johannes Rieke 23 Nov 30, 2022
Module for remote in-memory Python package/module loading through HTTP/S

httpimport Python's missing feature! The feature has been suggested in Python Mailing List Remote, in-memory Python package/module importing through H

John Torakis 220 Dec 17, 2022
python DroneCAN code generation, interface and utilities

UAVCAN v0 stack in Python Python implementation of the UAVCAN v0 protocol stack. UAVCAN is a lightweight protocol designed for reliable communication

DroneCAN 11 Dec 12, 2022
Cross-platform config and manager for click console utilities.

climan Help the project financially: Donate: https://smartlegion.github.io/donate/ Yandex Money: https://yoomoney.ru/to/4100115206129186 PayPal: https

null 3 Aug 31, 2021
Aerospace utilities: flight conditions package, standard atmosphere model, and more.

Aerospace Utilities About Module that contains commonly-used aerospace utilities for problem solving. Flight Condition: input altitude to compute comm

null 1 Jan 3, 2022
LinuxHelper - A collection of utilities for non-technical Linux users accessible via a GUI

Linux Helper A collection of utilities for non-technical Linux users accessible via a GUI This app is still in very early development, expect bugs and

Seth 7 Oct 3, 2022
0CD - BinaryNinja plugin to introduce some quality of life utilities for obsessive compulsive CTF enthusiasts

0CD Author: b0bb Quality of life utilities for obsessive compulsive CTF enthusia

null 12 Sep 14, 2022
Run python scripts and pass data between multiple python and node processes using this npm module

Run python scripts and pass data between multiple python and node processes using this npm module. process-communication has a event based architecture for interacting with python data and errors inside nodejs.

Tyler Laceby 2 Aug 6, 2021
PyPIContents is an application that generates a Module Index from the Python Package Index (PyPI) and also from various versions of the Python Standard Library.

PyPIContents is an application that generates a Module Index from the Python Package Index (PyPI) and also from various versions of the Python Standar

Collage Labs 10 Nov 19, 2022
Python communism - A module for initiating the communist revolution in each of our python modules

Python communist revolution A man once said to abolish the classes or something

null 758 Jan 3, 2023
A Python module for decorators, wrappers and monkey patching.

wrapt The aim of the wrapt module is to provide a transparent object proxy for Python, which can be used as the basis for the construction of function

Graham Dumpleton 1.8k Jan 6, 2023
Python screenshot library, replacement for the Pillow ImageGrab module on Linux.

tldr: Use Pillow The pyscreenshot module is obsolete in most cases. It was created because PIL ImageGrab module worked on Windows only, but now Linux

null 455 Dec 24, 2022
An ultra fast cross-platform multiple screenshots module in pure Python using ctypes.

Python MSS from mss import mss # The simplest use, save a screen shot of the 1st monitor with mss() as sct: sct.shot() An ultra fast cross-platfo

Mickaël Schoentgen 799 Dec 30, 2022
This module is for finding the execution time of a whole python program

exetime 3.8 This module is for finding the execution time of a whole program How to install $ pip install exetime Contents: General Information Instru

Saikat Das 4 Oct 18, 2021
Python module to work with Magneto Database directly without using broken Magento 2 core

Python module to work with Magneto Database directly without using broken Magento 2 core

Egor Shitikov 13 Nov 10, 2022