typing-inspect review
Our typing-inspect 0.9.0 install exposed predicates and accessors for runtime typing objects: unions, optionals, literals, TypeVars, NewType wrappers, forward references, generic bases, bounds, constraints, origins, and arguments. It was built when Python's typing internals varied sharply across releases, and it still covers a few questions that typing.get_origin and typing.get_args do not answer directly. Version 0.9.0 fixed handling of unsubscripted generics on Python 3.9 and newer and adjusted to a typing_extensions change. It was published in May 2023, remains classified Alpha, declares no Python requirement, and has no py.typed marker despite being a typing utility.
Do not add typing-inspect merely for get_origin and get_args on a current Python project. Keep it for its few unique legacy predicates only after testing PEP 604 unions, Annotated, TypedDict, postponed annotations, and each supported interpreter against version 0.9.0.
We installed it
| Install | ✓ · 0.2s | 3 packages on disk · 1 MB |
| Import | ✓ | import typing_inspect in 0.12s · pure Python |
| Known vulns | 0 | (pip-audit) |
Answers from our run
Does typing-inspect install cleanly?
Yes. In a fresh container with an empty cache, pip install typing-inspect finished in 0.2s, leaving 3 packages and 1 MB on disk. pip-audit reported no known vulnerabilities.
What does typing-inspect need to run?
Python 3.x, and nothing compiled: it is pure Python. In our run import typing_inspect succeeded in 0.12s.
typing-inspect or typing-extensions: which should you use?
typing-extensions: Choose it to use and backport newer typing constructs while relying on standard-library get_origin and get_args for inspection. Do not add typing-inspect merely for get_origin and get_args on a current Python project.
When should you not use typing-inspect?
You only need origin and arguments on Python 3.8 or newer. typing.get_origin and typing.get_args are in the standard library and understand newer forms better
Use it if
- An existing compatibility layer already depends on helpers such as is_optional_type, is_new_type, get_bound, get_constraints, or get_generic_bases
- Your serializer or dependency injector supports older annotation shapes and has tests for every Python version it claims
- You must inspect classic typing.Optional, typing.Union, Literal, TypeVar, NewType, or Generic forms through one legacy API
- The package is already transitive and one of its unique predicates removes more compatibility code than adopting it adds
- You only need origin and arguments on Python 3.8 or newer. typing.get_origin and typing.get_args are in the standard library and understand newer forms better
- Your metadata lives in Annotated. Version 0.9.0 reports the underlying type as the origin and omits annotation metadata from its arguments
- PEP 604 unions drive dispatch logic. is_union_type(int | str) works, but get_origin(int | str) returns None in the published 0.9.0 source
- You inspect TypedDict classes from typing. typed_dict_keys recognizes the typing_extensions and mypy_extensions metaclasses, not the standard-library one on current Python
- You need a maintained, typed dependency. PyPI has had no release since May 2023, the API is labeled experimental, and our install found no py.typed marker
Setup reality
We installed typing-inspect 0.9.0 in a fresh Python 3.12 Bookworm container with no cache. uv completed in 0.2 seconds and left three packages using 1 MB on disk. The measurement reported three direct dependencies. The code is pure Python, and its package metadata does not specify a Python requirement. pip-audit found zero known vulnerabilities. import typing_inspect succeeded in 0.12 seconds. The installed distribution has an MIT license and no py.typed marker.
The distribution is named typing-inspect, while Python imports typing_inspect. Its declared dependencies include mypy-extensions and typing-extensions, plus a conditional typing backport for very old Python. Modern static checkers cannot treat the installed module as a typed package because it publishes neither inline typed-package metadata nor bundled stubs. You may need a missing-import override or local stub. No native compiler, environment variable, credential, or configuration file is involved.
Compatibility is the real setup burden. get_last_origin and get_last_args deliberately raise ValueError on modern Python and tell callers to use get_origin or get_args. The 0.9.0 implementation recognizes PEP 604 unions in is_union_type and get_args, yet get_origin only checks typing's generic alias class, so an int | str origin falls through to None. Annotated metadata and standard-library TypedDict detection have similar gaps. Test concrete annotations on every interpreter in your support matrix rather than assuming a helper name covers later typing features.
Use typing.get_type_hints before inspection when postponed annotations or string forward references are possible. That call may execute code contained in annotations, so do not run it on untrusted definitions. get_args only unwraps one level, and is_optional_type checks direct None membership rather than recursively resolving TypeVars. Version 0.9.0 also changed get_parameters for unsubscripted List on Python 3.9 and later to return an empty tuple because newer aliases do not retain the earlier parameter data.
Patterns
Check for a direct optional type detect-optional
from typing import Optional
from typing_inspect import get_args, is_optional_type
annotation = Optional[str]
if is_optional_type(annotation):
value_types = tuple(arg for arg in get_args(annotation) if arg is not type(None))The predicate checks direct None membership. It does not recursively expand a TypeVar bound to an optional or an optional nested inside a list.
Read classic Union members inspect-union
from typing import Union
from typing_inspect import get_args, get_origin, is_union_type
annotation = Union[int, str]
assert is_union_type(annotation)
assert get_args(annotation) == (int, str)
print(get_origin(annotation))This works for typing.Union. For int | str in 0.9.0, is_union_type and get_args work while get_origin returns None.
Handle a pipe union without its origin inspect-pep604-union
from typing_inspect import get_args, is_union_type
annotation = int | str
if is_union_type(annotation):
members = get_args(annotation)
print(members)Dispatch on is_union_type rather than get_origin for PEP 604 syntax. The published 0.9.0 origin helper does not recognize types.UnionType.
Get a container and its parameters unwrap-generic
from typing import Dict, List
from typing_inspect import get_args, get_origin
annotation = Dict[str, List[int]]
container = get_origin(annotation)
key_type, value_type = get_args(annotation)
item_type = get_args(value_type)[0]get_args unwraps one level at a time. Recurse explicitly when nested generic parameters matter.
Extract values from Literal read-literal-values
from typing import Literal
from typing_inspect import get_args, is_literal_type
Status = Literal["queued", "done"]
if is_literal_type(Status):
allowed = frozenset(get_args(Status))Literal arguments are values, not classes. Compare them directly and do not pass them into isinstance.
Read TypeVar bounds and constraints inspect-typevar
from typing import TypeVar
from typing_inspect import get_bound, get_constraints, is_typevar
Number = TypeVar("Number", bound=int)
Text = TypeVar("Text", str, bytes)
assert is_typevar(Number)
print(get_bound(Number))
print(get_constraints(Text))A TypeVar uses a bound or a constraint tuple. Calling these helpers with a non-TypeVar raises TypeError.
Find the base behind NewType inspect-newtype
from typing import NewType
from typing_inspect import is_new_type
UserId = NewType("UserId", int)
base = UserId.__supertype__ if is_new_type(UserId) else UserIdNewType changed representation in Python 3.10. is_new_type covers the older callable and newer class-based forms supported by 0.9.0.
Read a ForwardRef target inspect-forward-reference
from typing import ForwardRef
from typing_inspect import get_forward_arg, is_forward_ref
reference = ForwardRef("Account")
if is_forward_ref(reference):
print(get_forward_arg(reference))Postponed annotations can remain plain strings instead of ForwardRef objects. Resolve trusted annotations with typing.get_type_hints before inspection.
Recover an instance generic when available inspect-generic-instance
from typing import Generic, TypeVar
from typing_inspect import get_generic_type
T = TypeVar("T")
class Box(Generic[T]):
pass
parameterized = Box[int]()
plain = Box()
print(get_generic_type(parameterized))
print(get_generic_type(plain))A parameterized construction may set __orig_class__. A plain Box instance only reveals its runtime class, so element type information is unavailable.
Read original generic base classes inspect-generic-bases
from typing import Generic, TypeVar
from typing_inspect import get_generic_bases
T = TypeVar("T")
class Parent(Generic[T]):
pass
class Child(Parent[int]):
pass
print(get_generic_bases(Child))The result comes from __orig_bases__ on current Python and may be empty when a class or metaclass does not retain that information.
Use the standard library for Annotated avoid-annotated-loss
from typing import Annotated, get_args, get_origin
Field = Annotated[int, "max=10"]
assert get_origin(Field) is Annotated
base_type, *metadata = get_args(Field)typing_inspect 0.9.0 unwraps Annotated to int and drops its metadata. Use typing.get_origin and typing.get_args for this construct.
Inspect a standard TypedDict without typed_dict_keys inspect-typed-dict
from typing import NotRequired, TypedDict, get_type_hints
class UserRow(TypedDict):
id: int
nickname: NotRequired[str]
fields = get_type_hints(UserRow, include_extras=True)
required = UserRow.__required_keys__
optional = UserRow.__optional_keys__typed_dict_keys in 0.9.0 misses current typing.TypedDict classes. The standard attributes also preserve which keys are required.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| typing-extensions | PyPI | Choose it to use and backport newer typing constructs while relying on standard-library get_origin and get_args for inspection |
| beartype | PyPI | Choose it when the goal is runtime enforcement of annotations rather than building your own dispatcher from inspection predicates |
| typeguard | PyPI | Choose it for decorator or import-hook runtime checking with errors that identify the mismatched value and annotation |
| pydantic | PyPI | Choose it when annotation inspection is only a step toward input validation, coercion, serialization, and schema generation |
More utils guides
lru-cache · ajv · type-fest · p-limit · find-up · js-yaml · the whole shelf →
How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.

