httk.workflow.sdk¶
The Python authoring SDK for native httk₂ workflow runners.
One runner is one program that implements the steps of one workflow. Steps are
registered on a Runner, and Runner.main() dispatches the step the
manager asked for to the handler that implements it, giving it one
Attempt object:
from httk.workflow import ChildSpec, Runner
run = Runner("defects")
@run.step
def characterize(a):
for site in range(a.input("sites")):
a.spawn(ChildSpec(step="relax", inputs={"site": site}), label=f"site-{site}")
a.gather("aggregate", on_impossible="triage")
if __name__ == "__main__":
raise SystemExit(run.main())
Nothing declares the shape of the workflow up front: a step decides at run time which children to spawn and which step runs next, so the graph of a job is whatever its steps published. Exactly one outcome is published per attempt, and the handler that returns without publishing one, or raises, is reported as such instead of leaving the attempt ambiguous.
Attributes¶
Classes¶
Which runner executes a child job synthesized by |
|
A complete child job described by the step and inputs it starts with. |
|
What one gathering step may know about one child it spawned. |
|
The children observed by the join that started this activation. |
|
Everything one attempt of one step may read, do, and publish. |
|
The registered steps of one workflow and the dispatch into them. |
Module Contents¶
- class httk.workflow.sdk.RunnerRef[source]¶
Which runner executes a child job synthesized by
ChildSpec.A synthesized child has no payload of its own, so its runner must be one that lives outside a payload: an entry of the workspace runner store, or an installed runner on the machine that runs it.
inherit()copies the reference of the spawning job itself, which is what a campaign whose steps all live in one published runner wants.
- class httk.workflow.sdk.ChildSpec[source]¶
A complete child job described by the step and inputs it starts with.
Everything not given follows the spawning job: its workflow, its claim pool, its priority, its resources, and its runner. The child therefore differs from its parent in exactly what the campaign varies, which is normally only step and inputs.
- inputs: collections.abc.Mapping[str, object][source]¶
- declarations: collections.abc.Mapping[str, collections.abc.Mapping[str, object]][source]¶
- class httk.workflow.sdk.ChildResult[source]¶
What one gathering step may know about one child it spawned.
Every member is derived from authoritative state by the manager before the gathering activation starts, so reading a child is a pure read of the attempt context and never a scan of the workspace. Paths are absolute.
- placement: pathlib.PurePosixPath[source]¶
- payload: pathlib.Path[source]¶
- workdir: pathlib.Path | None[source]¶
- data: pathlib.Path | None[source]¶
- class httk.workflow.sdk.ChildrenView[source]¶
The children observed by the join that started this activation.
The view is empty for an activation that follows no join, so a step can read it unconditionally.
- all: tuple[ChildResult, Ellipsis] = ()[source]¶
- property succeeded: tuple[ChildResult, Ellipsis][source]¶
The children that ended successfully, in spawn order.
- property failed: tuple[ChildResult, Ellipsis][source]¶
The children that ended badly, in spawn order.
- get(label: str, default: ChildResult | None = None) ChildResult | None[source]¶
Return the child spawned under label, or default.
- class httk.workflow.sdk.Attempt(context: httk.workflow.runtime.AttemptContext, *, control: pathlib.Path, payload: pathlib.Path, workdir: pathlib.Path, workspace: pathlib.Path, data: pathlib.Path | None = None, step: str | None = None, runner: Runner | None = None)[source]¶
Everything one attempt of one step may read, do, and publish.
An attempt owns exactly one implicit outcome draft. The draft is created by the first
spawn(),put(), orremove(), and it is published by exactly one ofadvance(),gather(),succeed(),retry(),pause(), orfail(). Publication is the single atomic rename the manager observes, so nothing a step did takes effect until the step says how it ended.- classmethod initialize(environment: collections.abc.Mapping[str, str] | None = None, *, runner: Runner | None = None) Self[source]¶
Bind this process to its attempt and recover an interrupted one.
Recovery replays every workdir batch an earlier attempt sealed but did not get to apply, so a handler always starts from a workdir whose sealed changes are complete. This is the only constructor a runner needs.
- property job: httk.workflow.models.JobDefinition[source]¶
The immutable definition of the job this attempt belongs to.
- property inputs: collections.abc.Mapping[str, object][source]¶
The application-defined
inputsobject of this job.
- property children: ChildrenView[source]¶
The children observed by the join that started this activation.
- input(name: str, default: object = _MISSING) object[source]¶
Return one member of the job’s
inputsobject.Without a default, a missing input is a
KeyError: a step that needs an input cannot run without it, and saying so immediately is better than failing later on a value that was never there.
- setting(name: str, default: object = None) object[source]¶
Resolve one application setting through its layers.
The layers are consulted most-specific first, and the first that has the name wins: this job’s
inputsobject, then the environment variableHTTK_+ the name upper-cased with dots as underscores (sovasp.commandreadsHTTK_VASP_COMMAND), then the workspace’s application settings, then default. This is how a step reads the VASP command a workspace was configured with without the operator exporting it for every job, while still letting one job or one shell override it.
- declare(name: str, document: collections.abc.Mapping[str, object]) pathlib.Path[source]¶
Record the observed workflow declaration name of this job.
The static declarations of a job are the ones
job.jsoncarried at submission, and they cannot change. A dynamic campaign nevertheless only learns at run time what it actually consumed and produced, so a step writes the refined document here and it is stored beside the job state as.httk-job/declarations/<name>.json, atomically. The bytes are carried verbatim: nothing here interprets the document, whose own members say which vocabulary and version it follows.The write is runner-private, so it never disturbs the payload digest, and repeating it overwrites: what a job observed is whatever its last word on the subject was. A harvest reports the observed document beside the declared one and never merges the two.
- declaration(name: str) collections.abc.Mapping[str, object] | None[source]¶
Return the workflow declaration name, observed first.
The document this job observed is returned when one was written, otherwise the one
job.jsondeclared, otherwiseNone.
- run(argv: collections.abc.Sequence[str], *, timeout: float | None = None, cwd: str | os.PathLike[str] | None = None, environment: collections.abc.Mapping[str, str] | None = None, termination_grace: float = 10.0) httk.workflow.runtime.CommandResult[source]¶
Run an argv array in the workdir and reap its process group.
- workdir_batch() httk.workflow.runtime_builders.ReplayableWorkdirBatch[source]¶
Start a replayable group of workdir changes.
- put(source: str | os.PathLike[str], destination: str | os.PathLike[str]) str[source]¶
Stage one file or directory for the job’s transactional data.
The operation is applied by the manager when the outcome is committed, exactly once, whatever happens to this process in between. Operation identifiers are generated in call order, so replaying the same step produces the same manifest.
- remove(destination: str | os.PathLike[str], *, missing_ok: bool = False) str[source]¶
Remove one path from the job’s transactional data.
- spawn(child: ChildSpec | str | os.PathLike[str], *, label: str, placement: str | pathlib.PurePosixPath | None = None) httk.workflow.runtime_builders.ChildReference[source]¶
Register one child job under label, to be created on publication.
child is either a
ChildSpec, which needs no payload at all, or the path of a prepared payload directory. The label is mandatory and must be unique within one attempt: it is howgather()andchildrenname this child later.
- advance(step: str, *, state: collections.abc.Mapping[str, object] | None = None, priority: int | None = None) pathlib.Path[source]¶
Publish a new activation of this job at step.
state is written to
statebefore the outcome is published, so the step that runs next always finds the state that decided to run it.
- gather(step: str, *, when: httk.workflow.runtime_builders.JoinCondition = 'all_succeeded', count: int | None = None, on_impossible: str | None = None) pathlib.Path[source]¶
Wait for the children spawned on this attempt, then run step.
The join names exactly the children
spawn()registered on this attempt, which is also the bundle that creates them, so the manager can always resolve it. when is one ofall_succeeded,all_terminal,any_succeeded, orat_leastwith count. When the condition can no longer be met, the job advances to on_impossible if one is named and fails withdependency_failureotherwise.
- succeed() pathlib.Path[source]¶
Publish the successful completion of this job.
- retry(reason: str) pathlib.Path[source]¶
Ask for another attempt of this same activation.
- pause(reason: str) pathlib.Path[source]¶
Pause this job until an operator resumes it.
- fail(code: str, message: str, *, details: collections.abc.Mapping[str, object] | None = None, retryable: bool = False) pathlib.Path[source]¶
Publish a structured terminal failure.
codeis the token a job lists inretry_on.retryabledeclares that repeating this attempt could help, which the manager honours within the attempt budgets of the job.
- class httk.workflow.sdk.Runner(workflow: str)[source]¶
The registered steps of one workflow and the dispatch into them.
A runner is created once at module level, its steps are registered with
step()before any work happens, andmain()is what the manager invokes. Registration is therefore complete before the first step runs, which is what lets every step name in a published outcome be checked against the steps that really exist.- step(function: StepHandler) StepHandler[source]¶
- step(*, name: str | None = None) collections.abc.Callable[[StepHandler], StepHandler]
Register one step handler, named after the function unless name is given.
- main(argv: collections.abc.Sequence[str] | None = None) int[source]¶
Run the step this process was launched for and publish its outcome.
Asked to describe itself — through
HTTK_WORKFLOW_DESCRIBE=1or--describe— the runner prints its description and exits without touching anything, so a tool can enumerate the steps of a runner it is not running.Every ending is an outcome: a step that publishes none is reported as
no_outcome, an unimplemented step asunknown_step, and a step that raises leaves anerror.jsonbreadcrumb and lets the exception reach the manager, whose retry policy owns what happens next.