Skip to content

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.

class

class JobContext

Fields

NameTypeDefaultDescription
runnerrunner
job_idjob_id
Methods and properties (5)

init constructor

JobContext(self, runner: JobRunner, job_id: str) -> None

job property

job: Job

step 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) -> None

Attach structured progress to a step (JobStep.data) and notify watchers.

log method

def log(self, detail: str) -> None

Append a detail line to the running step (or a synthetic ‘log’ step).

Source: packages/ump-sdk/src/ump_sdk/jobs.py, line 82

class

class JobRunner

Fields

NameTypeDefaultDescription
sourcesource
jobsdict[str, Job]{}
Methods and properties (6)

init constructor

JobRunner(self, source: str = 'ump') -> None

submit 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,
) -> Job

Schedule 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) -> Job

wait method

async def wait(self, job_id: str, timeout: float | None = None) -> Job

cancel method

def cancel(self, job_id: str) -> Job

watch 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

function

def accepted(
job: Job,
*,
prefix: str = '/v1/jobs',
extra: dict[str, Any] | None = None,
adapter: bool = False,
) -> JSONResponse

Spec §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

function

def mount_job_routes(
app: FastAPI,
runner: JobRunner,
prefix: str = '/v1/jobs',
*,
core_url: str | None = None,
transport: Any = None,
) -> None

GET {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

NameTypeValueDescription
JobFnCallable[['JobContext', dict[str, Any]], Awaitable[Any]]
TERMINALfrozenset({'succeeded', 'failed', 'cancelled'})
OPS_ROLESfrozenset({'log_operator', 'key_custodian', 'anchor_admin'})Roles that may read and cancel any job (operations work; they still author no client work).