graph
¶
Utilities for working with types as graphs.
Typical Usage
>>> import dataclasses
>>> from typelib import graph
>>> graph.static_order(dict[str, str])
[TypeNode(type=<class 'str'>, var=None, cyclic=False), TypeNode(type=dict[str, str], var=None, cyclic=False)]
>>>
>>> @dataclasses.dataclass
... class Class:
... attr: str
...
>>> graph.static_order(Class)
[TypeNode(type=<class 'str'>, var='attr', cyclic=False), TypeNode(type=<class '__main__.Class'>, var=None, cyclic=False)]
TypeNode
dataclass
¶
A "node" in a type graph.
cyclic
class-attribute
instance-attribute
¶
Whether this type annotation is cyclic.
get_type_graph
¶
get_type_graph(t: type) -> TopologicalSorter[TypeNode]
Get a directed graph of the type(s) this annotation represents,
Parameters:
-
t
(type
) –A type annotation.
Returns:
Note
A key aspect of building a directed graph of a given type is pre-emptive
detection and termination of cycles in the graph. If we detect a cycle, we
will wrap the type in a typing.ForwardRef
and mark the
TypeNode
instance as cyclic=True
.
Consumers of the graph can "delay" the resolution of a forward reference until
the graph's static_order()
has been exhausted, at which point they have
enough type information to resolve into the real type. (At least one layer down).
Resolution of cyclic/recursive types is always (necessarily) lazy and should only resolve one level deep on each attempt, otherwise we will find ourselves stuck in a closed loop which never terminates (infinite recursion).
Source code in src/typelib/graph.py
itertypes
¶
Iterate over the type-graph represented by t
from edges to root.
Parameters:
Yields:
Note
We will build a graph of types with the given type t
as the root node,
then iterate from the outermost leaves back to the root using BFS.
This is computationally expensive, so you are encouraged to use
static_order
instead of
itertypes
.
Source code in src/typelib/graph.py
static_order
¶
Get an ordered iterable of types which resolve into the root type provided.
Parameters:
Note
The order of types is guaranteed to rank from edges to root. If there are multiple edges, the order of those edges is not guaranteed.
This function is memoized to avoid the cost of re-computing a type annotation multiple times at runtime, which would be wasted effort, as types don't change at runtime.
To avoid memoization, you can make use of itertypes
.