API Reference
This section is generated directly from the Spytial source code using mkdocstrings.
Public package
spytial
RelationalizerBase
Bases: ABC
Abstract base class for relationalizers.
Relationalizers convert Python objects into atoms and relations for spatial visualization. They define how objects are represented as atoms and how relationships between objects are expressed as spatial relations.
can_handle(obj)
abstractmethod
Return True if this relationalizer can handle the given object.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
obj
|
Any
|
The Python object to test |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if this relationalizer can process the object, False otherwise |
relationalize(obj, walker_func)
abstractmethod
Convert object to atoms and relations using structured types.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
obj
|
Any
|
The object to convert |
required |
walker_func
|
Any
|
Function to recursively walk nested objects |
required |
Returns:
| Name | Type | Description |
|---|---|---|
List[Atom]
|
Tuple of (atoms_list, relations_list) |
|
atoms_list |
List[Relation]
|
List of Atom instances representing the object |
relations_list |
Tuple[List[Atom], List[Relation]]
|
List of Relation instances connecting this object to others |
declared_relations(obj)
Return the relation names obj's type declares, populated or not.
relationalize is extensional: it emits a tuple only where the
instance holds a value. A field that no walked instance populates
therefore leaves no trace at all, and a selector naming it resolves to
an arity-0 atom literal — indistinguishable from a misspelling. Naming
the field here lets the builder emit it as an empty relation instead,
so the data instance carries the type's shape and not only its
contents.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
obj
|
Any
|
The object whose type is being described |
required |
Returns:
| Type | Description |
|---|---|
List[str]
|
Relation names declared by the type; the builder emits any that no |
List[str]
|
instance populated as a relation with no tuples. |
Defaults to none. Containers and primitives have no declared schema,
and a relationalizer that renames fields (TupleRelationalizer emits
t0, t1, …) would otherwise declare names it never populates.
RelationalizerRegistry
Registry for relationalizers.
register(relationalizer_cls, priority=0)
classmethod
Register a relationalizer class with given priority.
find_relationalizer(obj)
classmethod
Find the first relationalizer that can handle the given object.
list_relationalizers()
classmethod
List all registered relationalizers with their priorities.
Returns:
| Type | Description |
|---|---|
List[Tuple[int, str]]
|
List of (priority, relationalizer_class_name) tuples, sorted by priority (highest first) |
clear()
classmethod
Clear all registered relationalizers (for testing).
SequenceRecorder
Context manager for recording a sequence of object snapshots for visualization.
Uses a single shared builder across all :meth:record calls so that atom IDs
remain stable across frames. This handles two common patterns:
-
In-place mutation — the same Python objects are modified between frames. Because
id(obj)never changes, the persistent ID table keeps IDs stable automatically with no extra configuration. -
Snapshot / deepcopy — a fresh copy of the data structure is created each step (e.g. functional algorithms, undo-history). Pass an
identitycallable that returns a stable string key for each object; atoms will then receive anidentity:<key>ID that stays constant across frames.
Example — in-place mutation::
with spytial.sequence() as seq:
seq.record(root)
insert(root, 42)
seq.record(root)
insert(root, 7)
seq.record(root)
seq.diagram()
Example — deepcopy snapshots with identity::
with spytial.sequence(identity=lambda n: str(n.id)) as seq:
for snap in snapshots:
seq.record(snap)
seq.diagram(method="file")
Atom labels are kept stable across frames by label_strategy. By
default ("persist") each atom's label is locked at the first frame
it appears. Pass label_strategy="back_construct" to defer label
resolution until :meth:diagram, which then picks the most informative
label per atom across the whole sequence — useful when record() runs
inside the constructor that produces the object (e.g. a make_set()
that snaps before its caller binding has completed; see issue #94).
record(obj, label=None, note=None)
Capture a snapshot of obj as the next frame in the sequence.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
obj
|
Any
|
The object to snapshot. |
required |
label
|
Optional[str]
|
Optional short label describing this step (e.g. "rotate left"). Shown in the status bar and as a scrubber tooltip. Whitespace is stripped; empty results are treated as no label. Truncated to 200 characters because the header is one line. |
None
|
note
|
Optional[str]
|
Optional longer description, shown in a collapsible panel. Multi-line text is fine; whitespace is stripped. |
None
|
diagram(method=None, auto_open=None, width=None, height=None, title=None, sequence_policy=None)
Render all recorded frames as an interactive sequence visualization.
Parameters override the values supplied to :func:sequence / the
constructor; omit them to use the constructor defaults.
EditCancelled
Bases: Exception
Raised by :func:edit when the editor is cancelled and on_cancel="raise".
AnnotatedType
A callable type alias that preserves spytial annotations.
This provides a clean syntax for reusable annotated types:
# Define once
Graph = AnnotatedType(Dict[int, List[int]],
InferredEdge(selector=EDGE_SEL, name=" "))
# Use naturally - Graph() wraps the value with type info
g = Graph({0: [1], 1: [2], 2: []})
diagram(g) # Annotations applied automatically!
The AnnotatedType acts as both: - A type hint (for documentation/type checkers) - A constructor (wraps values with the type info)
__init__(base_type, *annotations)
Create an annotated type alias.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
base_type
|
Any
|
The underlying type (e.g., Dict[int, List[int]]) |
required |
*annotations
|
Any
|
SpytialAnnotation instances (Orientation, InferredEdge, etc.) |
()
|
__call__(value)
Wrap a value with this type's annotations.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
Any
|
The value to wrap |
required |
Returns:
| Name | Type | Description |
|---|---|---|
Typed |
Typed
|
A wrapper that diagram() will recognize |
LineStyle
dataclass
Bases: _StyleBlock
Styling for a drawn edge line (edgeStyle, inferredEdge, GroupEdge connector).
All fields optional: color (CSS color), pattern ('solid'|'dashed'|'dotted'), weight (number > 0), highlight (CSS color).
TextStyle
dataclass
Bases: _StyleBlock
Styling for a label (edge labels, atom labels, group labels).
All fields optional: size ('small'|'normal'|'large'), color (CSS color).
BorderStyle
dataclass
Bases: _StyleBlock
Styling for an atom's border: color (CSS color), width (number > 0).
FillStyle
dataclass
Bases: _StyleBlock
Styling for an atom's interior fill: color (CSS color).
IconStyle
dataclass
Bases: _StyleBlock
An icon drawn on an atom (spytial-core 4.3).
All fields optional, including path, so a rule on a supertype can supply
the icon and a rule on a subtype tune only its opacity. An IconStyle with no
path draws nothing.
path-- a bundled icon name ('person'), an icon-pack reference ('bi:person-fill'), a URL, or a path.placement-- 'full' (the icon occupies the box) or 'badge' (a small marker in the corner, secondary to the label). Defaults to 'full'.opacity-- alpha in [0, 1]. Fade a 'full' icon to use it as a watermark behind the label.
GroupEdge
dataclass
Bases: _StyleBlock
Rich form of a selector-group's addEdge: direction plus connector styling.
points ('none'|'togroup'|'fromgroup') says which way the connector
between the group's key and the group points — the same key the YAML block
uses; lineStyle / textStyle style the connector edge and its label.
Serializes to addEdge: {points: ..., lineStyle: ..., textStyle: ...}.
SpytialAnnotation
Base class for spytial type annotations.
Subclasses represent specific constraints or directives that can be attached to type aliases using typing.Annotated.
__new__(*args, **kwargs)
Keep the arguments as passed, before any subclass touches them.
A subclass init coerces style blocks to plain dicts and rewrites a
deprecated spelling into its replacement before calling up, so by the
time self.kwargs exists it describes what the rule became. The
source block has to say what was written: Group(addEdge=GroupEdge(
points='togroup')), not Group(addEdge={'points': 'togroup'}), and
AtomColor(value='red'), not AtomColor(borderStyle={'color':
'red'}). An Annotated[...] rule is a call and not a decorator, so it
never has AST text to read back and this is the only record of it.
Every subclass takes keyword arguments only, so kwargs is the whole
call.
to_entry()
Convert to the internal registry format.
A scalar form serializes as a bare value rather than a mapping
(- flag: hideDisconnected); SCALAR_ITEMS says which keyword carries
it, so this matches the decorator path without either restating which
forms are scalar.
Orientation
Bases: SpytialAnnotation
Orientation constraint for spatial layout.
directions are the placement words -- above/below/left/right and the
adjacency variants directlyAbove/directlyBelow/directlyLeft/directlyRight.
(horizontal/vertical are Align's vocabulary, not this one; core matches no
case for them here and the constraint would be dropped.)
Usage
from typing import Annotated IntList = Annotated[list[int], Orientation(selector='items', directions=['left'])]
Cyclic
Bases: SpytialAnnotation
Cyclic constraint for circular layouts.
Usage
NodeRing = Annotated[list[Node], Cyclic(selector='items', direction='clockwise')]
Align
Bases: SpytialAnnotation
Alignment constraint.
direction is the shared axis: 'horizontal' or 'vertical'. Core rejects
anything else as internally inconsistent.
Usage
AlignedList = Annotated[list[int], Align(selector='items', direction='horizontal')]
Group
Bases: SpytialAnnotation
Grouping constraint.
Usage
Grouped = Annotated[MyType, Group(selector='items', name='mygroup')]
Set showLabel=False to hide the caption and its background pill while
keeping the group boundary. Keyed captions use the key's readable label;
IDs are added only to distinguish duplicate captions. Unary groups show
only name.
For the by-field form spytial-core 5.0 removed
(field/groupOn/addToGroup), see the group decorator's
docstring, which names the selector that replaces it.
addEdge controls the edge drawn between the group's key and the group
itself. For a binary selector with tuples
(a, b), (a, c), (a, d) the group is keyed by a and contains
{b, c, d}:
- ``'none'`` -> draw nothing (default)
- ``'togroup'`` -> edge from the key into the group (a -> group)
- ``'fromgroup'`` -> edge from the group back to the key (group -> a)
Legacy addEdge=True is still accepted and maps to 'togroup'.
Grouped = Annotated[MyType, Group(selector='...', name='g', addEdge='togroup')]
To also style the connector, pass a GroupEdge (spytial-core 3.0), and
style the group's own label with a top-level textStyle:
Grouped = Annotated[MyType, Group(selector='...', name='g',
addEdge=GroupEdge(points='togroup', lineStyle=LineStyle(pattern='dashed')),
textStyle=TextStyle(color='navy'))]
AtomColor
Bases: AtomStyle
Deprecated legacy atom color directive; use AtomStyle instead.
Rewrites to an atomStyle entry with value as the border color (the
legacy directive colored the border, not the fill).
AtomStyle
Bases: SpytialAnnotation
Atom style directive (spytial-core 3.0).
Styles an atom's border, interior fill, icon, and label independently.
showLabel is independent of the icon: iconStyle.placement controls
the icon's geometry, showLabel controls whether the label is drawn at
all. That split is what makes a faded watermark, or an icon with no label,
expressible (spytial-core 4.3).
Usage
Styled = Annotated[list[int], AtomStyle(selector='self', borderStyle=BorderStyle(color='blue'))] Filled = Annotated[Tree, AtomStyle(selector='Node', fillStyle=FillStyle(color='#eef6ff'), textStyle=TextStyle(size='large'))] Marked = Annotated[Tree, AtomStyle(selector='Dir', iconStyle=IconStyle(path='bi:folder-fill', placement='badge'))]
Size
Bases: SpytialAnnotation
Size constraint.
Size fixes a node's geometry, which is what the layout solves over rather
than presentation layered on a solved layout, so it belongs under
constraints (spytial-core 4.3; the directives section still accepts it
behind a deprecation warning).
selector is optional -- omit it to resize every node.
Usage
SizedList = Annotated[list[int], Size(selector='items', height=50, width=50)] AllSquare = Annotated[list[int], Size(height=50, width=50)]
Icon
Bases: SpytialAnnotation
Icon directive.
Usage
IconList = Annotated[list[int], Icon(selector='items', path='icon.svg', showLabels=True)]
EdgeColor
Bases: EdgeStyle
Deprecated legacy edge color directive; use EdgeStyle instead.
Rewrites to an edgeStyle entry: value -> lineStyle.color,
style -> lineStyle.pattern, weight -> lineStyle.weight.
EdgeStyle
Bases: SpytialAnnotation
Edge style directive (spytial-core 3.0).
Styles the edges of a field/relation: the drawn line via lineStyle,
the edge's label via textStyle, plus the showLabel/hidden flags.
Usage
StyledEdges = Annotated[Tree, EdgeStyle(field='children', lineStyle=LineStyle(color='red', pattern='dashed'))] LabeledEdges = Annotated[Tree, EdgeStyle(field='Uses', lineStyle=LineStyle(color='#d10000', weight=3), textStyle=TextStyle(size='small'), showLabel=True)] HiddenEdges = Annotated[Tree, EdgeStyle(field='internal', hidden=True)]
HideField
Bases: SpytialAnnotation
Hide field directive.
Usage
CleanView = Annotated[MyClass, HideField(field='_private')] Filtered = Annotated[MyClass, HideField(field='debug', filter='debug & Production')]
HideAtom
Bases: SpytialAnnotation
Hide atom constraint.
Hiding an atom changes what the layout has to place, and can make a spec
unsatisfiable against the other constraints, so it belongs under
constraints (spytial-core 4.3; the directives section still accepts it
behind a deprecation warning).
Usage
Filtered = Annotated[list[int], HideAtom(selector='hidden')]
Projection
Bases: SpytialAnnotation
Deprecated projection directive; it has no effect.
spytial-core's layout-spec parser does not read projection — projecting
over a sig is a pre-layout transform on the data instance, driven by the
viewer's projection controls, rather than something a layout spec declares.
Authoring one emits YAML that core discards, so remove it.
Usage
Projected = Annotated[MyType, Projection(sig='MySig')] # no-op
Attribute
Bases: SpytialAnnotation
Attribute directive.
Usage
WithAttr = Annotated[MyType, Attribute(field='value')] Filtered = Annotated[MyType, Attribute(field='status', filter='status & Active')]
InferredEdge
Bases: SpytialAnnotation
Inferred edge directive.
Styling uses the shared lineStyle/textStyle blocks (spytial-core 3.0); the inline color/style/weight keys are deprecated and rewritten into lineStyle.
draw (spytial-core 3.2) is '<end> -> <end>', each end '_' (the
atom itself) or the name of a group constraint, in which case that end
attaches to that group's hull -- giving group-to-group and node-to-group
edges. A keyed group (binary selector) is picked by the end's atom; a unary
group is attached to directly. '_ -> _' means the same as omitting it.
Usage
WithEdges = Annotated[Graph, InferredEdge(name='connection', selector='nodes')] StyledEdges = Annotated[Graph, InferredEdge(name='ancestor', selector='^parent', lineStyle=LineStyle(color='gray', pattern='dotted'))] HullEdges = Annotated[Graph, InferredEdge(name='reports to', selector='^parent', draw='regions -> regions')]
Tag
Bases: SpytialAnnotation
Tag directive for adding computed attributes to nodes.
Unlike 'attribute', this does NOT remove edges - it adds computed attribute values to nodes based on selector evaluation.
Usage
Simple binary tag - displays as "age: 25"
Tagged = Annotated[MyType, Tag(toTag='Person', name='age', value='age')]
Ternary selector - displays as "score[Math]: 95"
Graded = Annotated[MyType, Tag(toTag='Student', name='score', value='grades')]
Four-column result (student, course, exam, grade) displays as
"score[Math,Midterm]: 95".
ExamGraded = Annotated[MyType, Tag(toTag='Student', name='score', value='examGrades')]
name is literal. The value selector must preserve the tagged atom
as its first column; its last column is displayed as the value. A selector
such as Person.age returns ages without their owning Person atoms and
therefore cannot attach those values as tags. Use age instead.
Flag
Bases: SpytialAnnotation
Flag directive.
name is one of the two whole-diagram switches core acts on:
'hideDisconnected' (drop atoms with no edges) or
'hideDisconnectedBuiltIns' (drop only disconnected built-in atoms).
Usage
Flagged = Annotated[MyType, Flag(name='hideDisconnected')]
relationalizer(cls=None, *, priority=0)
Decorator to register a class as a relationalizer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
priority
|
int
|
Higher priority relationalizers are tried first. Priorities 0-99 are reserved for built-in relationalizers. |
0
|
reify(data_instance, root_id=None)
Reconstruct a Python object from a Spytial data instance.
Rebuild the object named by the instance's root (or root_id).
Supports built-in values, resolvable references, and class instances with
reconstructible state. Unsupported types can return attribute proxies or
incomplete instances. Shared references and many mutable cycles survive;
cycles through immutable containers can lose identity. This is not a
lossless inverse for every Python value. For dataclass instances whose
fields an editor may have stripped, :func:spytial.edit registers reifiers
that apply declared field defaults before restoring recorded values.
replit(data_instance, root_id=None)
Return repr() of the object :func:reify would reconstruct.
Reproduces the host REPL's echo for a data instance.
diagram(obj, method=None, auto_open=True, width=None, height=None, title=None, perf_path=None, perf_iterations=None, headless=False, timeout=None, as_type=None)
Display a Python object in the sPyTial visualizer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
obj
|
Any
|
Any Python object to visualize. |
required |
method
|
Optional[str]
|
Display method - "inline" (Jupyter), "browser" (new tab), "file" (save to file), or "headless" (headless browser for testing) If None (default), automatically selects "inline" in notebooks or "browser" otherwise. |
None
|
auto_open
|
bool
|
Whether to automatically open browser (for "browser" method) |
True
|
width
|
Optional[int]
|
Width of the visualization container in pixels (default: auto-detected) |
None
|
height
|
Optional[int]
|
Height of the visualization container in pixels (default: auto-detected) |
None
|
title
|
Optional[str]
|
Title for the browser tab/page (default: "sPyTial Visualization") |
None
|
perf_path
|
Optional[Union[str, Path]]
|
Optional path to save performance metrics JSON file. If None, metrics are not saved (unless perf_iterations is set). |
None
|
perf_iterations
|
Optional[int]
|
Optional number of times to render for performance benchmarking. If provided, the visualization will be rendered N times and metrics will be aggregated. Works with method="browser", "file", or "headless". |
None
|
headless
|
bool
|
Run in headless Chrome for testing/benchmarking (requires selenium and chromedriver). If True, method is automatically set to "headless". |
False
|
timeout
|
Optional[float]
|
Timeout in seconds for headless mode. If None, automatically calculated based on perf_iterations (default: max(120, perf_iterations * 5)). For large/complex visualizations, set this higher (e.g., timeout=600 for 10 minutes). |
None
|
as_type
|
Optional[Any]
|
Optional annotated type to treat the object as. Annotations from this type will be applied in addition to any introspected from the object itself. Use AnnotatedType() or typing.Annotated to define types with spytial annotations. |
None
|
Examples:
Define an annotated type
Graph = AnnotatedType(Dict[int, List[int]], InferredEdge(...), Orientation(...))
Display a dict as a Graph
g = {0: [1], 1: [2]} diagram(g, as_type=Graph)
Returns:
| Name | Type | Description |
|---|---|---|
str |
Optional[Union[str, Dict[str, Any]]]
|
Path to the generated HTML file (if method="file", "browser", or "headless") |
dict |
Optional[Union[str, Dict[str, Any]]]
|
Performance metrics (if method="headless" and perf_iterations > 0) |
sequence(identity=None, sequence_policy='stability', method=None, auto_open=True, width=None, height=None, title=None, as_type=None, label_strategy='persist')
Create a :class:SequenceRecorder for capturing algorithm steps or temporal snapshots.
The recorder uses a shared builder so atom IDs are stable across every
:meth:~SequenceRecorder.record call within the same session.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
identity
|
Optional[Callable[[Any], Optional[str]]]
|
Optional callable |
None
|
sequence_policy
|
str
|
Continuity policy for the frontend renderer. One of
|
'stability'
|
method
|
Optional[str]
|
Display method — |
None
|
auto_open
|
bool
|
Whether to open the browser automatically ( |
True
|
width
|
Optional[int]
|
Width of the visualization in pixels. |
None
|
height
|
Optional[int]
|
Height of the visualization in pixels. |
None
|
title
|
Optional[str]
|
Browser tab / page title. |
None
|
as_type
|
Optional[Any]
|
Optional annotated type applied to every recorded object. |
None
|
label_strategy
|
str
|
How atom labels are kept consistent across frames.
|
'persist'
|
Returns:
| Name | Type | Description |
|---|---|---|
A |
SequenceRecorder
|
class: |
Example::
with spytial.sequence() as seq:
seq.record(data)
step(data)
seq.record(data)
seq.diagram()
evaluate(obj, method=None, auto_open=True, width=None, height=None, as_type=None)
Evaluate a Python object using the sPyTial evaluator.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
obj
|
Any
|
Any Python object to evaluate. |
required |
method
|
Optional[str]
|
Display method - "inline" (Jupyter), "browser" (new tab), or "file" (save to file). If None (default), automatically selects "inline" in notebooks or "browser" otherwise. |
None
|
auto_open
|
bool
|
Whether to automatically open the browser (for "browser" method). |
True
|
width
|
Optional[int]
|
Width of the evaluation container in pixels (default: auto-detected). |
None
|
height
|
Optional[int]
|
Height of the evaluation container in pixels (default: auto-detected). |
None
|
as_type
|
Optional[Any]
|
Optional annotated type to treat the object as. Annotations from this type will be applied in addition to any introspected from the object itself. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
str |
Optional[str]
|
Path to the generated HTML file (if method="file" or "browser"). |
edit(instance, *, on_cancel='seed', timeout=None, height=560)
Open a structured editor for any value and return the reified result on Done.
The interactive counterpart to :func:spytial.diagram. Serves the editor as
a page from an ephemeral 127.0.0.1 server (inline in a local notebook, or
a browser tab from a script), blocks until you click Done or
Cancel, then reconstructs a fresh Python object with :func:reify and
returns it. instance itself is never mutated.
Accepts any value (dataclasses, dicts, lists, arbitrary objects), mirroring
:func:spytial.diagram. No Jupyter comm and no extra dependencies — only the
standard library and a browser. The supported transport is a local script or
a local Jupyter kernel; in pyodide and hosted notebooks (Colab,
JupyterHub, Binder) the browser can't reach the local server, so edit()
prints a note, shows :func:edit_html (standalone, Export button) instead,
and returns None.
The editor can never hang the kernel: if the page never connects, stops
responding, or you close it, edit() unblocks and returns per on_cancel.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
instance
|
Any
|
Any value to seed the editor with. |
required |
on_cancel
|
str
|
What to return if the editor is cancelled / closed /
disconnected — |
'seed'
|
timeout
|
Optional[float]
|
Optional overall seconds to wait; |
None
|
height
|
int
|
Pixel height of the inline iframe in a notebook. |
560
|
Returns:
| Type | Description |
|---|---|
Any
|
The reified Python object on Done; otherwise per |
Example::
result = spytial.edit(TreeNode(value=1)) # edit visually, click Done
edit_html(instance, *, method=None, auto_open=True, height=500)
Render a visual structured-value editor as standalone HTML.
This is the pyodide-compatible path. It generates a full HTML page
with the <structured-input-graph> editor and displays it via an
inline iframe (in notebooks) or a browser tab. Use the built-in
Export button to copy the resulting Python constructor code.
Accepts any value (dataclasses, dicts, lists, arbitrary objects). To get the
edited object back in Python directly (local script / local Jupyter), use
:func:edit instead.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
instance
|
Any
|
Any value to start editing from. |
required |
method
|
Optional[str]
|
|
None
|
auto_open
|
bool
|
Open the browser tab automatically (only for
|
True
|
height
|
int
|
Pixel height of the inline iframe. |
500
|
Returns:
| Type | Description |
|---|---|
Optional[str]
|
|
Optional[str]
|
when displayed in a browser. |
Example::
spytial.edit_html(TreeNode(value=1)) # or {"a": 1}, [1, 2], …
get_spytial_core_version()
Return the version of spytial-core used by this package.
annotate(obj, annotation_type, **kwargs)
Apply any annotation type to a specific object. :param obj: The object to annotate. :param annotation_type: The type of annotation (e.g., 'orientation', 'cyclic'). :param kwargs: The annotation parameters. :return: The annotated object (for chaining).
annotate_orientation(obj, **kwargs)
Apply orientation annotation to a specific object.
annotate_cyclic(obj, **kwargs)
Apply cyclic annotation to a specific object.
annotate_align(obj, **kwargs)
Apply align annotation to a specific object.
annotate_group(obj, **kwargs)
Apply group annotation to a specific object.
annotate_atomColor(obj, **kwargs)
Apply atomColor annotation to a specific object (deprecated; use annotate_atomStyle).
annotate_atomStyle(obj, **kwargs)
Apply atomStyle annotation to a specific object.
annotate_size(obj, **kwargs)
Apply size annotation to a specific object.
annotate_icon(obj, **kwargs)
Apply icon annotation to a specific object.
annotate_edgeColor(obj, **kwargs)
Apply edgeColor annotation to a specific object (deprecated; use annotate_edgeStyle).
annotate_edgeStyle(obj, **kwargs)
Apply edgeStyle annotation to a specific object.
annotate_projection(obj, **kwargs)
Apply projection annotation to a specific object.
annotate_attribute(obj, **kwargs)
Apply attribute annotation to a specific object.
annotate_hideField(obj, **kwargs)
Apply hideField annotation to a specific object.
annotate_hideAtom(obj, **kwargs)
Apply hideAtom annotation to a specific object.
annotate_inferredEdge(obj, **kwargs)
Apply inferredEdge annotation to a specific object.
annotate_tag(obj, **kwargs)
Apply tag annotation to a specific object.
annotate_flag(obj, **kwargs)
Apply flag annotation to a specific object.
extract_spytial_annotations(type_hint)
Extract spytial annotations from a typing.Annotated type hint.
:param type_hint: A type hint, possibly Annotated with spytial markers. :return: A dict with 'constraints' and 'directives' lists, or None if no annotations.
get_base_type(type_hint)
Get the base type from an Annotated type hint.
:param type_hint: A type hint, possibly Annotated. :return: The base type (unwrapped from Annotated if applicable).
annotate_type_alias(type_alias, annotation_type, **kwargs)
Attach a spytial annotation to a type alias.
This function allows you to annotate type aliases (e.g., list[int], MyTypeAlias) with spatial constraints and directives. When objects are visualized, if their type annotation matches a registered type alias, these annotations are applied.
Usage
Define a type alias
IntList = list[int]
Annotate it
annotate_type_alias(IntList, 'orientation', selector='items', directions=['left']) annotate_type_alias(IntList, 'atomColor', selector='self', value='blue')
Or use the convenience functions:
annotate_type_alias_orientation(IntList, selector='items', directions=['left'])
:param type_alias: The type alias to annotate (e.g., list[int], MyClass, etc.) :param annotation_type: The type of annotation ('orientation', 'group', etc.) :param kwargs: The annotation parameters. :return: The type alias (for chaining).
get_type_alias_annotations(type_alias)
Retrieve annotations registered for a type alias.
:param type_alias: The type alias to look up. :return: A dictionary with 'constraints' and 'directives' lists, or None if not found.
clear_type_alias_annotations(type_alias=None)
Clear annotations for a specific type alias or all type aliases.
Note: This only clears the legacy registry-based annotations. Annotations using typing.Annotated are immutable and don't need clearing.
:param type_alias: If provided, clear only this type alias's annotations. If None, clear all type alias annotations.
list_type_alias_annotations()
List all registered type alias annotations (legacy registry).
:return: A dictionary mapping type alias keys to their annotation registries.
dont_inherit_constraints(cls)
Mark a class to not inherit constraints from parent classes.
This is useful when you want to override a parent class's constraints. Constraints applied directly to this class are still included.
Example
@orientation(selector='children', directions=['below']) class Parent: pass
@dont_inherit_constraints class Child(Parent): # Will NOT have Parent's orientation constraint pass
:param cls: The class to mark as not inheriting constraints. :return: The class (for chaining).
dont_inherit_directives(cls)
Mark a class to not inherit directives from parent classes.
This is useful when you want to override a parent class's directives. Directives applied directly to this class are still included.
Example
@atomColor(selector='root', value='red') class Parent: pass
@dont_inherit_directives class Child(Parent): # Will NOT have Parent's atomColor directive pass
:param cls: The class to mark as not inheriting directives. :return: The class (for chaining).
dont_inherit_annotations(cls)
Mark a class to not inherit any annotations from parent classes.
Shorthand for applying both dont_inherit_constraints and dont_inherit_directives.
Example
@orientation(selector='children', directions=['below']) @atomColor(selector='root', value='red') class Parent: pass
@dont_inherit_annotations class Child(Parent): # Will NOT have any annotations from Parent pass
:param cls: The class to mark as not inheriting annotations. :return: The class (for chaining).
collect_decorators(obj, type_hint=None)
Collect all decorators applied to the class of the given object, as well as any annotations applied directly to the object instance, and any annotations registered for matching type aliases. Respects inheritance control flags (dont_inherit_constraints, dont_inherit_directives). :param obj: The object whose class decorators and object annotations should be collected. :param type_hint: Optional type hint to look up type alias annotations. :return: A combined dictionary of constraints and directives (deduplicated).
serialize_to_yaml_string(decorators)
Serialize the collected constraints and directives to a YAML string. :param decorators: The collected decorators (constraints and directives). :return: YAML string representation of the decorators.
reset_object_ids()
Reset the global object-level state. Useful for testing or when you need deterministic object ID generation across multiple runs.
Clears both global registries — object IDs and object annotations — for the built-in values that can't store that state on themselves.
Warning: This will clear all existing object ID mappings and any annotations recorded for un-attributable objects, so existing selectors that depend on previous object IDs may no longer work.
apply_if(condition, *decorators)
Conditional decorator macro for sPyTial. Usage: @apply_if(CONDITION, orientation(...), hideField(...), attribute(...), ... ) class MyClass: ... If CONDITION is True, applies all decorators in order to the class. If CONDITION is False, returns the class unchanged.
Diagramming
spytial.visualizer
sPyTial Visualization Display Module
This module provides functions to display Python objects using the sPyTial visualizer.
SequenceRecorder
Context manager for recording a sequence of object snapshots for visualization.
Uses a single shared builder across all :meth:record calls so that atom IDs
remain stable across frames. This handles two common patterns:
-
In-place mutation — the same Python objects are modified between frames. Because
id(obj)never changes, the persistent ID table keeps IDs stable automatically with no extra configuration. -
Snapshot / deepcopy — a fresh copy of the data structure is created each step (e.g. functional algorithms, undo-history). Pass an
identitycallable that returns a stable string key for each object; atoms will then receive anidentity:<key>ID that stays constant across frames.
Example — in-place mutation::
with spytial.sequence() as seq:
seq.record(root)
insert(root, 42)
seq.record(root)
insert(root, 7)
seq.record(root)
seq.diagram()
Example — deepcopy snapshots with identity::
with spytial.sequence(identity=lambda n: str(n.id)) as seq:
for snap in snapshots:
seq.record(snap)
seq.diagram(method="file")
Atom labels are kept stable across frames by label_strategy. By
default ("persist") each atom's label is locked at the first frame
it appears. Pass label_strategy="back_construct" to defer label
resolution until :meth:diagram, which then picks the most informative
label per atom across the whole sequence — useful when record() runs
inside the constructor that produces the object (e.g. a make_set()
that snaps before its caller binding has completed; see issue #94).
record(obj, label=None, note=None)
Capture a snapshot of obj as the next frame in the sequence.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
obj
|
Any
|
The object to snapshot. |
required |
label
|
Optional[str]
|
Optional short label describing this step (e.g. "rotate left"). Shown in the status bar and as a scrubber tooltip. Whitespace is stripped; empty results are treated as no label. Truncated to 200 characters because the header is one line. |
None
|
note
|
Optional[str]
|
Optional longer description, shown in a collapsible panel. Multi-line text is fine; whitespace is stripped. |
None
|
diagram(method=None, auto_open=None, width=None, height=None, title=None, sequence_policy=None)
Render all recorded frames as an interactive sequence visualization.
Parameters override the values supplied to :func:sequence / the
constructor; omit them to use the constructor defaults.
diagram(obj, method=None, auto_open=True, width=None, height=None, title=None, perf_path=None, perf_iterations=None, headless=False, timeout=None, as_type=None)
Display a Python object in the sPyTial visualizer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
obj
|
Any
|
Any Python object to visualize. |
required |
method
|
Optional[str]
|
Display method - "inline" (Jupyter), "browser" (new tab), "file" (save to file), or "headless" (headless browser for testing) If None (default), automatically selects "inline" in notebooks or "browser" otherwise. |
None
|
auto_open
|
bool
|
Whether to automatically open browser (for "browser" method) |
True
|
width
|
Optional[int]
|
Width of the visualization container in pixels (default: auto-detected) |
None
|
height
|
Optional[int]
|
Height of the visualization container in pixels (default: auto-detected) |
None
|
title
|
Optional[str]
|
Title for the browser tab/page (default: "sPyTial Visualization") |
None
|
perf_path
|
Optional[Union[str, Path]]
|
Optional path to save performance metrics JSON file. If None, metrics are not saved (unless perf_iterations is set). |
None
|
perf_iterations
|
Optional[int]
|
Optional number of times to render for performance benchmarking. If provided, the visualization will be rendered N times and metrics will be aggregated. Works with method="browser", "file", or "headless". |
None
|
headless
|
bool
|
Run in headless Chrome for testing/benchmarking (requires selenium and chromedriver). If True, method is automatically set to "headless". |
False
|
timeout
|
Optional[float]
|
Timeout in seconds for headless mode. If None, automatically calculated based on perf_iterations (default: max(120, perf_iterations * 5)). For large/complex visualizations, set this higher (e.g., timeout=600 for 10 minutes). |
None
|
as_type
|
Optional[Any]
|
Optional annotated type to treat the object as. Annotations from this type will be applied in addition to any introspected from the object itself. Use AnnotatedType() or typing.Annotated to define types with spytial annotations. |
None
|
Examples:
Define an annotated type
Graph = AnnotatedType(Dict[int, List[int]], InferredEdge(...), Orientation(...))
Display a dict as a Graph
g = {0: [1], 1: [2]} diagram(g, as_type=Graph)
Returns:
| Name | Type | Description |
|---|---|---|
str |
Optional[Union[str, Dict[str, Any]]]
|
Path to the generated HTML file (if method="file", "browser", or "headless") |
dict |
Optional[Union[str, Dict[str, Any]]]
|
Performance metrics (if method="headless" and perf_iterations > 0) |
sequence(identity=None, sequence_policy='stability', method=None, auto_open=True, width=None, height=None, title=None, as_type=None, label_strategy='persist')
Create a :class:SequenceRecorder for capturing algorithm steps or temporal snapshots.
The recorder uses a shared builder so atom IDs are stable across every
:meth:~SequenceRecorder.record call within the same session.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
identity
|
Optional[Callable[[Any], Optional[str]]]
|
Optional callable |
None
|
sequence_policy
|
str
|
Continuity policy for the frontend renderer. One of
|
'stability'
|
method
|
Optional[str]
|
Display method — |
None
|
auto_open
|
bool
|
Whether to open the browser automatically ( |
True
|
width
|
Optional[int]
|
Width of the visualization in pixels. |
None
|
height
|
Optional[int]
|
Height of the visualization in pixels. |
None
|
title
|
Optional[str]
|
Browser tab / page title. |
None
|
as_type
|
Optional[Any]
|
Optional annotated type applied to every recorded object. |
None
|
label_strategy
|
str
|
How atom labels are kept consistent across frames.
|
'persist'
|
Returns:
| Name | Type | Description |
|---|---|---|
A |
SequenceRecorder
|
class: |
Example::
with spytial.sequence() as seq:
seq.record(data)
step(data)
seq.record(data)
seq.diagram()
Evaluator
spytial.evaluator
sPyTial Evaluator Module
This module provides functions to evaluate expressions using the sPyTial evaluator.
evaluate(obj, method=None, auto_open=True, width=None, height=None, as_type=None)
Evaluate a Python object using the sPyTial evaluator.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
obj
|
Any
|
Any Python object to evaluate. |
required |
method
|
Optional[str]
|
Display method - "inline" (Jupyter), "browser" (new tab), or "file" (save to file). If None (default), automatically selects "inline" in notebooks or "browser" otherwise. |
None
|
auto_open
|
bool
|
Whether to automatically open the browser (for "browser" method). |
True
|
width
|
Optional[int]
|
Width of the evaluation container in pixels (default: auto-detected). |
None
|
height
|
Optional[int]
|
Height of the evaluation container in pixels (default: auto-detected). |
None
|
as_type
|
Optional[Any]
|
Optional annotated type to treat the object as. Annotations from this type will be applied in addition to any introspected from the object itself. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
str |
Optional[str]
|
Path to the generated HTML file (if method="file" or "browser"). |
Structured input
spytial.structured_input
sPyTial structured input — visual editing of any value.
Two ways to edit a structured value and get it back as a Python object:
-
:func:
edit— the interactive verb. Serves the editor from an ephemeral127.0.0.1server (see :mod:spytial._edit_server), blocks until the user clicks Done, then reconstructs the object with :func:reifyand returns it. Needs a local kernel the browser can reach (local script / local Jupyter); hosted notebooks and pyodide fall back to :func:edit_html. -
:func:
edit_html— renders standalone HTML viainput_template.htmlwith an Export button (copy-paste constructor code). No server; works in pyodide and other lightweight / hosted environments, and is the fallback :func:edituses where the local server isn't reachable.
EditCancelled
Bases: Exception
Raised by :func:edit when the editor is cancelled and on_cancel="raise".
edit(instance, *, on_cancel='seed', timeout=None, height=560)
Open a structured editor for any value and return the reified result on Done.
The interactive counterpart to :func:spytial.diagram. Serves the editor as
a page from an ephemeral 127.0.0.1 server (inline in a local notebook, or
a browser tab from a script), blocks until you click Done or
Cancel, then reconstructs a fresh Python object with :func:reify and
returns it. instance itself is never mutated.
Accepts any value (dataclasses, dicts, lists, arbitrary objects), mirroring
:func:spytial.diagram. No Jupyter comm and no extra dependencies — only the
standard library and a browser. The supported transport is a local script or
a local Jupyter kernel; in pyodide and hosted notebooks (Colab,
JupyterHub, Binder) the browser can't reach the local server, so edit()
prints a note, shows :func:edit_html (standalone, Export button) instead,
and returns None.
The editor can never hang the kernel: if the page never connects, stops
responding, or you close it, edit() unblocks and returns per on_cancel.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
instance
|
Any
|
Any value to seed the editor with. |
required |
on_cancel
|
str
|
What to return if the editor is cancelled / closed /
disconnected — |
'seed'
|
timeout
|
Optional[float]
|
Optional overall seconds to wait; |
None
|
height
|
int
|
Pixel height of the inline iframe in a notebook. |
560
|
Returns:
| Type | Description |
|---|---|
Any
|
The reified Python object on Done; otherwise per |
Example::
result = spytial.edit(TreeNode(value=1)) # edit visually, click Done
edit_html(instance, *, method=None, auto_open=True, height=500)
Render a visual structured-value editor as standalone HTML.
This is the pyodide-compatible path. It generates a full HTML page
with the <structured-input-graph> editor and displays it via an
inline iframe (in notebooks) or a browser tab. Use the built-in
Export button to copy the resulting Python constructor code.
Accepts any value (dataclasses, dicts, lists, arbitrary objects). To get the
edited object back in Python directly (local script / local Jupyter), use
:func:edit instead.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
instance
|
Any
|
Any value to start editing from. |
required |
method
|
Optional[str]
|
|
None
|
auto_open
|
bool
|
Open the browser tab automatically (only for
|
True
|
height
|
int
|
Pixel height of the inline iframe. |
500
|
Returns:
| Type | Description |
|---|---|
Optional[str]
|
|
Optional[str]
|
when displayed in a browser. |
Example::
spytial.edit_html(TreeNode(value=1)) # or {"a": 1}, [1, 2], …
Suggestion
spytial.suggest
spytial.suggest — scaffold a SpyTial spec from class analysis.
A lightweight, deterministic starting point you then edit::
from spytial.suggest import suggest
draft = suggest(TreeNode) # static analysis only
print(draft.to_source()) # paste-able @spytial.* stack
draft.apply() # or decorate the class live
draft # rich panel in Jupyter
Pass instance= when a plain class can't be read statically (e.g. children
default to None), and registry= to run an isolated set of heuristics.
Extend the rules with the :func:heuristic decorator.
This subpackage is intentionally not imported by spytial itself: the
ordinary static path adds no dependencies and stays dormant until you reach for
it. Class-only ask= calls lazily use Hypothesis to derive a concrete
witness; pass examples= or strategy= to control that witness set.
AskError
Bases: RuntimeError
ask= could not be honored — and the user explicitly asked, so we say so.
Raised (never swallowed) for: no/unresolvable provider, no buildable example datum, no headless evaluator, a failed provider call, a model "cannot express that" reply, or a full round of candidates that all failed validation. The message carries the reasons — the model's own, or the per-candidate diagnostics from evaluation.
ClassInfo
dataclass
A class and its discovered fields; passed to class-scope heuristics.
self_ref_fields
property
Names of fields that point back to a node-like type.
FieldInfo
dataclass
Everything the analyzer learned about a single field of a class.
SpecDraft
The result of :func:spytial.suggest.suggest.
Holds the proposed directives plus the conflict "losers" (alternatives) and any informational notes, and knows how to render itself four ways.
to_source(enabled_only=True, with_comments=True)
A paste-able stack of @spytial.* decorators.
to_registry(enabled_only=True)
The {"constraints": [...], "directives": [...]} registry dict.
apply(enabled_only=True)
Decorate the class live with the enabled suggestions; returns the class.
Suggestion
dataclass
One proposed directive, with the reasoning that produced it.
dedup_key()
Identity for de-duplication: same directive + same kwargs.
ClaudeCode
Provider backed by the Claude Code CLI (claude -p) — your subscription.
Runs on Claude Code's auth (OAuth/subscription), so it needs no separate API key.
claude has no native JSON-schema flag, so the schema is injected into the
prompt and the reply is parsed with :func:extract_json.
Individual, local use only: Claude Code's subscription token isn't licensed for
multi-user or hosted services — use an API key (an :class:LlmModel) for those.
Codex
Provider backed by the OpenAI Codex CLI (codex exec) — your ChatGPT plan.
Uses --output-schema for native JSON-schema-enforced output, so the reply
conforms to schema by construction. Runs on your ChatGPT (Plus/Pro/…) sign-in.
Individual, local use only — as with :class:ClaudeCode.
EnrichError
Bases: RuntimeError
A provider couldn't be built or a provider call failed.
suggest catches this (and, at call time, any exception a provider raises)
and records a note instead of propagating — enrichment is best-effort.
EnrichProvider
Bases: Protocol
The provider slot: a callable (prompt, *, schema) -> dict.
Implement it as a function or as a class with __call__. The return value is
a JSON object (mapping) conforming to schema — the model's structured reply.
LlmModel
Provider backed by a named model in Simon Willison's llm library.
enrich="llama3.2" / enrich="gpt-4o" resolve here. The id is explicit — no
llm default model is ever consulted, so the same call enriches the same way
everywhere. Raises :class:EnrichError if llm isn't installed or the id is
unknown, so a typo fails loudly rather than silently doing nothing.
HeuristicRegistry
An ordered collection of heuristics.
Use the default registry for global rules, or construct one for an isolated
rule set passed to :func:suggest.
heuristic(fn=None, *, scope='field', priority=0)
Decorator form of :meth:register.
run(class_info)
Invoke every heuristic; return (priority, suggestion) pairs.
copy()
A new registry seeded with this one's heuristics (e.g. the built-ins).
build_class_info(cls, instance=None)
Return a :class:ClassInfo for cls, optionally sampling instance.
heuristic(fn=None, *, scope='field', priority=0)
Register a heuristic into the default registry.
Works bare (@heuristic) or parameterized (@heuristic(scope='class')).
suggest(target, *, instance=None, examples=None, strategy=None, registry=None, enrich=None, ask=None)
Analyze a class and return a :class:SpecDraft of proposed directives.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
target
|
Any
|
the class to analyze, or an instance (its class is used and the instance is sampled). |
required |
instance
|
Any
|
an optional example object to sample for types static reading
can't recover. Also the default example for selector validation (see
|
None
|
examples
|
Optional[List[Any]]
|
a list of example instances to validate model-authored selectors
against (the |
None
|
strategy
|
Any
|
an optional Hypothesis strategy that supplies one generated witness
in addition to any fixed |
None
|
registry
|
Optional[HeuristicRegistry]
|
an alternate :class: |
None
|
enrich
|
Any
|
turn on the optional LLM enrichment layer by saying what to enrich
with — there is no ambient default. Either a model-id string resolved
through the |
None
|
ask
|
Optional[str]
|
a natural-language layout request to translate into directive(s) —
e.g. |
None
|
Annotations
spytial.annotations
LineStyle
dataclass
Bases: _StyleBlock
Styling for a drawn edge line (edgeStyle, inferredEdge, GroupEdge connector).
All fields optional: color (CSS color), pattern ('solid'|'dashed'|'dotted'), weight (number > 0), highlight (CSS color).
TextStyle
dataclass
Bases: _StyleBlock
Styling for a label (edge labels, atom labels, group labels).
All fields optional: size ('small'|'normal'|'large'), color (CSS color).
BorderStyle
dataclass
Bases: _StyleBlock
Styling for an atom's border: color (CSS color), width (number > 0).
FillStyle
dataclass
Bases: _StyleBlock
Styling for an atom's interior fill: color (CSS color).
IconStyle
dataclass
Bases: _StyleBlock
An icon drawn on an atom (spytial-core 4.3).
All fields optional, including path, so a rule on a supertype can supply
the icon and a rule on a subtype tune only its opacity. An IconStyle with no
path draws nothing.
path-- a bundled icon name ('person'), an icon-pack reference ('bi:person-fill'), a URL, or a path.placement-- 'full' (the icon occupies the box) or 'badge' (a small marker in the corner, secondary to the label). Defaults to 'full'.opacity-- alpha in [0, 1]. Fade a 'full' icon to use it as a watermark behind the label.
GroupEdge
dataclass
Bases: _StyleBlock
Rich form of a selector-group's addEdge: direction plus connector styling.
points ('none'|'togroup'|'fromgroup') says which way the connector
between the group's key and the group points — the same key the YAML block
uses; lineStyle / textStyle style the connector edge and its label.
Serializes to addEdge: {points: ..., lineStyle: ..., textStyle: ...}.
SpytialAnnotation
Base class for spytial type annotations.
Subclasses represent specific constraints or directives that can be attached to type aliases using typing.Annotated.
__new__(*args, **kwargs)
Keep the arguments as passed, before any subclass touches them.
A subclass init coerces style blocks to plain dicts and rewrites a
deprecated spelling into its replacement before calling up, so by the
time self.kwargs exists it describes what the rule became. The
source block has to say what was written: Group(addEdge=GroupEdge(
points='togroup')), not Group(addEdge={'points': 'togroup'}), and
AtomColor(value='red'), not AtomColor(borderStyle={'color':
'red'}). An Annotated[...] rule is a call and not a decorator, so it
never has AST text to read back and this is the only record of it.
Every subclass takes keyword arguments only, so kwargs is the whole
call.
to_entry()
Convert to the internal registry format.
A scalar form serializes as a bare value rather than a mapping
(- flag: hideDisconnected); SCALAR_ITEMS says which keyword carries
it, so this matches the decorator path without either restating which
forms are scalar.
Orientation
Bases: SpytialAnnotation
Orientation constraint for spatial layout.
directions are the placement words -- above/below/left/right and the
adjacency variants directlyAbove/directlyBelow/directlyLeft/directlyRight.
(horizontal/vertical are Align's vocabulary, not this one; core matches no
case for them here and the constraint would be dropped.)
Usage
from typing import Annotated IntList = Annotated[list[int], Orientation(selector='items', directions=['left'])]
Cyclic
Bases: SpytialAnnotation
Cyclic constraint for circular layouts.
Usage
NodeRing = Annotated[list[Node], Cyclic(selector='items', direction='clockwise')]
Align
Bases: SpytialAnnotation
Alignment constraint.
direction is the shared axis: 'horizontal' or 'vertical'. Core rejects
anything else as internally inconsistent.
Usage
AlignedList = Annotated[list[int], Align(selector='items', direction='horizontal')]
Group
Bases: SpytialAnnotation
Grouping constraint.
Usage
Grouped = Annotated[MyType, Group(selector='items', name='mygroup')]
Set showLabel=False to hide the caption and its background pill while
keeping the group boundary. Keyed captions use the key's readable label;
IDs are added only to distinguish duplicate captions. Unary groups show
only name.
For the by-field form spytial-core 5.0 removed
(field/groupOn/addToGroup), see the group decorator's
docstring, which names the selector that replaces it.
addEdge controls the edge drawn between the group's key and the group
itself. For a binary selector with tuples
(a, b), (a, c), (a, d) the group is keyed by a and contains
{b, c, d}:
- ``'none'`` -> draw nothing (default)
- ``'togroup'`` -> edge from the key into the group (a -> group)
- ``'fromgroup'`` -> edge from the group back to the key (group -> a)
Legacy addEdge=True is still accepted and maps to 'togroup'.
Grouped = Annotated[MyType, Group(selector='...', name='g', addEdge='togroup')]
To also style the connector, pass a GroupEdge (spytial-core 3.0), and
style the group's own label with a top-level textStyle:
Grouped = Annotated[MyType, Group(selector='...', name='g',
addEdge=GroupEdge(points='togroup', lineStyle=LineStyle(pattern='dashed')),
textStyle=TextStyle(color='navy'))]
AtomStyle
Bases: SpytialAnnotation
Atom style directive (spytial-core 3.0).
Styles an atom's border, interior fill, icon, and label independently.
showLabel is independent of the icon: iconStyle.placement controls
the icon's geometry, showLabel controls whether the label is drawn at
all. That split is what makes a faded watermark, or an icon with no label,
expressible (spytial-core 4.3).
Usage
Styled = Annotated[list[int], AtomStyle(selector='self', borderStyle=BorderStyle(color='blue'))] Filled = Annotated[Tree, AtomStyle(selector='Node', fillStyle=FillStyle(color='#eef6ff'), textStyle=TextStyle(size='large'))] Marked = Annotated[Tree, AtomStyle(selector='Dir', iconStyle=IconStyle(path='bi:folder-fill', placement='badge'))]
AtomColor
Bases: AtomStyle
Deprecated legacy atom color directive; use AtomStyle instead.
Rewrites to an atomStyle entry with value as the border color (the
legacy directive colored the border, not the fill).
Size
Bases: SpytialAnnotation
Size constraint.
Size fixes a node's geometry, which is what the layout solves over rather
than presentation layered on a solved layout, so it belongs under
constraints (spytial-core 4.3; the directives section still accepts it
behind a deprecation warning).
selector is optional -- omit it to resize every node.
Usage
SizedList = Annotated[list[int], Size(selector='items', height=50, width=50)] AllSquare = Annotated[list[int], Size(height=50, width=50)]
Icon
Bases: SpytialAnnotation
Icon directive.
Usage
IconList = Annotated[list[int], Icon(selector='items', path='icon.svg', showLabels=True)]
EdgeStyle
Bases: SpytialAnnotation
Edge style directive (spytial-core 3.0).
Styles the edges of a field/relation: the drawn line via lineStyle,
the edge's label via textStyle, plus the showLabel/hidden flags.
Usage
StyledEdges = Annotated[Tree, EdgeStyle(field='children', lineStyle=LineStyle(color='red', pattern='dashed'))] LabeledEdges = Annotated[Tree, EdgeStyle(field='Uses', lineStyle=LineStyle(color='#d10000', weight=3), textStyle=TextStyle(size='small'), showLabel=True)] HiddenEdges = Annotated[Tree, EdgeStyle(field='internal', hidden=True)]
EdgeColor
Bases: EdgeStyle
Deprecated legacy edge color directive; use EdgeStyle instead.
Rewrites to an edgeStyle entry: value -> lineStyle.color,
style -> lineStyle.pattern, weight -> lineStyle.weight.
HideField
Bases: SpytialAnnotation
Hide field directive.
Usage
CleanView = Annotated[MyClass, HideField(field='_private')] Filtered = Annotated[MyClass, HideField(field='debug', filter='debug & Production')]
HideAtom
Bases: SpytialAnnotation
Hide atom constraint.
Hiding an atom changes what the layout has to place, and can make a spec
unsatisfiable against the other constraints, so it belongs under
constraints (spytial-core 4.3; the directives section still accepts it
behind a deprecation warning).
Usage
Filtered = Annotated[list[int], HideAtom(selector='hidden')]
Projection
Bases: SpytialAnnotation
Deprecated projection directive; it has no effect.
spytial-core's layout-spec parser does not read projection — projecting
over a sig is a pre-layout transform on the data instance, driven by the
viewer's projection controls, rather than something a layout spec declares.
Authoring one emits YAML that core discards, so remove it.
Usage
Projected = Annotated[MyType, Projection(sig='MySig')] # no-op
Attribute
Bases: SpytialAnnotation
Attribute directive.
Usage
WithAttr = Annotated[MyType, Attribute(field='value')] Filtered = Annotated[MyType, Attribute(field='status', filter='status & Active')]
InferredEdge
Bases: SpytialAnnotation
Inferred edge directive.
Styling uses the shared lineStyle/textStyle blocks (spytial-core 3.0); the inline color/style/weight keys are deprecated and rewritten into lineStyle.
draw (spytial-core 3.2) is '<end> -> <end>', each end '_' (the
atom itself) or the name of a group constraint, in which case that end
attaches to that group's hull -- giving group-to-group and node-to-group
edges. A keyed group (binary selector) is picked by the end's atom; a unary
group is attached to directly. '_ -> _' means the same as omitting it.
Usage
WithEdges = Annotated[Graph, InferredEdge(name='connection', selector='nodes')] StyledEdges = Annotated[Graph, InferredEdge(name='ancestor', selector='^parent', lineStyle=LineStyle(color='gray', pattern='dotted'))] HullEdges = Annotated[Graph, InferredEdge(name='reports to', selector='^parent', draw='regions -> regions')]
Flag
Bases: SpytialAnnotation
Flag directive.
name is one of the two whole-diagram switches core acts on:
'hideDisconnected' (drop atoms with no edges) or
'hideDisconnectedBuiltIns' (drop only disconnected built-in atoms).
Usage
Flagged = Annotated[MyType, Flag(name='hideDisconnected')]
Tag
Bases: SpytialAnnotation
Tag directive for adding computed attributes to nodes.
Unlike 'attribute', this does NOT remove edges - it adds computed attribute values to nodes based on selector evaluation.
Usage
Simple binary tag - displays as "age: 25"
Tagged = Annotated[MyType, Tag(toTag='Person', name='age', value='age')]
Ternary selector - displays as "score[Math]: 95"
Graded = Annotated[MyType, Tag(toTag='Student', name='score', value='grades')]
Four-column result (student, course, exam, grade) displays as
"score[Math,Midterm]: 95".
ExamGraded = Annotated[MyType, Tag(toTag='Student', name='score', value='examGrades')]
name is literal. The value selector must preserve the tagged atom
as its first column; its last column is displayed as the value. A selector
such as Person.age returns ages without their owning Person atoms and
therefore cannot attach those values as tags. Use age instead.
extract_spytial_annotations(type_hint)
Extract spytial annotations from a typing.Annotated type hint.
:param type_hint: A type hint, possibly Annotated with spytial markers. :return: A dict with 'constraints' and 'directives' lists, or None if no annotations.
get_base_type(type_hint)
Get the base type from an Annotated type hint.
:param type_hint: A type hint, possibly Annotated. :return: The base type (unwrapped from Annotated if applicable).
annotate_type_alias(type_alias, annotation_type, **kwargs)
Attach a spytial annotation to a type alias.
This function allows you to annotate type aliases (e.g., list[int], MyTypeAlias) with spatial constraints and directives. When objects are visualized, if their type annotation matches a registered type alias, these annotations are applied.
Usage
Define a type alias
IntList = list[int]
Annotate it
annotate_type_alias(IntList, 'orientation', selector='items', directions=['left']) annotate_type_alias(IntList, 'atomColor', selector='self', value='blue')
Or use the convenience functions:
annotate_type_alias_orientation(IntList, selector='items', directions=['left'])
:param type_alias: The type alias to annotate (e.g., list[int], MyClass, etc.) :param annotation_type: The type of annotation ('orientation', 'group', etc.) :param kwargs: The annotation parameters. :return: The type alias (for chaining).
get_type_alias_annotations(type_alias)
Retrieve annotations registered for a type alias.
:param type_alias: The type alias to look up. :return: A dictionary with 'constraints' and 'directives' lists, or None if not found.
clear_type_alias_annotations(type_alias=None)
Clear annotations for a specific type alias or all type aliases.
Note: This only clears the legacy registry-based annotations. Annotations using typing.Annotated are immutable and don't need clearing.
:param type_alias: If provided, clear only this type alias's annotations. If None, clear all type alias annotations.
list_type_alias_annotations()
List all registered type alias annotations (legacy registry).
:return: A dictionary mapping type alias keys to their annotation registries.
reset_object_ids()
Reset the global object-level state. Useful for testing or when you need deterministic object ID generation across multiple runs.
Clears both global registries — object IDs and object annotations — for the built-in values that can't store that state on themselves.
Warning: This will clear all existing object ID mappings and any annotations recorded for un-attributable objects, so existing selectors that depend on previous object IDs may no longer work.
validate_fields(type_, kwargs, valid_fields)
Validate that the required fields for a given type are present in kwargs. For constraints that support multiple parameter sets (like group), try each set. :param type_: The type of constraint or directive. :param kwargs: The provided fields for the decorator. :param valid_fields: The list of required fields for the type, or a list of alternative field sets. :raises ValueError: If no valid field set matches the provided kwargs.
annotate_orientation(obj, **kwargs)
Apply orientation annotation to a specific object.
annotate_cyclic(obj, **kwargs)
Apply cyclic annotation to a specific object.
annotate_align(obj, **kwargs)
Apply align annotation to a specific object.
annotate_group(obj, **kwargs)
Apply group annotation to a specific object.
annotate_atomColor(obj, **kwargs)
Apply atomColor annotation to a specific object (deprecated; use annotate_atomStyle).
annotate_atomStyle(obj, **kwargs)
Apply atomStyle annotation to a specific object.
annotate_size(obj, **kwargs)
Apply size annotation to a specific object.
annotate_icon(obj, **kwargs)
Apply icon annotation to a specific object.
annotate_edgeColor(obj, **kwargs)
Apply edgeColor annotation to a specific object (deprecated; use annotate_edgeStyle).
annotate_edgeStyle(obj, **kwargs)
Apply edgeStyle annotation to a specific object.
annotate_projection(obj, **kwargs)
Apply projection annotation to a specific object.
annotate_attribute(obj, **kwargs)
Apply attribute annotation to a specific object.
annotate_hideField(obj, **kwargs)
Apply hideField annotation to a specific object.
annotate_hideAtom(obj, **kwargs)
Apply hideAtom annotation to a specific object.
annotate_inferredEdge(obj, **kwargs)
Apply inferredEdge annotation to a specific object.
annotate_tag(obj, **kwargs)
Apply tag annotation to a specific object.
annotate_flag(obj, **kwargs)
Apply flag annotation to a specific object.
annotate(obj, annotation_type, **kwargs)
Apply any annotation type to a specific object. :param obj: The object to annotate. :param annotation_type: The type of annotation (e.g., 'orientation', 'cyclic'). :param kwargs: The annotation parameters. :return: The annotated object (for chaining).
apply_if(condition, *decorators)
Conditional decorator macro for sPyTial. Usage: @apply_if(CONDITION, orientation(...), hideField(...), attribute(...), ... ) class MyClass: ... If CONDITION is True, applies all decorators in order to the class. If CONDITION is False, returns the class unchanged.
collect_decorators(obj, type_hint=None)
Collect all decorators applied to the class of the given object, as well as any annotations applied directly to the object instance, and any annotations registered for matching type aliases. Respects inheritance control flags (dont_inherit_constraints, dont_inherit_directives). :param obj: The object whose class decorators and object annotations should be collected. :param type_hint: Optional type hint to look up type alias annotations. :return: A combined dictionary of constraints and directives (deduplicated).
serialize_to_yaml_string(decorators)
Serialize the collected constraints and directives to a YAML string. :param decorators: The collected decorators (constraints and directives). :return: YAML string representation of the decorators.
dont_inherit_constraints(cls)
Mark a class to not inherit constraints from parent classes.
This is useful when you want to override a parent class's constraints. Constraints applied directly to this class are still included.
Example
@orientation(selector='children', directions=['below']) class Parent: pass
@dont_inherit_constraints class Child(Parent): # Will NOT have Parent's orientation constraint pass
:param cls: The class to mark as not inheriting constraints. :return: The class (for chaining).
dont_inherit_directives(cls)
Mark a class to not inherit directives from parent classes.
This is useful when you want to override a parent class's directives. Directives applied directly to this class are still included.
Example
@atomColor(selector='root', value='red') class Parent: pass
@dont_inherit_directives class Child(Parent): # Will NOT have Parent's atomColor directive pass
:param cls: The class to mark as not inheriting directives. :return: The class (for chaining).
dont_inherit_annotations(cls)
Mark a class to not inherit any annotations from parent classes.
Shorthand for applying both dont_inherit_constraints and dont_inherit_directives.
Example
@orientation(selector='children', directions=['below']) @atomColor(selector='root', value='red') class Parent: pass
@dont_inherit_annotations class Child(Parent): # Will NOT have any annotations from Parent pass
:param cls: The class to mark as not inheriting annotations. :return: The class (for chaining).
Provider system
spytial.provider_system
Spytial-Core Data Instance Relationalizer Architecture
This module provides a pluggable system for converting Python objects into CnD-compatible atom/relation representations using relationalizers.
RelationalizerRegistry
Registry for relationalizers.
register(relationalizer_cls, priority=0)
classmethod
Register a relationalizer class with given priority.
find_relationalizer(obj)
classmethod
Find the first relationalizer that can handle the given object.
list_relationalizers()
classmethod
List all registered relationalizers with their priorities.
Returns:
| Type | Description |
|---|---|
List[Tuple[int, str]]
|
List of (priority, relationalizer_class_name) tuples, sorted by priority (highest first) |
clear()
classmethod
Clear all registered relationalizers (for testing).
CnDDataInstanceBuilder
Main builder that composes providers to build data instances.
build_instance(obj, as_type=None)
Build a complete data instance from an object.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
obj
|
Any
|
The Python object to build an instance from. |
required |
as_type
|
Optional[Any]
|
Optional annotated type to treat the object as. Annotations from this type will be applied in addition to introspected ones. |
None
|
apply_label_persistence(instance)
Lock atom labels at first observation across builds.
Mutates instance in place so that any atom whose ID was already
labelled in a prior build keeps that earlier label, and any new atom's
label is recorded for future builds. Both the top-level atoms list
and each types[*].atoms sub-list are rewritten so the data instance
stays internally consistent.
Used by :class:SequenceRecorder when its label strategy is "persist"
— the same atom ID across frames always renders with one label.
get_collected_decorators()
Get all decorators collected during the build process, deduplicated.
During _walk(), the same class-level decorators are collected once per object instance of that class. This deduplicates them so the final YAML spec contains each unique rule only once.
__call__(obj)
Allow the builder to be called as a function for recursive walking.
None means the walk refused the object — emit no relation for it.
build_types(atoms)
Build the types field for the data instance.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
atoms
|
List[Dict]
|
List of atoms, each containing a "type" field with the most specific type. |
required |
Returns:
| Type | Description |
|---|---|
List[Dict]
|
List of type definitions, each matching the desired format. |
reify(data_instance, root_id=None)
Reconstruct Python objects from a Spytial-Core data instance.
This method reverses the build_instance operation, converting atoms and relations back into Python objects.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data_instance
|
Dict
|
Dictionary containing 'atoms', 'relations', and optionally 'rootId' as returned by build_instance(). |
required |
root_id
|
Optional[str]
|
Optional atom id to use as the reconstruction root. Overrides any 'rootId' present in data_instance. When neither is provided the root is inferred from the relation topology. |
None
|
Returns:
| Type | Description |
|---|---|
Any
|
The reconstructed Python object corresponding to the root atom |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the data instance is malformed or cannot be reconstructed |
replit(data_instance, root_id=None)
Reproduce the host REPL's output for a data instance.
Equivalent to repr(self.reify(data_instance, root_id)). Agreement
with the original value's repr depends on reconstruction support and
that repr's dependence on identity or iteration order. Proxies render
as TypeName(field=value); a cyclic proxy can raise RecursionError.
can_reify(data_instance)
Check if a data instance can be reified.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data_instance
|
Dict
|
Dictionary to check |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if the data instance appears to be valid for reification |
register_reifier(type_name, reifier_func)
Register a custom reifier function for a specific type.
This provides an extensibility mechanism for complex reconstruction scenarios.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
type_name
|
str
|
The atom type name to handle (e.g., "MyCustomClass") |
required |
reifier_func
|
Callable[..., Any]
|
Function with signature (atom, relations, reify_atom) -> object where: - atom: Dict containing atom information (id, type, label, etc.) - relations: Dict mapping relation names to lists of target atom IDs - reify_atom: Function to recursively reify other atoms by ID |
required |
Example
def custom_reifier(atom, relations, reify_atom): # Custom reconstruction logic obj = MyCustomClass() for rel_name, target_ids in relations.items(): setattr(obj, rel_name, [reify_atom(tid) for tid in target_ids]) return obj
builder.register_reifier("MyCustomClass", custom_reifier)
unregister_reifier(type_name)
Remove a custom reifier for a specific type.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
type_name
|
str
|
The atom type name to remove the reifier for |
required |
list_reifiers()
List all registered custom reifier type names.
Returns:
| Type | Description |
|---|---|
List[str]
|
List of type names that have custom reifiers registered |
default_max_depth()
Deepest nesting :meth:CnDDataInstanceBuilder._walk will follow.
Derived from sys.getrecursionlimit() at call time, so raising the
interpreter's limit raises this too: 100 on a stock CPython (limit 1000),
375 at a limit of 3000. Tracked for removal in sidprasad/spytial#131,
which makes the walk iterative and drops the ceiling entirely.
relationalizer(cls=None, *, priority=0)
Decorator to register a class as a relationalizer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
priority
|
int
|
Higher priority relationalizers are tried first. Priorities 0-99 are reserved for built-in relationalizers. |
0
|
reify(data_instance, root_id=None)
Reconstruct a Python object from a Spytial data instance.
Rebuild the object named by the instance's root (or root_id).
Supports built-in values, resolvable references, and class instances with
reconstructible state. Unsupported types can return attribute proxies or
incomplete instances. Shared references and many mutable cycles survive;
cycles through immutable containers can lose identity. This is not a
lossless inverse for every Python value. For dataclass instances whose
fields an editor may have stripped, :func:spytial.edit registers reifiers
that apply declared field defaults before restoring recorded values.
replit(data_instance, root_id=None)
Return repr() of the object :func:reify would reconstruct.
Reproduces the host REPL's echo for a data instance.
Utilities
spytial.utils
Shared utilities for sPyTial.
Typed
A wrapper that preserves type hint information for values.
Use this when you want to pass annotated type information to diagram() without explicitly passing type_hint=.
Example
Graph = Annotated[Dict[int, List[int]], Orientation(...)] g = {0: [1], 1: [2]} diagram(Typed(g, Graph)) # Annotations from Graph will be applied
AnnotatedType
A callable type alias that preserves spytial annotations.
This provides a clean syntax for reusable annotated types:
# Define once
Graph = AnnotatedType(Dict[int, List[int]],
InferredEdge(selector=EDGE_SEL, name=" "))
# Use naturally - Graph() wraps the value with type info
g = Graph({0: [1], 1: [2], 2: []})
diagram(g) # Annotations applied automatically!
The AnnotatedType acts as both: - A type hint (for documentation/type checkers) - A constructor (wraps values with the type info)
__init__(base_type, *annotations)
Create an annotated type alias.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
base_type
|
Any
|
The underlying type (e.g., Dict[int, List[int]]) |
required |
*annotations
|
Any
|
SpytialAnnotation instances (Orientation, InferredEdge, etc.) |
()
|
__call__(value)
Wrap a value with this type's annotations.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
Any
|
The value to wrap |
required |
Returns:
| Name | Type | Description |
|---|---|---|
Typed |
Typed
|
A wrapper that diagram() will recognize |
in_vscode()
True when running under the VS Code Python/Jupyter extension.
The local kernel is reachable there, but the webview's CSP frequently
blanks an inline 127.0.0.1 iframe — so :func:spytial.edit opens an
external browser instead.
edit_environment()
Classify the runtime for :func:spytial.edit's local-server transport.
Returns one of:
"pyodide"— Python runs in the browser; no sockets, so the local server can't be created at all."remote"— a hosted kernel whose127.0.0.1the user's browser can't reach (Google Colab, JupyterHub, Binder)."local"— a local script or a local Jupyter kernel; the server transport works (VS Code is "local"; see :func:in_vscode).
is_notebook()
Detect if we're running in a Jupyter notebook environment. Returns True if in a notebook, False otherwise.
default_method()
Return the default display method based on environment. Returns 'inline' in notebooks, 'browser' otherwise.