tangl.core identity¶
Identity, selection, and registry primitives used throughout the engine.
Related design docs
Related notes
Identity and lookup¶
- class Entity(_ctx=None, *, uid=<factory>, label=None, tags=<factory>, templ_hash=None)[source]¶
Canonical concrete core entity composed from identity + constructor-form traits.
Why¶
Entityis intentionally minimal. It adds no persistent fields beyondUnstructurableandHasIdentityand exists mainly to:fix the default trait composition order for core entities, and
inject lifecycle dispatch hooks during creation paths.
Notes
Inheritance order is
(Unstructurable, HasNamespace, HasIdentity).HasNamespaceadds namespace contribution behavior, while__eq__still compares by value viaUnstructurable.eq_by_value().Two entities with the same
uidbut different constructor-form values are not equal under==.Use
HasIdentity.eq_by_id()when you need identity-only comparison.
Dispatch hook control signal
Pass
_ctxto__init__orstructure()to activate dispatch hooks. The underscore prefix means this is a control argument and is not stored as model data. If a subclass also has actxdata field, both can coexist:CallReceipt(result=5, ctx=context, _ctx=context)
Example
>>> e = Entity(label="abc") >>> f = Entity(uid=e.uid, label=e.label) >>> e is not f and e.eq_by_value(f) and e == f True >>> e.eq_by_id(f) True >>> g = Entity(uid=e.uid, label="different") >>> e.eq_by_id(g) and e != g True
See also
tangl.core.dispatchHook registration and execution behavior.
tangl.core.ctxAmbient context helpers for hook propagation.
- class Selector(*, predicate=None, **extra_data)[source]¶
Pure query predicate model for matching entities.
Why¶
Selectors decouple query criteria from entity implementation. Entities expose attributes and predicate-like methods; selectors provide reusable matching logic.
Key Features¶
Selectoris a Pydantic model, not anEntity.predicatestores an optional custom callable pre-check.With
extra="allow", all criteria kwargs beyondpredicateare stored in__pydantic_extra__and interpreted bymatches().
Notes
Criterion value
typing.Anyis a wildcard and is skipped.Noneis not a wildcard; it is compared normally.Any callable entity attribute is invoked with the criterion value, not only
has_*/is_*names (those are conventions, not requirements).Avoid properties that return callables on matchable names because selector callable detection is based on runtime
callable(...)checks.
Example
>>> class E(Entity): ... @property ... def label_rev(self): ... return self.label[::-1] >>> e = E(label="abc") >>> Selector( ... predicate=lambda x: x.label == "abc", ... label="abc", ... has_kind=E, ... label_rev="cba", ... ).matches(e) True
>>> s = Selector(has_identifier="abc") >>> f = E(label="def") >>> list(s.filter([e, f])) [<E:abc>]
- class Registry(_ctx=None, *, uid=<factory>, label=None, tags=<factory>, templ_hash=None, members=<factory>)[source]¶
Indexed owning collection with selection and chaining.
A Registry is core’s owning boundary for intra-related entities. It is the canonical dereference mechanism for ID-linked graphs:
members are indexed by uid: UUID
other references should store only UUIDs and dereference through a registry
### Selection
Use find_one / find_all with a Selector for flexible matching. Do not overload get() with fuzzy identifier logic; get() is strictly UUID → entity.
### Layering
chain_find_all is core’s primitive for layered composition:
treat multiple registries as a search chain
yields matching members across all registries in order
### Persistence
members is declared with Field(exclude=True) so Pydantic model dumps do not automatically include it. unstructure() and structure() handle member payloads explicitly.
Registry.unstructure() includes all members as unstructured constructor-form dicts. Registry.structure() recreates the registry and re-adds structured members.
### Duplicate IDs
add() silently overwrites existing members for duplicate uid keys.
### Dispatch hooks
Pass _ctx to add, get, or remove to allow higher layers to intercept operations.
__setitem__ intentionally raises; callers must use add() so registry-aware binding and dispatch hooks remain consistent.
Examples
>>> a = Entity(label="abc"); b = Entity(label="def") >>> r = Registry(); r.add(a); r.add(b) >>> len(r.members) 2 >>> r.get(a.uid) # indexed by uid <Entity:abc> >>> r.all_labels() == {"abc", "def"} True >>> s = Selector.from_identifier("abc") >>> r.find_one(s) <Entity:abc> >>> c = Entity(label="abc") >>> q = Registry(); q.add(c) >>> list(Registry.chain_find_all(r, q, selector=s)) == [a, c] True >>> data = r.unstructure() >>> rr = Registry.structure(data) >>> len(rr.members) 2 >>> r is not rr and r == rr # compare by value True >>> rr.add(Entity()) # compare by value includes members field >>> rr != r True
- class Singleton(*, label, uid=<factory>, tags=<factory>, templ_hash=None)[source]¶
Process-local singleton keyed by
labelper concrete subclass.Why¶
Singletons represent immutable concept-level references that should not be duplicated in-memory. They are identified by
(class, label)rather than(class, uid).Key Features¶
each subclass gets an isolated
_instancesregistry via__init_subclass__();duplicate labels are rejected before model construction;
un/structuring is reference-only (kind + label), resolving to existing instances.
Notes
structure()expects the referenced instance to already exist.use tokens when singleton concepts need graph-local mutable state.
Example
>>> a = Singleton(label="abc"); _ = Singleton(label="def") >>> Singleton.has_instance("abc") True >>> Singleton.get_instance("abc") is a True >>> data = a.unstructure() >>> Singleton.structure(data) is a True
- class InstanceInheritance(*, label, inherit_from=None, uid=<factory>, tags=<factory>, templ_hash=None)[source]¶
Singleton with creation-time field inheritance from another instance.
inherit_fromcopies non-identity, non-private fields from the referent at creation time only. Explicit kwargs override inherited values.Example
>>> class S(InstanceInheritance): value: str = Field() >>> S(label="foo", value="bar").value 'bar' >>> S(label="baz", inherit_from="foo").value 'bar' >>> S(label="foobar", inherit_from="baz").value 'bar'