A Python library for inspecting type annotations and building graph representations of type metadata
Project description
typing-graph
typing-graph is a building block for Python libraries and frameworks that derive runtime behavior from type annotations. If you're building validation frameworks, CLI tools, serializers, ORMs, or similar tools that inspect types to generate code or configure behavior, typing-graph provides the structured type introspection layer so you can focus on your domain logic.
[!WARNING] This project is in early development. APIs may change without notice. Not yet recommended for production use.
Pass any type (generics, Annotated, dataclasses, TypedDict, PEP 695 aliases) and get back a graph of nodes representing the type structure and its metadata. The library handles metadata hoisting (extracting annotations from Annotated wrappers), qualifier detection (ClassVar, Final, Required), forward reference resolution, and caching. Each node exposes a children() method for recursive traversal.
Built on Pydantic's typing-inspection library and designed for compatibility with annotated-types.
Why I built this
After studying how projects like Pydantic, SQLAlchemy, and Typer derive behavior from type annotations, I became fascinated with the pattern and started experimenting with it in my own projects, and after writing similar introspection code across many of them, I decided to extract the common plumbing into a reusable library.
typing-graph provides a consistent graph representation that handles the edge cases of Python's typing system, so projects can focus on their domain logic instead of reinventing type introspection and metadata extraction.
Features
- Comprehensive type introspection: Inspect any Python type annotation and receive a structured, type-safe node representation
- Graph-based representation: Every type node exposes a
children()method for recursive traversal - Metadata hoisting: Automatically extract
Annotatedmetadata and attach it to base types - Qualifier extraction: Detect
ClassVar,Final,Required,NotRequired,ReadOnly, andInitVarqualifiers - Caching: Global cache for efficient repeated introspection
- Forward reference handling: Configurable evaluation modes for forward references (eager, deferred, or stringified)
- Modern Python support: PEP 695 type parameters, PEP 696 defaults, PEP 647
TypeGuard, PEP 742TypeIs - Structured type support:
dataclass,TypedDict,NamedTuple,Protocol,Enum - Metadata querying API: Predicate-based filtering, type-based extraction, scoped queries
- annotated-types integration:
GroupedMetadataflattening and convenience methods for constraint extraction - Type inspection controls: Allowlists/blocklists, configurable depth boundaries
- Graph traversal API:
walk()for depth-first traversal,edges()for semantic edge metadata - Visitor pattern and path tracking: Type-dispatched visitors, breadth-first traversal
- attrs support: Field metadata, validators, converters
- Pydantic support: Field metadata, validators, serializers
See the roadmap for details on planned features.
Installation
Requires Python 3.10 or later.
pip
pip install typing-graph
uv
uv add typing-graph
Poetry
poetry add typing-graph
Quick start
>>> from typing import Annotated
>>> from dataclasses import dataclass
>>> from typing_graph import inspect_type
>>> # Define constraint metadata (like you might in a validation framework)
>>> @dataclass
... class Pattern:
... regex: str
>>> @dataclass
... class MinLen:
... value: int
>>> # Define a reusable annotated type alias
>>> URL = Annotated[str, Pattern(r"^https?://")]
>>> # Build a complex nested type
>>> Urls = Annotated[list[URL], MinLen(1)]
>>> # Inspect the type graph
>>> node = inspect_type(Urls)
>>> node # doctest: +SKIP
SubscriptedGenericNode(metadata=(MinLen(value=1),), origin=GenericTypeNode(cls=list), args=(ConcreteNode(metadata=(Pattern(regex='^https?://'),), cls=str),))
>>> # The outer node is a SubscriptedGenericNode (list) with container-level metadata
>>> node.origin.cls
<class 'list'>
>>> node.metadata
(MinLen(value=1),)
>>> # Traverse to the element type - it carries its own metadata
>>> element = node.args[0]
>>> element.cls
<class 'str'>
>>> element.metadata
(Pattern(regex='^https?://'),)
Each node in the graph carries its own metadata, enabling frameworks to apply different validation or transformation logic at each level of the type structure.
Inspecting functions
from typing_graph import inspect_function
def greet(name: str, times: int = 1) -> str:
return name * times
func = inspect_function(greet)
print(func.name) # "greet"
print(func.signature.parameters[0].name) # "name"
print(func.signature.returns.cls) # str
Inspecting classes
from dataclasses import dataclass
from typing_graph import inspect_class, DataclassNode
@dataclass(frozen=True, slots=True)
class User:
name: str
age: int
node = inspect_class(User)
assert isinstance(node, DataclassNode)
assert node.frozen is True
assert node.slots is True
assert len(node.fields) == 2
Inspecting modules
from typing_graph import inspect_module
import mymodule
types = inspect_module(mymodule)
print(types.classes) # Dict of class names to inspection results
print(types.functions) # Dict of function names to FunctionNodes
print(types.type_aliases) # Dict of type alias names to alias nodes
Type node hierarchy
All type representations inherit from TypeNode, which provides:
source: Optional source location where the code defines the typemetadata: Tuple of metadata fromAnnotatedwrappers (when hoisted)qualifiers: Set of type qualifiers (ClassVar,Final, etc.)children(): Method returning child nodes for graph traversal
Core type nodes
| Node | Represents |
|---|---|
ConcreteNode |
Non-generic nominal types (int, str, custom classes) |
GenericTypeNode |
Unsubscripted generics (list, Dict) |
SubscriptedGenericNode |
Applied type arguments (list[int], Dict[str, T]) |
UnionNode |
Union types (Union[A, B], A | B) |
TupleNode |
Tuple types (heterogeneous and homogeneous) |
CallableNode |
Callable types with parameter and return type info |
AnnotatedNode |
Annotated[T, ...] when metadata is not hoisted |
Special forms
| Node | Represents |
|---|---|
AnyNode |
typing.Any |
NeverNode |
typing.Never / typing.NoReturn |
SelfNode |
typing.Self |
LiteralNode |
Literal[...] with specific values |
LiteralStringNode |
typing.LiteralString |
Type variables
| Node | Represents |
|---|---|
TypeVarNode |
Type variables with bounds, constraints, and variance |
ParamSpecNode |
Parameter specification variables (PEP 612) |
TypeVarTupleNode |
Variadic type variables (PEP 646) |
Structured types
| Node | Represents |
|---|---|
DataclassNode |
Dataclasses with field metadata |
TypedDictNode |
TypedDict with field definitions |
NamedTupleNode |
NamedTuple with named fields |
ProtocolNode |
Protocol with methods and attributes |
EnumNode |
Enum with typed members |
AttrsNode |
attrs classes |
PydanticModelNode |
Pydantic models |
Configuration
Control inspection behavior with InspectConfig:
from typing_graph import inspect_type, InspectConfig, EvalMode
config = InspectConfig(
eval_mode=EvalMode.DEFERRED, # How to handle forward references
max_depth=50, # Maximum recursion depth
hoist_metadata=True, # Move Annotated metadata to base type
include_source_locations=False, # Track where types are defined
)
node = inspect_type(SomeType, config=config)
Forward reference evaluation modes
EvalMode.EAGER: Fully resolve annotations; fail on errorsEvalMode.DEFERRED: UseForwardRefNodefor unresolvable annotations (default)EvalMode.STRINGIFIED: Keep annotations as strings, resolve lazily
Inspection functions
| Function | Purpose |
|---|---|
inspect_type() |
Inspect any type annotation |
inspect_function() |
Inspect a function's signature and metadata |
inspect_signature() |
Inspect a callable's signature |
inspect_class() |
Auto-detect and inspect a class |
inspect_dataclass() |
Inspect a dataclass specifically |
inspect_enum() |
Inspect an Enum specifically |
inspect_module() |
Discover all public types in a module |
inspect_type_alias() |
Inspect a type alias |
to_runtime_type() |
Convert a node back to runtime type hints |
cache_clear() |
Clear the global inspection cache |
cache_info() |
Get cache statistics |
Documentation
Full documentation is available at typing-graph.tbhb.dev.
Acknowledgments
Pydantic's approach to type introspection and metadata extraction inspired this library. typing-graph builds on Pydantic's typing-inspection library for low-level type introspection.
AI help
This project uses Claude Code as a development tool for:
- Rubber ducking and exploring architecture and design alternatives
- Drafting documentation and docstrings
- Generating test scaffolding and boilerplate
- Code cleanup and refactoring suggestions
- Researching Python typing edge cases
- Running benchmarks and mutation testing
All contributions undergo review and testing before inclusion, regardless of origin.
License
MIT License. See LICENSE for details.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file typing_graph-0.3.0.tar.gz.
File metadata
- Download URL: typing_graph-0.3.0.tar.gz
- Upload date:
- Size: 45.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.9.17 {"installer":{"name":"uv","version":"0.9.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b9d4a9a5e47ccdca9bdae61d400bee5cb2b466ce2d86167343c3e53afcb09d9a
|
|
| MD5 |
6f6dcddc86ba987a723901e74cf0d404
|
|
| BLAKE2b-256 |
44357f8eab20edaa3ddc86611893c7c5723b58111572ceb34327640bf1a57384
|
File details
Details for the file typing_graph-0.3.0-py3-none-any.whl.
File metadata
- Download URL: typing_graph-0.3.0-py3-none-any.whl
- Upload date:
- Size: 49.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.9.17 {"installer":{"name":"uv","version":"0.9.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9319b0552b9b35996e691637434dde6ec1b127346c3e062ef49ac8ca50da6530
|
|
| MD5 |
d6ad1266b5de1903374bbc4090c8807c
|
|
| BLAKE2b-256 |
288d5590e1243f63a4599e0da7278439b858c099ab45b3e233b63d215ff79a65
|