Python API

The module is eessi_arch_guard. The TaskProlog and the console script both call eessi_arch_guard.main(). eessi_arch_guard.decide() compares one environment. eessi_arch_guard.limited_decide() applies the deadline. eessi_arch_guard.probe_cpu() and eessi_arch_guard.probe_accel() read the node.

The page below is generated from the module. Names that start with an underscore are omitted.

Reload EESSI modules when a Slurm task lands on another CPU or GPU target.

The TaskProlog target is this file. See DESIGN.md for the design record. The documentation is under docs/. Output is either Slurm TaskProlog lines (export, unset, print) or, with –shell, bash assignments for eval. Default TaskProlog mode exits 0 unless a chained site prolog exits non-zero. Opt-in –strict returns 78 for failed decisions and unexpected exceptions. A non-zero status cancels the task.

class eessi_arch_guard.Outcome(changes: Dict[str, str | None], message: str | None, kind: str, detail: str | None = None)[source]

Environment delta, optional message, kind, and optional detail.

kind is skip, noop, reloaded, or failed.

changes: Dict[str, str | None]

Alias for field number 0

message: str | None

Alias for field number 1

kind: str

Alias for field number 2

detail: str | None

Alias for field number 3

class eessi_arch_guard.CpuProbe(status: str, target: str, candidates: Tuple[str, ...], error: str, override: str = '')[source]

CPU walk result.

status is ok, none, or error. override is the installed directory when it differs from the unfiltered archdetect choice.

status: str

Alias for field number 0

target: str

Alias for field number 1

candidates: Tuple[str, ...]

Alias for field number 2

error: str

Alias for field number 3

override: str

Alias for field number 4

class eessi_arch_guard.AccelProbe(status: str, subdir: str, error: str)[source]

Accelerator probe. status is ok, none, or error.

status: str

Alias for field number 0

subdir: str

Alias for field number 1

error: str

Alias for field number 2

class eessi_arch_guard.LoadedModules(eessi: str, others: Tuple[str, ...], error: str)[source]

The one EESSI/<version> name, the other loaded names, and an error token.

eessi: str

Alias for field number 0

others: Tuple[str, ...]

Alias for field number 1

error: str

Alias for field number 2

class eessi_arch_guard.RestoreResult(ok: bool, env: Dict[str, str] | None, missing: Tuple[str, ...], error: str)[source]

Helper result: success flag, restored environment, missing modules, error text.

ok: bool

Alias for field number 0

env: Dict[str, str] | None

Alias for field number 1

missing: Tuple[str, ...]

Alias for field number 2

error: str

Alias for field number 3

eessi_arch_guard.one_line(text: str, limit: int = 500) → str[source]

Single line, no controls, bounded. Safe to place after print or on stderr.

eessi_arch_guard.module_name_ok(name: str) → bool[source]

True when name matches the module grammar and is at most 256 characters.

eessi_arch_guard.safe_rel_subdir(value: str) → bool[source]

Relative EESSI subdirectory: no empty, absolute, or .. segments.

eessi_arch_guard.safe_absolute(path: str) → bool[source]
eessi_arch_guard.strip_startup(env: Mapping[str, str]) → Dict[str, str][source]
eessi_arch_guard.value_emit_ok(value: str, multiline: bool) → bool[source]
eessi_arch_guard.environment_delta(old: Mapping[str, str], new: Mapping[str, str], multiline: bool) → Dict[str, str | None] | None[source]

Changes to publish. None means a value cannot be carried: publish nothing.

eessi_arch_guard.message_bytes(text: str) → bytes[source]

One locale-encoded message, inside the prolog line buffer.

eessi_arch_guard.write_fd(fd: int, data: bytes) → None[source]

Write bytes to a descriptor. A failed write is dropped.

eessi_arch_guard.render_bytes(changes: Mapping[str, str | None], message: str | None, shell: bool, announce: bool) → bytes[source]

Prolog or shell bytes. Export values keep the bytes they stand for.

eessi_arch_guard.render(changes: Mapping[str, str | None], message: str | None, shell: bool, announce: bool) → str[source]

Prolog or shell text. Prolog values are literal; shell values are quoted.

eessi_arch_guard.parse_loaded_modules(env: Mapping[str, str]) → LoadedModules[source]
eessi_arch_guard.parse_cpu_line(stdout: str) → List[str] | None[source]
class eessi_arch_guard.DecisionLimit(seconds: float)[source]

One deadline for the lock, both archdetect runs, and the restore helper.

remaining() → float[source]
check() → None[source]
install() → None[source]
restore() → None[source]
hold_signals() → None[source]
release_signals() → None[source]
eessi_arch_guard.run_captured(argv: Sequence[str], env: Mapping[str, str], input_bytes: bytes | None = None, new_session: bool = False) → CompletedProcess[source]

Run a command. Under a decision limit, its session is killed at the deadline.

eessi_arch_guard.read_cpu_flags(path: str) → frozenset[source]

Intersection of every flags line. One CPU without the flag rejects it.

eessi_arch_guard.march_of(candidate: str) → str[source]
eessi_arch_guard.candidate_supports_cpu(candidate: str, flags: frozenset) → bool[source]

False when this march implies waitpkg and the CPU flags omit it.

eessi_arch_guard.probe_cpu(env: Mapping[str, str]) → CpuProbe[source]

Select the first installed cpupath candidate that satisfies the waitpkg rule.

eessi_arch_guard.probe_accel(env: Mapping[str, str]) → AccelProbe[source]

Return accelpath stdout, or status none when the script reports no GPU.

eessi_arch_guard.lmod_invocable(path: str) → bool[source]
eessi_arch_guard.restore_work_base(env: Mapping[str, str]) → str[source]

Select the configured workspace without creating or widening its base.

eessi_arch_guard.source_exports(module_env: Mapping[str, str], script: bytes) → Tuple[Dict[str, str] | None, str][source]
eessi_arch_guard.run_lmod(module_env: Mapping[str, str], lmod_cmd: str, args: Sequence[str]) → CompletedProcess[source]
eessi_arch_guard.apply_lmod(module_env: Mapping[str, str], lmod_cmd: str, args: Sequence[str]) → Tuple[Dict[str, str] | None, str][source]
eessi_arch_guard.to_wire(value: str) → str[source]

One character per byte this process would pass to an exec.

eessi_arch_guard.from_wire(value: str) → str[source]

Inverse of to_wire in this process.

eessi_arch_guard.do_restore(payload: Mapping[str, object]) → Dict[str, object][source]

Purge and reload from a JSON request. The return value is the JSON object.

eessi_arch_guard.restore_main() → int[source]
eessi_arch_guard.call_restore(env: Mapping[str, str], eessi: str, modules: Sequence[str]) → RestoreResult[source]
eessi_arch_guard.decide(env: ~typing.Mapping[str, str], shell: bool, cpu_probe=<function probe_cpu>, accel_probe=<function probe_accel>, restore=<function call_restore>) → Outcome[source]

Compare the loaded target with this node and build the environment delta.

eessi_arch_guard.cache_digest(env: Mapping[str, str]) → str[source]
eessi_arch_guard.cache_directory(env: Mapping[str, str]) → str | None[source]
eessi_arch_guard.cache_file_path(env: Mapping[str, str], shell: bool = False) → str | None[source]

Cache path for this uid, node, job, step, and output mode. None when the job id is unset.

The mode is part of the name because the decision depends on it: a value with a newline reloads for –shell and fails in a prolog, and only a prolog line has the Slurm 17.11 length cap.

eessi_arch_guard.cached_decide(env: ~typing.Mapping[str, str], shell: bool, cpu_probe=<function probe_cpu>, accel_probe=<function probe_accel>, restore=<function call_restore>) → Outcome[source]
eessi_arch_guard.limited_decide(env: ~typing.Mapping[str, str], shell: bool, timeout: float = 60.0, cpu_probe=<function probe_cpu>, accel_probe=<function probe_accel>, restore=<function call_restore>) → Outcome[source]

Decide under one deadline. A timeout or SIGTERM is not written to the cache.

eessi_arch_guard.parse_timeout(text: str) → float | None[source]

A positive number of seconds at most TIMEOUT_MAX, or None.

eessi_arch_guard.resolve_timeout(cli_text: str | None, settings: Mapping[str, str] | None) → Tuple[float, str | None][source]

CLI overrides the config key, which overrides DEFAULT_TIMEOUT.

eessi_arch_guard.parse_config(text: str) → Dict[str, str][source]
eessi_arch_guard.parse_config_bytes(blob: bytes) → Tuple[Dict[str, str], Tuple[str, ...]][source]

Settings from raw config bytes. A bad value does not drop other keys.

eessi_arch_guard.parse_chain_config(text: str) → str | None[source]
eessi_arch_guard.config_chain(settings: Mapping[str, str] | None) → str | None[source]
eessi_arch_guard.site_config(script_path: str) → Tuple[Dict[str, str] | None, str | None][source]

Return (settings, warning). The first existing candidate wins.

eessi_arch_guard.configured_chain(script_path: str) → Tuple[str | None, str | None][source]

Return (chain, warning). A user-owned config is not a chain source.

eessi_arch_guard.run_chain(path: str, env: Mapping[str, str]) → Tuple[int, bytes, bytes][source]
class eessi_arch_guard.Args(shell: bool, restore: bool, help: bool, version: bool, chain: str | None, timeout: str | None, strict: bool = False)[source]

Parsed output mode, helper mode, configuration and failure policy.

shell: bool

Alias for field number 0

restore: bool

Alias for field number 1

help: bool

Alias for field number 2

version: bool

Alias for field number 3

chain: str | None

Alias for field number 4

timeout: str | None

Alias for field number 5

strict: bool

Alias for field number 6

eessi_arch_guard.parse_args(argv: Sequence[str]) → Args[source]

Parse the prolog argv. Flags other than the documented ones are skipped.

eessi_arch_guard.main(argv: Sequence[str] | None = None) → int[source]

Run the guard with its selected decision failure policy.