Workers and scripts
HTTP is one caller of an application use case. execute() applies Tenchi's
request validation and context-scoping rules from jobs, command-line scripts,
message consumers, or tests without inventing a second contract.
Execute validated input
from tenchi.execution import execute
async def handle_job(payload: bytes, context: AppContext) -> Todo:
return await execute(
create_todo,
request_json=payload,
context=context,
)request_json= validates serialized input against the use case's annotated
request parameter. Use request= for an already decoded Python value.
execute() deliberately supplies only request and context. A use case that
requires HTTP-specific params, query, or headers is not portable to this
entrypoint and fails with ExecutionError.
Open a context explicitly
from tenchi.execution import open_context
async with open_context(create_request_context()) as context:
result = await execute(rebuild_index, context=context)open_context() accepts a direct value, an awaitable, or an async context
manager. This mirrors the server's context behavior, including cleanup for
transactional or request-scoped resources.
Observe the use case
Pass the same observer to HTTP composition and direct execution:
import logging
from tenchi.execution import UseCaseOutcome, execute
logger = logging.getLogger("app.operations")
def observe_use_case(outcome: UseCaseOutcome) -> None:
operation = str(
getattr(outcome.use_case, "__name__", type(outcome.use_case).__name__)
)
logger.info(
"use_case.complete",
extra={
"operation": operation,
"entrypoint": outcome.entrypoint,
"status": outcome.status,
"duration_seconds": outcome.duration_seconds,
"completed_at": outcome.completed_at.isoformat(),
"error_code": outcome.error_code,
},
)
result = await execute(
rebuild_index,
context=create_request_context,
use_case_observers=(observe_use_case,),
)create_app(use_case_observers=(observe_use_case,)) reports the HTTP path
through the same UseCaseOutcome shape. The observer runs after context
cleanup, while duration_seconds measures only the use-case call and
completed_at records when that call returned or raised.
No outcome is emitted when validation, context acquisition, or a boundary hook
fails before the use case starts. Observer failures are logged and do not
replace the use case's result or exception. Keep observers fast or hand work to
a non-blocking telemetry pipeline because execute() waits for them before it
returns.
Keep transport concerns outside
The message consumer acknowledges, retries, or dead-letters work. The use case
owns application behavior. Convert message metadata into application input
before calling execute() rather than passing a queue SDK object into the
application layer.
Authorization remains in the use case. When a job acts for a user, construct a context with verified actor identity and let the same policy checks run.
For durable delivery, idempotent commands, and retry classification, continue
with Background jobs and Retries and background
work. Use execute() directly when the caller already owns the
function identity and no durable name or producer contract is needed. For
operator-invoked backfills, repairs, replays, and maintenance, use
Operational tasks
to add stable discovery plus result validation around the same use cases.