ump_sdk.jobs
module ump-sdk
Tiny in-process background job helper (Temporal stand-in) with step history.
Usage in an app::
runner = JobRunner(source="models")mount_job_routes(app, runner) # GET /v1/jobs, /v1/jobs/{id}, /v1/jobs/{id}/events (SSE)
async def fit(ctx: JobContext, params): async with ctx.step("validate"): ... async with ctx.step("fit", detail="ridge + bootstrap"): result = await asyncio.to_thread(do_fit, params) return {"model_id": ...} # stored as job.result
job = runner.submit("fit", fit, {"client_id": "acme-us"}, steps=["validate", "fit"])return accepted(job) # 202, Location: /v1/jobs/{id}, {job_id, state, status_url, ...}A job has state (queued, running, succeeded, failed, cancelled) and, once terminal, result or
problem (RFC 9457), as spec §19 names them. GET /v1/jobs?state= filters by state.
Jobs live in memory only; a restart loses them (production: Temporal workflows).
Who sees a job (mount_job_routes): every job records the submitting principal and its client_id
(the client_id= argument, else params["client_id"]). List, get, events and :cancel serve a job to
- the principal that submitted it;
- a service principal (
service:*); - an ops role (
log_operator,key_custodian,anchor_admin) with a verified scope from core; - a caller whose verified core scope covers the job’s
client_id.
Anyone else gets 404 NOT_FOUND (the job is also left out of the list), so job ids do not leak across
clients. A job with no client is its submitter’s (plus services and ops). A job submitted with no principal
(adapters and the e2e runner, which run behind an app that already checked the caller) stays open to every
caller, as before. When core cannot be asked the scope is unverified and only the first two rules apply.
Classes
Section titled “Classes”JobContext
Section titled “JobContext”class
class JobContextFields
| Name | Type | Default | Description |
|---|---|---|---|
runner | runner | ||
job_id | job_id |
Methods and properties (5)
init constructor
JobContext(self, runner: JobRunner, job_id: str) -> Nonejob property
job: Jobstep method
async def step(self, name: str, detail: str = '') -> AsyncIterator[JobStep]set_data method
def set_data(self, name: str, data: dict[str, Any] | None) -> NoneAttach structured progress to a step (JobStep.data) and notify watchers.
log method
def log(self, detail: str) -> NoneAppend a detail line to the running step (or a synthetic ‘log’ step).
Source: packages/ump-sdk/src/ump_sdk/jobs.py, line 82
JobRunner
Section titled “JobRunner”class
class JobRunnerFields
| Name | Type | Default | Description |
|---|---|---|---|
source | source | ||
jobs | dict[str, Job] | {} |
Methods and properties (6)
init constructor
JobRunner(self, source: str = 'ump') -> Nonesubmit method
def submit( self, kind: str, fn: JobFn, params: dict[str, Any] | None = None, *, steps: list[str] | None = None, principal: str | None = None, client_id: str | None = None,) -> JobSchedule fn(ctx, params) on the running event loop; returns the queued Job.
principal and client_id (default params["client_id"]) decide who may read or cancel it.
get method
def get(self, job_id: str) -> Jobwait method
async def wait(self, job_id: str, timeout: float | None = None) -> Jobcancel method
def cancel(self, job_id: str) -> Jobwatch method
async def watch(self, job_id: str, poll: float = 0.5) -> AsyncIterator[Job]Yield the job each time it changes, ending after a terminal state.
Source: packages/ump-sdk/src/ump_sdk/jobs.py, line 129
Functions
Section titled “Functions”accepted
Section titled “accepted”function
def accepted( job: Job, *, prefix: str = '/v1/jobs', extra: dict[str, Any] | None = None, adapter: bool = False,) -> JSONResponseSpec §19: 202 Accepted with Location: {prefix}/{id} for a call that runs as a job. An app’s body is
{job_id, state, status_url, events_url, job} plus extra; an adapter’s (adapter=True) is exactly
the engine profile’s JobAccepted {job_id, state, status_url}. status_url equals Location.
Source: packages/ump-sdk/src/ump_sdk/jobs.py, line 67
mount_job_routes
Section titled “mount_job_routes”function
def mount_job_routes( app: FastAPI, runner: JobRunner, prefix: str = '/v1/jobs', *, core_url: str | None = None, transport: Any = None,) -> NoneGET {prefix}, GET {prefix}/{id}, GET {prefix}/{id}/events (SSE), POST {prefix}/{id}:cancel.
Scoped per the module docstring: the submitter, services, ops roles and callers scoped to the job’s client
see and cancel a job; others get 404. core_url/transport reach core /v1/whoami (default
base_url("core")).
Source: packages/ump-sdk/src/ump_sdk/jobs.py, line 254
Constants
Section titled “Constants”| Name | Type | Value | Description |
|---|---|---|---|
JobFn | Callable[['JobContext', dict[str, Any]], Awaitable[Any]] | ||
TERMINAL | frozenset({'succeeded', 'failed', 'cancelled'}) | ||
OPS_ROLES | frozenset({'log_operator', 'key_custodian', 'anchor_admin'}) | Roles that may read and cancel any job (operations work; they still author no client work). |