tangl.core dispatch¶
Behavior and dispatch primitives that higher layers use to register and execute hooks.
Related design docs
Related notes
Behaviors¶
- class Behavior(registry=None, graph=None, *, uid=<factory>, label=None, tags=<factory>, templ_hash=None, seq=None, func=<function Behavior.<lambda>>, task=None, priority=Priority.NORMAL, dispatch_layer=DispatchLayer.LOCAL, wants_caller_kind=None, wants_exact_kind=True)[source]¶
Example
>>> b = Behavior(func=lambda *nums, **kwargs: sum(nums)) >>> receipt = b(1, 2, 3) >>> f"sum{receipt.args}={receipt.result}" 'sum(1, 2, 3)=6' >>> deferred = b.defer() >>> assert deferred.result is None >>> deferred.resolve(4, 5, 6) 15 >>> f"sum{deferred.args}={deferred.result}" 'sum(4, 5, 6)=15' >>> c = Behavior(func=lambda *_, **__: True, wants_caller_kind=Entity) >>> assert Selector(caller_kind=Entity).matches(c) and not Selector(caller_kind=dict).matches(c)
- class BehaviorRegistry(_ctx=None, *, uid=<factory>, label=None, tags=<factory>, templ_hash=None, members=<factory>, default_task=None, default_priority=Priority.NORMAL, default_dispatch_layer=DispatchLayer.APPLICATION)[source]¶
Example
>>> br = BehaviorRegistry() >>> f = br.register(lambda *nums, **kwargs: sum(nums), task="sum") >>> g = br.register(lambda *args, **kwargs: ''.join([str(a) for a in args]), ... task="join", priority=Priority.EARLY) >>> next( br.execute_all(task="sum", call_args=(1, 2, 3)) ).result 6 >>> next( br.execute_all(task="join", call_args=('a', 'b', 'c')) ).result 'abc' >>> CallReceipt.gather_results( *br.execute_all(call_args=(1, 2, 3)) ) ... # join triggers first even tho registered last, b/c lower priority ['123', 6]
- class CallReceipt(_ctx=None, *, uid=<factory>, label=None, tags=<factory>, templ_hash=None, seq=None, origin_id=None, result=None, callback=None, args=None, kwargs=None, ctx=None, **kwargs_)[source]¶
Aggregation Modes:
Receipt aggregation or folding summarizes dispatch traces into a concrete result or list of results. One key detail is that behaviors that return a None result are tracked with a receipt for audit, but do not participate in result reduction. Several generic aggregators are implemented as class functions on Receipt (handling for collections of receipts). Additional aggregators can be introduced at other layers.
Supported aggregation helpers: -
first_result: first non-Noneresult (single winner). -last_result: last non-Noneresult (override pattern). -all_true: all non-Noneresults are truthy (validation gates). -gather_results: collect all non-Noneresults. -merge_results: flatten lists or merge dicts (later entries override).Examples
>>> receipts = [ CallReceipt(result=None), ... CallReceipt(result=1), ... CallReceipt(result=0), ... CallReceipt(result=None) ] >>> CallReceipt.gather_results(*receipts) [1, 0] >>> CallReceipt.first_result(*receipts) 1 >>> CallReceipt.last_result(*receipts) 0 >>> CallReceipt.all_true(*receipts) False >>> CallReceipt.merge_results(CallReceipt(result=[1,2,3]), ... CallReceipt(result=[4,5,6])) # flattens [1, 2, 3, 4, 5, 6] >>> dict( CallReceipt.merge_results(CallReceipt(result={'a': 'foo'}), ... CallReceipt(result={'b': 'bar'}), ... CallReceipt(result={'a': 'baz'})) ) # late overrides {'a': 'baz', 'b': 'bar'}
Dispatch module¶
Default global dispatch registry and explicit hook pairs for core lifecycle events.
Hook pairs are exposed as explicit decorators and runners:
creation: -
on_create/do_create(pre-structuring,data -> data) -on_init/do_init(post-init,caller -> None)registry indexing: -
on_add_item/do_add_item(registry, item -> item) -on_get_item/do_get_item(registry, item -> item) -on_remove_item/do_remove_item(registry, item -> None)graph relationships: -
on_link/do_link(caller, node -> None) -on_unlink/do_unlink(caller, node -> None)
Context contract¶
Dispatch chaining is driven by the runtime context protocol used by
tangl.core.behavior.BehaviorRegistry:
ctx.get_authorities()contributes extra registries by layer.ctx.get_inline_behaviors()contributes one-off callables.
Callers normally pass _ctx to hook-aware APIs (for example Entity(..., _ctx=ctx),
Entity.structure(..., _ctx=ctx), Registry.add(..., _ctx=ctx)), or rely on ambient
context via using_ctx.
Aggregation contracts¶
do_createfolds hook results withCallReceipt.merge_results().do_add_itemanddo_get_itemuseCallReceipt.last_result().do_init,do_remove_item,do_link, anddo_unlinkforce receipt evaluation withCallReceipt.gather_results().
Example
>>> assert Entity(label="bar").label == "bar"
>>> _ = on_init(func=lambda *, caller, ctx = None, **kwargs: setattr(caller, "label", "foo"))
>>> item = Entity(
... label="bar",
... _ctx=SimpleNamespace(
... get_authorities=lambda: [],
... get_inline_behaviors=lambda: [],
... ),
... ) # calls global dispatch by default
>>> assert item.label == "foo"
>>> q = BehaviorRegistry()
>>> _ = q.register(task="init", dispatch_layer=DispatchLayer.APPLICATION,
... func=lambda *, caller, ctx = None, **kwargs: setattr(caller, "label", "baz"))
>>> item = Entity(
... label="bar",
... _ctx=SimpleNamespace(
... get_authorities=lambda: [q],
... get_inline_behaviors=lambda: [],
... ),
... )
>>> assert item.label == "baz"
>>> dispatch.clear() # always clean up global registries after using
Dispatch Layer Mapping¶
GLOBAL / core /
core.dispatch/ auditing and loggingSYSTEM / vm /
vm.dispatch/ phase handlers and provisioningSYSTEM / service /
service.dispatch/ API and persistenceAPPLICATION / story /
story.dispatch/ content rendering and domain rulesAPPLICATION / mechanics /
story.dispatch/ story semantics extensionsAPPLICATION / discourse /
story.dispatch/ prose model extensionsAPPLICATION / media /
story.dispatch/ media extensionsAUTHOR / world /
world.dispatch/ world-specific mechanicsLOCAL / vm.frame /
vm.frame.dispatch/ one-off handlers