loom.core.engine¶
- class loom.core.engine.ComputeStep(fn, accepts_context=False)[source]¶
Bases:
objectA compute transformation step.
Holds a direct reference to the
ComputeFnso the plan is self-contained and requires no back-reference to the UseCase class.
- class loom.core.engine.EventKind(value)[source]¶
Bases:
StrEnumDiscriminator for runtime and compile-time events.
Used by
MetricsAdapterand structured loggers to route events without isinstance checks.
- class loom.core.engine.ExecutionPlan(use_case_type, param_bindings, input_binding, load_steps, exists_steps, compute_steps, rule_steps, read_only=False, caller_binding=None, agent_bindings=(), mcp_bindings=())[source]¶
Bases:
objectImmutable compiled representation of a UseCase’s execution flow.
Built once at startup by
UseCaseCompilerand reused for every request. No dynamic reflection occurs after compilation.- Parameters:
use_case_type (type[Any]) – The
UseCasesubclass this plan was compiled from.param_bindings (tuple[ParamBinding, ...]) – Primitive parameters bound from the caller.
input_binding (InputBinding | None) – Command payload binding, or
Noneif absent.caller_binding (CallerBinding | None) – Caller-identity binding, or
Nonewhen the use case declares noCaller()parameter.agent_bindings (tuple[AgentBinding, ...]) –
Agent()marker bindings, in declaration order; empty when the use case declares none.mcp_bindings (tuple[McpBinding, ...]) –
Mcp()marker bindings, in declaration order; empty when the use case declares none.load_steps (tuple[LoadStep, ...]) – Entity prefetch steps, in declaration order.
exists_steps (tuple[ExistsStep, ...]) – Boolean existence checks, in declaration order.
compute_steps (tuple[ComputeStep, ...]) – Compute transformations, in declaration order.
rule_steps (tuple[RuleStep, ...]) – Rule validations, in declaration order.
read_only (bool) – When
True, the executor skips opening aUnitOfWorktransaction for this use case. Set automatically fromUseCase.read_onlyat compile time. The HTTP layer may also override this at the handler level (e.g. GET routes).
Example:
plan = ExecutionPlan( use_case_type=UpdateUserUseCase, param_bindings=(ParamBinding("user_id", int),), input_binding=InputBinding("cmd", UpdateUserCommand), load_steps=( LoadStep( "user", User, source_kind=SourceKind.PARAM, source_name="user_id", lookup_kind=LookupKind.BY_ID, against="id", ), ), exists_steps=(), compute_steps=(), rule_steps=(), )
- class loom.core.engine.InputBinding(name, command_type)[source]¶
Bases:
objectA command payload parameter marked with
Input().The executor builds the command from the raw payload dict and injects it under this parameter name.
- class loom.core.engine.LoadStep(name, entity_type, source_kind, source_name, lookup_kind, against, profile='default', on_missing=OnMissing.RAISE)[source]¶
Bases:
objectAn entity prefetch step marked with
LoadByIdorLoad.The executor resolves the entity from a repository before calling
execute. Missing behavior is controlled byon_missing.- Parameters:
name (str) – Parameter name as declared in the signature.
source_kind (SourceKind) – Where the lookup value is extracted from.
source_name (str) – Name of param/command field used as lookup value.
lookup_kind (LookupKind) – Lookup strategy (id or arbitrary field).
against (str) – Entity field used in repository lookup.
profile (str) – Loading profile forwarded to
repo.get_by_id. Defaults to"default".on_missing (OnMissing) – Policy when no entity is found.
- class loom.core.engine.MetricsAdapter(*args, **kwargs)[source]¶
Bases:
ProtocolPort for recording runtime and compile-time events.
Implement this protocol to plug in any metrics backend (Prometheus, StatsD, in-memory counters, etc.) without coupling the framework to a concrete provider.
Transport adapters may also implement this protocol to record HTTP or Kafka-level metrics alongside UseCase-level events.
Example:
class PrometheusAdapter: def on_event(self, event: RuntimeEvent) -> None: if event.kind == EventKind.EXEC_DONE: DURATION.labels(usecase=event.use_case_name).observe( (event.duration_ms or 0) / 1000 )
- on_event(event)[source]¶
Process a runtime or compile-time event.
- Parameters:
event (RuntimeEvent) – Immutable event carrying kind, use case name, optional step name, duration, status, and error.
- Return type:
None
- class loom.core.engine.ParamBinding(name, annotation)[source]¶
Bases:
objectA primitive parameter declared in
execute.Represents a positional/keyword argument that is provided by the caller at execution time (e.g.
user_id: int).
- class loom.core.engine.PostCommitChannel[source]¶
Bases:
objectOrdered queue of actions to run after the owning transaction commits.
Two priorities, each FIFO among itself:
enqueue_priority()runs an action ahead of every plainenqueue()action, regardless of the order the two calls were made in. Two actions of the same priority keep the order they were queued in. This only orders actions queued on this channel: a caller with more than one path to the same kind of action —CachedRepositoryqueues its own write’s bump throughenqueue_priority()but the@transactionalhook walk queues the mixins’ tagged bump through plainenqueue()— decides per call site which lane it belongs in; the channel does not infer that from what the action does.Example:
channel = PostCommitChannel() channel.enqueue(lambda: broker.send(message)) await channel.drain(committed=True)
- enqueue(action)[source]¶
Append an action; it runs after every
enqueue_priority()action.
- enqueue_priority(action)[source]¶
Queue an action ahead of every plain
enqueue()action.Use this for an action other queued actions may depend on having already run — a cache invalidation ahead of the job dispatches that might read the cache it invalidates. Two actions queued this way still run in the order they were queued.
- async drain(*, committed)[source]¶
Run every queued action: the priority lane shielded, then the plain lane.
The owner unbinds the channel before draining, so an execution started from an action opens its own lifecycle. A failing action does not stop the others, in either lane; their failures collect into one
PostCommitError.The two lanes carry different durability stories, so they are run differently:
A priority action (
enqueue_priority()) describes a write that has already committed — that is the whole point of deferring it here rather than running it inline. It runs underasyncio.shield: a cancellation reaching this call cannot interrupt it, so it always completes even if the caller gives up on waiting. It is expected to be cheap (a handful of cacheincrcalls), which is what makes the shield safe to take unconditionally.A plain action (
enqueue()) — a job dispatch — has its own durability story and no such requirement, so it stays exactly as interruptible as before the priority lane existed: a cancellation arriving while the plain lane runs stops the drain at once, and the plain actions not yet run stay queued so a later drain can run them. In inline job mode a dispatched job’s body is awaited here as a full nested use-case execution (run), with no framework-imposed timeout — shielding it, as the priority lane is shielded, would make it uncancellable for as long as it ran; this is why the two lanes are not shielded alike.
- Parameters:
committed (bool) – Whether a transaction of this owner’s had committed when the actions ran.
Falsewhen the owner held no unit of work, so a caller may safely retry the whole operation.- Raises:
PostCommitError – If any action raised; carries every failure.
- Return type:
None
- exception loom.core.engine.PostCommitError(*, committed, failures)[source]¶
Bases:
LoomErrorRaised when one or more post-commit actions failed after a commit.
Every action runs even when an earlier one fails; the error carries all failures in enqueue order.
- class loom.core.engine.RuleStep(fn, accepts_context=False)[source]¶
Bases:
objectA rule validation step.
Holds a direct reference to the
RuleFnso the plan is self-contained and requires no back-reference to the UseCase class.
- class loom.core.engine.RuntimeEvent(kind, use_case_name, step_name=None, duration_ms=None, status=None, error=None, trace_id=None, error_kind=None, pipeline_ms=None, commit_ms=None)[source]¶
Bases:
objectImmutable event emitted by the compiler and runtime executor.
Consumed by
MetricsAdapterand structured loggers. Carries no transport-specific data — adapters translate to their own formats.- Parameters:
kind (EventKind) – Event discriminator.
use_case_name (str) – Qualified name of the executing UseCase.
step_name (str | None) – Step label (e.g.
"Load User"), orNone.duration_ms (float | None) – Wall-clock duration in milliseconds, or
None.status (str | None) – Outcome label (e.g.
"success","failure"), orNone.error (BaseException | None) – Exception instance if the event represents a failure, or
None.trace_id (str | None) – Trace the execution ran under, or
None.error_kind (str | None) – Phase that failed on
EXEC_ERROR("begin","business","commit","cancelled","post_commit"), orNone.pipeline_ms (float | None) – Time spent in the use-case pipeline, or
Nonewhen the pipeline did not start.commit_ms (float | None) – Time spent closing the unit of work, or
Nonewhen no commit was attempted.
Example:
event = RuntimeEvent( kind=EventKind.EXEC_DONE, use_case_name="UpdateUserUseCase", duration_ms=12.4, status="success", )
- class loom.core.engine.RuntimeExecutor(compiler, *, uow_factory=None, debug_execution=False, logger=None, metrics=None, repo_resolver=None, agent_resolver=None, mcp_resolver=None)[source]¶
Bases:
objectExecutes UseCases from their compiled ExecutionPlan without reflection.
Receives a fully constructed UseCase instance and drives execution through the fixed pipeline: bind params → build command → load entities → apply computes → check rules → call execute.
Owns the execution lifecycle around that pipeline. When a
uow_factoryis provided, each top-level execution opens aUnitOfWorkthrough its context-manager protocol and closes it the same way: commit on success, rollback on any exception, cancellation included. Nested calls detected via acontextvars.ContextVarshare the outer transaction and never open an additional UoW. Post-commit actions (job dispatches) are queued on aPostCommitChannelowned by the execution that owns the unit of work (or by the outermost execution when there is none) and run once the unit of work has closed.No signature inspection occurs at runtime. All structural information comes from the cached ExecutionPlan produced by UseCaseCompiler.
Emits
RuntimeEventobjects to the optionalMetricsAdapter:EXEC_STARTbefore the unit of work opens and exactly one terminal event,EXEC_DONEonce the unit of work has committed and closed orEXEC_ERRORwith theerror_kindof the step that failed. Enriches log calls with structured fields (usecase,duration_ms,status) for structured log consumers.- Parameters:
compiler (UseCaseCompiler) – Compiler used to retrieve cached plans.
uow_factory (UnitOfWorkFactory | None) – Optional UoW factory. When provided, each top-level
execute()call is wrapped in a single atomic transaction. Nested executions within the same async context reuse the outer UoW.debug_execution (bool) – When
True, emits[STEP]logs for every pipeline stage. Defaults toFalse(summary logs only).logger (LoggerPort | None) – Optional logger. Defaults to the framework logger.
metrics (MetricsAdapter | None) – Optional metrics adapter. When provided, receives
EXEC_START,EXEC_DONE, andEXEC_ERRORevents.repo_resolver (Callable[[type[Any]], Any] | None) – Optional callable that resolves a repository instance from an entity model type. Used by
Load/Existswhendependenciesoverride is not passed toexecute().agent_resolver (Callable[[str, Identity], Any] | None) – Optional callable resolving an
Agent()marker parameter to a handle, given the agent’s deployment name and the verified caller of this execution.Noneuntilbind_agent_resolver()is called — see that method for why it is bound after construction rather than passed in here.mcp_resolver (Callable[[str, tuple[str, ...], Identity], Any] | None) – Optional callable resolving an
Mcp()marker parameter to a handle, given the marker’s server name, its own declaredincludeand the verified caller of this execution.Noneuntilbind_mcp_resolver()is called, for the same reasonagent_resolveris bound after construction.
Example:
executor = RuntimeExecutor( compiler, uow_factory=SQLAlchemyUnitOfWorkFactory(session_manager), metrics=prometheus_adapter, ) result = await executor.execute( use_case, params={"user_id": 1}, payload={"email": "new@corp.com"}, )
- bind_agent_resolver(resolver)[source]¶
Bind the resolver used by every
Agent()marker parameter.A separate step from construction because the resolver depends on the AI runtime, and the AI pillar is optional and built later: the composition root creates this executor first — so every other use case can be compiled and served regardless of whether an
ai:section exists — and wires this resolver in second, once that runtime exists. A use case declaringAgent()before this is called fails at its first execution with a clear error, not with a missing-attribute crash.- Parameters:
resolver (Callable[[str, Identity], Any]) – Builds one agent handle from the agent’s deployment name and the verified caller of one execution.
- Raises:
RuntimeError – If a resolver is already bound.
- Return type:
None
- bind_mcp_resolver(resolver)[source]¶
Bind the resolver used by every
Mcp()marker parameter.A second resolver, not an overload of
bind_agent_resolver(): that one is typed to two arguments (the agent name and the verified caller), this one to three (the server name, the marker’s ownincludeand the verified caller). Serving both kinds through one callable would need a discriminant argument telling them apart, which is the multi-behaviour flag this project’s engineering rules forbid — so this is a distinct method, bound separately, the same waybind_agent_resolver()is bound separately from construction: the AI runtime it depends on exists only once the AI pillar has built it.
- async run(use_case_type, *, factory, params=None, payload=None, dependencies=None, load_overrides=None, read_only=False, identity=None)[source]¶
Build
use_case_typethroughfactoryand execute it.- Parameters:
use_case_type (type[Compilable]) – Compiled use case or job class to build and run.
factory (UseCaseFactory) – Factory that constructs the instance with its dependencies.
params (dict[str, Any] | None) – Primitive parameter values keyed by name.
payload (dict[str, Any] | None) – Raw dict for command construction via
Input().dependencies (dict[type[Any], Any] | None) – Mapping of entity type to repository for the load steps.
load_overrides (dict[type[Any], Any] | None) – Pre-loaded entities by type, bypassing repo calls.
read_only (bool) – When
True, no unit of work is opened.identity (Identity | None) – Verified caller for this execution.
- Returns:
The result produced by
execute().- Return type:
Any
- async execute(compilable: UseCase[Any, ResultT], *, params: dict[str, Any] | None = None, payload: dict[str, Any] | None = None, dependencies: dict[type[Any], Any] | None = None, load_overrides: dict[type[Any], Any] | None = None, read_only: bool = False, identity: Identity | None = None) ResultT[source]¶
- async execute(compilable: Job[ResultT], *, params: dict[str, Any] | None = None, payload: dict[str, Any] | None = None, dependencies: dict[type[Any], Any] | None = None, load_overrides: dict[type[Any], Any] | None = None, read_only: bool = False, identity: Identity | None = None) ResultT
- async execute(compilable: Compilable, *, params: dict[str, Any] | None = None, payload: dict[str, Any] | None = None, dependencies: dict[type[Any], Any] | None = None, load_overrides: dict[type[Any], Any] | None = None, read_only: bool = False, identity: Identity | None = None) Any
Execute a compiled instance via its ExecutionPlan.
Accepts any object satisfying the
Compilableprotocol — bothUseCaseandJobinstances are valid inputs.When a
uow_factorywas provided at construction and no UoW is already active in the current async context, enters a fresh UoW, runs the pipeline, and exits it: commit on success, rollback on any exception, cancellation included. Nested calls reuse the existing UoW transparently. Post-commit actions queued during the execution run after the UoW has closed; when they fail the result is aPostCommitErrorand the transaction stays committed.- Parameters:
compilable (Compilable) – Constructed instance to execute.
params (dict[str, Any] | None) – Primitive parameter values keyed by name.
payload (dict[str, Any] | None) – Raw dict for command construction via
Input().dependencies (dict[type[Any], Any] | None) – Mapping of entity type to repository, used for
LoadById()/Load()/Exists()steps.load_overrides (dict[type[Any], Any] | None) – Pre-loaded entities by type, bypassing repo calls. Used by test harnesses.
read_only (bool) – When
True, skips opening aUnitOfWorktransaction even if auow_factorywas provided. Automatically set toTrueby the HTTP layer for GET routes. Also honoured whenplan.read_onlyisTrue.identity (Identity | None) – Verified caller for this execution, supplied by the transport. Required when the plan declares a
Caller()parameter; passANONYMOUSexplicitly to run a declared-identity use case without a caller.
- Returns:
The result produced by
execute().- Raises:
loom.core.errors.RuleViolations – If one or more rule steps fail.
NotFound – If a Load step finds no entity in the repository.
Unauthenticated – If the plan declares
Caller()and no identity was supplied.loom.core.engine.post_commit.PostCommitError – If a post-commit action failed after the unit of work committed.
- Return type:
- class loom.core.engine.UseCaseCompiler(logger=None, metrics=None)[source]¶
Bases:
objectCompiles UseCase subclasses into immutable ExecutionPlans at startup.
Inspects the
executesignature exactly once per class, validates structural constraints, and caches the resulting plan.No reflection occurs after compilation — the plan drives all runtime execution via
RuntimeExecutor.- Parameters:
logger (LoggerPort | None) – Optional logger. Defaults to the framework logger.
metrics (MetricsAdapter | None) – Optional metrics adapter. When provided, receives
COMPILE_STARTandCOMPILE_DONEevents.
Example:
compiler = UseCaseCompiler(metrics=my_adapter) plan = compiler.compile(UpdateUserUseCase)
- compile(use_case_type)[source]¶
Return the ExecutionPlan for
use_case_type, compiling if needed.Compilation is idempotent: calling this method multiple times with the same class returns the cached plan without re-inspection. Accepts any class satisfying the
Compilableprotocol — bothUseCaseandJobsubclasses are valid.- Parameters:
use_case_type (type[Compilable]) – Concrete compilable class to compile.
- Returns:
Immutable ExecutionPlan.
- Raises:
CompilationError – If the signature violates structural constraints.
- Return type:
- get_plan(use_case_type)[source]¶
Return the cached plan for
use_case_type, orNone.- Parameters:
use_case_type (type[Compilable]) – UseCase subclass to look up.
- Returns:
Cached ExecutionPlan if compiled, otherwise
None.- Return type:
ExecutionPlan | None