Skip to main content

errand

errand runs your work in any environment — a conda or uv env, a container, another machine over ssh, a Slurm allocation, any stack of them — building and updating it from what it declares, and syncing your files there and back.

An errand is a trip made on someone's behalf: go there, do the thing, come back with what it produced.

# errand-envs.py
import errand

errand.envs[ "local" ]   = errand.Env( [ errand.Micromamba( "proj", python = "3.13", requirements = "requirements.txt" ) ] )
errand.envs[ "cluster" ] = errand.Env( [ errand.Ssh( host = "gpu-box", root = "~/proj" ),
                                         errand.Slurm( gpus = 1, time = "2:00:00" ),
                                         errand.Apptainer( image = "cuda.sif", recipe = "cuda.def" ) ] )
errand run --env local pytest                       # created and installed on first use
errand run --env cluster python train.py --lr 1e-3  # rsync, srun, apptainer, and back
errand run --env cluster --pull out make results    # ...and out/ comes back here
errand run --env local,cluster ./bench              # in both places, one after the other
errand shell --env cluster                          # be let in, to look around

The site has everything, split by theme, with tutorials: https://hleclerc.github.io/errand/ (built from docs/).

Installing

pip install errand-run

The distribution is errand-run — errand on PyPI is somebody else's project — but what you type and what you import is errand. It has no dependencies, and is not going to get any: its job is to build the environment the work runs in, so it has to run before any environment exists.

Environments are stacks of layers

Read outside in: that machine, an allocation on it, a container, your command.

layer
Micromamba, Conda, Uv, Venv an interpreter and its packages: python =, requirements =, packages =, pip =
Nix, Guix, Module a flake or a manifest; module load the way a cluster picks a toolchain
Apptainer, Docker, Podman a container, built from its recipe when it is missing or changed
Ssh another machine: the project is rsynced there before, what was asked for comes back after
Slurm, Oar, Pbs, Sge, Lsf an allocation, sized from what the work asks for
Windows a Windows VM under QEMU that errand installs and boots — MSVC included
Vars environment variables for the child

Kept current by themselves. Before every run, errand compares what each layer declares — a Python version, a requirements.txt, a container recipe, a pip list — with what it last built, and brings it up to date when they have drifted. An environment that already exists the first time errand sees it is adopted as it is, never rebuilt on somebody's first command.

errand envs              # what is declared, and in what state
errand setup --dry-run   # what bringing it up to date would run

Files go there and come back. Over Ssh the project is pushed with rsync before the command runs; --pull DIR brings a directory back afterwards, and also = [ "../lib" ] pushes a sibling checkout the work needs. Ask for an output directory with -o and it is made over there, handed to the command as $ERRAND_OUT_DIR, and fetched back here.

Several places at once. A comma is several environments, and environments carry tags:

errand run --env local,cluster ./bench
errand run -t cuda ./bench             # every environment tagged cuda
errand run --fp 32,64 ./bench          # an environment that does not say fp covers both

What you get on top

  • The machine to yourself when it matters. errand keeps a queue per host, outside the process: errand run -x ./bench waits for what is running and holds everything else back; --cpus 8, --gpus 1 take a share, and a card is assigned, not merely counted. Inside a Slurm allocation errand does not queue — the scheduler already decided.
  • Let it run. errand run --batch … gives the shell back — a detached session here, a released ssh command there, an sbatch on a cluster — and errand status / errand watch say how it goes.
  • A record of every run, in .errand/: what ran, where, when, how it ended, what it printed. With -o, a directory per run under runs/, whose path is computed from the work, the place and the date, so that two machines or two days sit side by side.
  • One screen for all of it: errand tui, drawn with the standard library's curses.

Files, suites and entries

What follows errand run is read from the command line alone, never guessed:

errand run train.py --lr 1e-3          # a .py: run by the python of the place
errand run ./bench                     # anything else: executed as it is
errand run make check                  # ...or a command of the place
errand run 'tests/**::*'               # x::y: the entries of a suite -- pytest, Catch2, cargo
errand run tests/test_api.py::test_ok  # one of them
errand run -r bench 'tests/**::*'      # through a runner of the project's own

x::y goes to the runner that recognises the file — pytest, Catch2, cargo, or errand's own entries — which collects one entry per test, records each, and translates its vocabulary: a @pytest.mark.slow is a tag that -e '!slow' filters on. A project names its own runner instances in errand-project.py, and says what a bare errand run means:

# errand-project.py
import errand

errand.runners[ "bench" ] = errand.Pytest( dirs = [ "tests/bench" ] )
errand.runners.replace( errand.Catch2( dir = "cpp", build = "cmake --build build" ) )
errand.default_run = [ "tests/**::*", "cpp/**::*" ]

errand also has entries of its own, for work that has no framework — if test( "name" ): next to the code, with typed parameters and numbers to follow. See Writing entries.

Configuration

None is needed: errand run <command> works in an empty directory. A project's errand-*.py files sit at its root and are ordinary Python; errand init writes a first pair.

errand-project.py what the project does: runners, default_run, errand.configure( … ). Versioned
errand-envs.py where it runs: the environments. Versioned when it names no machine
errand-vars.py what only your machine knows — hosts, paths — read first, never versioned

Examples

In examples/: environments over three machines, an existing pytest / Catch2 / cargo project, and errand's own entries.

Status

Everything above works and is covered by errand's own suite. Not released: the API may still move. How errand's own entries are expressed — needs, numbers to follow, skips — is being reworked into a protocol any program can follow (ADR 0003).

Metadata

Release files for errand-run 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for errand-run 0.1.1
File Size Uploaded Provenance Actions
errand_run-0.1.1.tar.gz 343.2 kB Without provenance Details for errand_run-0.1.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for errand-run 0.1.1
File Interpreter ABI Platform Actions
errand_run-0.1.1-py3-none-any.whl Python 3 none any Details for errand_run-0.1.1-py3-none-any.whl

Total release size: 493.1 kB

Release files / errand_run-0.1.1.tar.gz

Download URL errand_run-0.1.1.tar.gz
Size 343.2 kB
Tags Source
SHA-256 checksum
How to use checksums
2febe3c710e6d0d9565b5b22a74e770ad5ba2b0ada117ac94ed0ecc8c889bb5c
BLAKE2b-256 checksum
How to use checksums
83c432a0c86538690edfdf1bf934b5940fdee5e757b0c99f43e7d8f6f3cfc164
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.1

Release files / errand_run-0.1.1-py3-none-any.whl

Download URL errand_run-0.1.1-py3-none-any.whl
Size 149.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
49c16fc700fdd5d5f6b6a13b7945dc29916344175c4b0d60349e2add76228ecf
BLAKE2b-256 checksum
How to use checksums
5eecbebf02d0cd95b93853701930041c0b9847526940a3cbd70fa34c91a84aa8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.1

Security for errand-run 0.1.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page