httk.workflow.scaffold¶
Scaffolding submitted jobs from a template, some files, and some inputs.
A job is a payload directory plus a job.json that names the runner to execute,
and building one by hand means knowing the runner’s workflow name, its initial
step, its digest, and where its inputs live in the payload. This module is the
short way: new_job() takes a template — a packaged runner a domain
registered by name, or the path of a runner file of your own — stages the files
the runner reads, writes the job.json, and submits the result, all in one
call.
Packaged templates are not known to this module. A domain or compat engine
registers each one it ships with register_template(), supplying only the
generic description of a starting point — its name, the runner it starts from,
the workflow and steps that runner declares, the modes a job of it defaults to,
and what it does — so the scaffold resolves and pins a template without ever
importing the science that owns it.
from httk.workflow import Workspace
from httk.workflow.scaffold import new_job
workspace = Workspace.initialize("workflow-workspace", extensions=["transactional-data-v1"])
job = new_job(workspace, "some-template", files={"input": "input"}, tag="example")
print(job.job_key, job.payload)
By default the runner file is published into the workspace runner store, and the
job references it there by digest. That is what makes a scaffolded job durable:
the bytes that will run are pinned in the workspace, so upgrading the installed
httk-workflow underneath a queued campaign cannot change what its jobs execute.
Publication is content addressed — the store name carries the digest of the
bytes — so scaffolding the same template twice publishes nothing the second time,
and a later, different version of a packaged runner lands beside the old one
instead of replacing it. publish="installed" instead references a packaged
template through the reserved pkg: form, which copies nothing at all.
new_jobs() is the same operation for a campaign: one template resolution and
one publication amortized over every job, and a lazy iterator over the results. By
design, generating a partitioned campaign costs one payload and one marker per
job and never materializes a list of them.
Attributes¶
Classes¶
One packaged template a domain or compat engine offers by name. |
|
What one job of a |
|
One resolved starting point for a job: a runner file and how to run it. |
|
One job this module submitted, and everything needed to look at it again. |
Functions¶
|
Register one packaged template, replacing any registered under its name. |
|
Return the name of every registered template, in registration order. |
|
Return the provider name names, by template name or runner file. |
|
Return the self-description one runner file prints, by running it. |
|
Return the registered template name names, or |
|
Return the |
|
Return every structure file of one directory, in a stable order. |
|
Return the job tag one structure file name suggests, or |
|
Scaffold, submit, and describe one job of template. |
|
Scaffold and submit one job per member of items, lazily. |
|
Return where one staged file lands inside a payload. |
Module Contents¶
- class httk.workflow.scaffold.TemplateProvider[source]¶
One packaged template a domain or compat engine offers by name.
A provider is the generic description of a starting point. It names the packaged runner it starts from by the package the runner file is a module of and the file beside that module — so the reserved
pkg:form and the digest are resolved from the provider alone — and it declares the runner’s workflow, its steps, the modes a job of it defaults to, and what it does. Declaring the steps here rather than running the runner to ask keeps scaffolding cheap; the owning domain’s tests hold the declaration to what the runner really describes.
- httk.workflow.scaffold.register_template(provider: TemplateProvider) None[source]¶
Register one packaged template, replacing any registered under its name.
A domain calls this once per template it ships when its package is imported, which is how
httk workflow job new --template NAMEresolves a packaged runner the generic scaffold never names.
- httk.workflow.scaffold.registered_templates() tuple[str, Ellipsis][source]¶
Return the name of every registered template, in registration order.
- httk.workflow.scaffold.template_provider(name: str) TemplateProvider | None[source]¶
Return the provider name names, by template name or runner file.
- class httk.workflow.scaffold.JobItem[source]¶
Bases:
TypedDictWhat one job of a
new_jobs()campaign varies from the shared values.Every member is optional, and a member that is absent takes the value
new_jobs()was called with.inputsandfilesare merged over the shared mappings key by key; everything else replaces the shared value.- inputs: collections.abc.Mapping[str, object][source]¶
- files: collections.abc.Mapping[str, str | os.PathLike[str]][source]¶
- placement: str | pathlib.PurePosixPath[source]¶
- class httk.workflow.scaffold.JobTemplate[source]¶
One resolved starting point for a job: a runner file and how to run it.
A template is either one of the runners a domain registered by name — see
registered_templates()— or a runner file of your own, which is described by running it — every native runner answers--describewith its workflow and its steps — so a scaffolded job never guesses either.- source: pathlib.Path[source]¶
- property store_name: str[source]¶
The content-addressed name this template takes in a runner store.
The digest of the bytes is part of the name, so publishing is idempotent for identical bytes and never overwrites a name a submitted job pinned: an upgraded packaged runner is published beside the version its queued jobs still reference.
- class httk.workflow.scaffold.ScaffoldedJob[source]¶
One job this module submitted, and everything needed to look at it again.
- placement: pathlib.PurePosixPath[source]¶
- payload: pathlib.Path[source]¶
- marker: pathlib.Path[source]¶
- runner: collections.abc.Mapping[str, object][source]¶
- httk.workflow.scaffold.describe_runner(runner: str | os.PathLike[str]) dict[str, object][source]¶
Return the self-description one runner file prints, by running it.
Every native runner — Python or Bash — answers
HTTK_WORKFLOW_DESCRIBE=1with its workflow name and its registered steps and exits without touching anything, which is how a runner nobody wrote a template for is still scaffolded without being told what it implements.
- httk.workflow.scaffold.packaged_template(name: str) JobTemplate | None[source]¶
Return the registered template name names, or
None.
- httk.workflow.scaffold.resolve_template(template: str | os.PathLike[str], *, workflow: str | None = None, step: str | None = None, data_mode: DataMode | None = None) JobTemplate[source]¶
Return the
JobTemplatetemplate names.template is the name of a packaged template, the file name of a packaged runner, or the path of a runner file. A runner file is described by running it, so its workflow name and its steps come from the runner itself; workflow and step override what it said, and step is required when a runner registers several steps and none of them is
start.
- httk.workflow.scaffold.structure_files(directory: str | os.PathLike[str]) list[pathlib.Path][source]¶
Return every structure file of one directory, in a stable order.
A structure is a file matching one of
STRUCTURE_PATTERNS— the VASP conventionsPOSCAR,POSCAR.something, andsomething.vasp— which is what makes a directory of structures one campaign.
- httk.workflow.scaffold.structure_tag(path: str | os.PathLike[str]) str | None[source]¶
Return the job tag one structure file name suggests, or
None.The tag is the part of the name that identifies the structure —
Si2OofPOSCAR.Si2O,fcc-aloffcc-al.vasp— reduced to the tag syntax the protocol allows. A name that says nothing beyondPOSCARsuggests no tag.
- httk.workflow.scaffold.new_job(workspace: httk.workflow.workspace.Workspace, template: str | os.PathLike[str], *, inputs: collections.abc.Mapping[str, object] | None = None, files: collections.abc.Mapping[str, str | os.PathLike[str]] | None = None, tag: str | None = None, placement: str | pathlib.PurePosixPath = DEFAULT_PLACEMENT, priority: int | None = None, workdir_mode: WorkdirMode = 'persistent', data_mode: DataMode | None = None, publish: PublishMode = 'workspace', step: str | None = None, workflow: str | None = None, name: str | None = None) ScaffoldedJob[source]¶
Scaffold, submit, and describe one job of template.
template is a registered template name — see
registered_templates()— or the path of a runner file. files maps payload names to the files to stage there: a bare name lands in the payload’sFILES_DIRECTORY, which is where a packaged runner reads its inputs, and a name with a directory in it is used verbatim. inputs becomes the job’sinputsobject, whose members are documented per runner.data_mode defaults to what the template needs —
transactionalfor a template whose runner publishes collected results, andnonefor a runner that said nothing. publishworkspacepublishes the runner file into the workspace runner store and pins its digest;installedreferences a packaged runner through the reservedpkg:form instead and copies nothing.
- httk.workflow.scaffold.new_jobs(workspace: httk.workflow.workspace.Workspace, template: str | os.PathLike[str], items: collections.abc.Iterable[JobItem], *, inputs: collections.abc.Mapping[str, object] | None = None, files: collections.abc.Mapping[str, str | os.PathLike[str]] | None = None, tag: str | None = None, placement: str | pathlib.PurePosixPath = DEFAULT_PLACEMENT, priority: int | None = None, workdir_mode: WorkdirMode = 'persistent', data_mode: DataMode | None = None, publish: PublishMode = 'workspace', step: str | None = None, workflow: str | None = None, name: str | None = None) collections.abc.Iterator[ScaffoldedJob][source]¶
Scaffold and submit one job per member of items, lazily.
Every keyword is the shared value of the whole campaign, and every member of one
JobItemis what that job varies:inputsandfilesare merged over the shared mappings, andtag,name,placement, andpriorityreplace the shared value.This is the pattern for a campaign of any size. The template is resolved once and its runner published once, however many jobs follow, so every job costs exactly one payload directory and one state marker; items is consumed as an iterator and the results are yielded as they are submitted, so a structure generator can be turned into jobs without either side of the loop ever being materialized.
def structures(): for path in sorted(Path("structures").glob("POSCAR.*")): yield {"files": {"POSCAR": path}, "tag": structure_tag(path)} for job in new_jobs(workspace, "some-template", structures(), inputs={"kpoint_density": 30.0}): print(job.job_key)
- httk.workflow.scaffold.payload_relative(name: str) pathlib.PurePosixPath[source]¶
Return where one staged file lands inside a payload.
A bare name lands in
FILES_DIRECTORY, soPOSCARbecomesfiles/POSCAR— where the packaged runners read it — and a name that carries a directory of its own is used exactly as it is written.