tangl.core topology¶
Graph topology primitives used by the story and vm layers.
Related design docs
Related notes
Topology¶
- class Graph(_ctx=None, *, uid=<factory>, label=None, tags=<factory>, templ_hash=None, members=<factory>, factory=None)[source]¶
Specialized registry for graph topology.
Why¶
Graph centralizes topology shape concerns:
typed creation helpers for nodes/edges/subgraphs,
typed selector-based queries,
and graph-level hook bridges for link/unlink operations.
Notes
Creation helpers instantiate
kind(**attrs)directly, then rely on registry ownership for binding and lifecycle hooks.Graphmay also carry an optional singletonfactoryauthority. This is explicitly structured and unstructured by hand so graphs can recover behavior registries after persistence. This mirrors the story-layerStoryGraph.worldround-trip shim and is an interim pattern until core gains generic recursive handling for entity-shaped fields.Typed find helpers apply narrowing via
selector.with_criteria(has_kind=...). Becausewith_criteriaavoids wideninghas_kind, callers may narrow kinds but cannot widen helper defaults.API¶
bind_factory()attaches or clears the singleton graph authority.path_dist()ranks candidates by shared containment ancestry.unstructure()andstructure()preserve singleton factory identity across graph round-trips.
Example
>>> g = Graph() >>> a = Node(label="a", registry=g) >>> b = Node(label="b", registry=g) >>> e = g.add_edge(a, b, label="ab") >>> g.nodes [<Node:a>, <Node:b>] >>> e.predecessor is a and e.successor is b True
- class GraphItem(registry=None, graph=None, *, uid=<factory>, label=None, tags=<factory>, templ_hash=None)[source]¶
Base class for items managed by
Graph.Graph items are registry-aware entities.
graphis the preferred alias for the bound registry pointer when the owner is a graph.
- class Node(registry=None, graph=None, *, uid=<factory>, label=None, tags=<factory>, templ_hash=None)[source]¶
Graph vertex with directional edge navigation and wiring helpers.
- class Edge(registry=None, graph=None, *, uid=<factory>, label=None, tags=<factory>, templ_hash=None, predecessor_id=None, successor_id=None)[source]¶
Directed connection between predecessor and successor graph items.
Notes
Endpoints may be dangling (None) by design.
Access patterns: -
edge.predecessor/edge.successorfor graph-dereferenced properties. -edge.set_predecessor(node, _ctx=...)for explicit context-driven mutation. -edge.predecessor = nodefor ambient-context mutation only.Example
>>> g = Graph() >>> n = Node(label="n", registry=g) >>> e = Edge(registry=g, successor_id=n.uid) >>> g.find_one(Selector(successor=n)) <Edge:anon->n>
- class Subgraph(registry=None, graph=None, *, uid=<factory>, label=None, tags=<factory>, templ_hash=None, member_ids=<factory>)[source]¶
Non-hierarchical grouping of graph items.
Subgraph reuses
EntityGroupmembership semantics and adds graph-level link/unlink hook bridge calls when context is provided.
- class HierarchicalNode(registry=None, graph=None, *, uid=<factory>, label=None, tags=<factory>, templ_hash=None, member_ids=<factory>)[source]¶
Node + hierarchy composition.
This class combines
HierarchicalGroup(parent/path/ancestor semantics) withNode(edge navigation and wiring helpers).
- class Token(registry=None, graph=None, *, uid=<factory>, label=None, tags=<factory>, templ_hash=None, token_from)[source]¶
Dynamic node wrapper around one singleton instance.
Why¶
Tokens let frozen
Singletondefinitions participate in mutable topology. The singleton stores concept-level defaults, while each token stores node-local state.Key Features¶
Split identity:
token_fromreferences the singleton label;labelnames the token node itself.Delegation + override: reads check token fields first, then delegate missing attributes/methods to
reference_singleton.Local instance vars: singleton fields marked
json_schema_extra={"instance_var": True}are materialized as mutable token fields on the generated wrapper class.Wrapper cache: repeated
Token[SomeType]subscriptions reuse the same cached dynamic class keyed by(Token, SomeType).
API¶
wrapped_cls– singleton type bound to the dynamic token wrapper.token_from– singleton label to resolve withinwrapped_cls.label– graph-node name inherited fromNode.reference_singleton– access the underlying instance._instance_vars()– collect instance-var field definitions from the wrapped class._create_wrapper_cls()– emit a new wrapper subclass with those fields.
Notes
Writes are intentionally asymmetric: instance-var fields are writable on the token, while delegated singleton fields remain frozen and cannot be reassigned through the token.
Examples
>>> class SwordType(Singleton): ... damage: str ... sharpness: float = Field(1.0, json_schema_extra={"instance_var": True}) ... def __repr__(self): ... return ( f"<{self.__class__.__name__}:{self.get_label()}" ... f"(damage={self.damage}, sharpness={self.sharpness})>" ) >>> SwordType(label="short sword", damage="1d6") <SwordType:short sword(damage=1d6, sharpness=1.0)> >>> t = Token[SwordType](token_from="short sword", ... label="Glamdring", sharpness=2.0); t <Token[...SwordType]:Glamdring(damage=1d6, sharpness=2.0)> >>> t.has_kind(SwordType) True >>> t.sharpness -= 0.5; t # used it, mutate instance var <Token[...SwordType]:Glamdring(damage=1d6, sharpness=1.5)> >>> SwordType.get_instance("short sword").sharpness # reference unchanged 1.0