Source code for modelly.experiments.worker
"""The job-array task entry point: ``python -m modelly.experiments.worker TASKS [INDEX]``.
``TASKS`` is the ``tasks.json`` a SLURM or PBS executor wrote; ``INDEX`` picks
one task from it (default: ``$SLURM_ARRAY_TASK_ID``, ``$PBS_ARRAY_INDEX`` or
``$PBS_ARRAYID``). The task runs, its Parquet file goes to the store, and the
exit status is 0 when it finished, 1 when it failed (recorded in the store).
"""
from __future__ import annotations
import json
import os
import sys
from pathlib import Path
from modelly.experiments.tasks import DONE, RunOutcome, RunTask, run_task
EXIT_DONE, EXIT_FAILED, EXIT_USAGE = 0, 1, 2
MAX_ARGUMENTS = 2 # TASKS and INDEX
INDEX_VARIABLES = ("SLURM_ARRAY_TASK_ID", "PBS_ARRAY_INDEX", "PBS_ARRAYID")
[docs]
def load_tasks(path: str | Path) -> list[RunTask]:
"""The tasks a cluster executor wrote to ``path``."""
rows = json.loads(Path(path).read_text(encoding="utf-8"))
return [RunTask.from_dict(row) for row in rows]
[docs]
def array_index(argument: str | None = None) -> int:
"""The task index: ``argument``, else the scheduler's array index variable."""
if argument is not None:
return int(argument)
for variable in INDEX_VARIABLES:
if os.environ.get(variable):
return int(os.environ[variable])
raise LookupError(
f"no task index: pass one or set one of {', '.join(INDEX_VARIABLES)}"
)
[docs]
def run_array_task(tasks_file: str | Path, index: int) -> RunOutcome:
"""Run task ``index`` of ``tasks_file`` (what each job-array element does)."""
return run_task(load_tasks(tasks_file)[index])
[docs]
def main(argv: list[str] | None = None) -> int:
"""Run one task of a job array; the exit status."""
argv = sys.argv[1:] if argv is None else argv
if not argv or len(argv) > MAX_ARGUMENTS:
sys.stderr.write(
f"usage: python -m modelly.experiments.worker TASKS [INDEX]\n{__doc__}"
)
return EXIT_USAGE
try:
index = array_index(argv[1] if len(argv) == MAX_ARGUMENTS else None)
outcome = run_array_task(argv[0], index)
except (LookupError, ValueError, OSError) as error:
sys.stderr.write(f"modelly.experiments.worker: {error}\n")
return EXIT_USAGE
if outcome.status == DONE:
return EXIT_DONE
sys.stderr.write(outcome.error or "run failed\n")
return EXIT_FAILED
if __name__ == "__main__":
sys.exit(main())