Python DSL for defining Harmont CI pipelines. Pipelines are chains of shell commands, branched with .fork(), synchronized with hm.wait(), registered with a decorator, and rendered to a JSON IR consumed by the Harmont runner.
Pipelines defined with this package are run by the companion harmont-cli (hm run).
The package installs as harmont and you import it as harmont:
import harmont as hmharmont is not yet published to PyPI. Install from source:
git clone https://github.com/harmont-dev/harmont-py
cd harmont-py
pip install -e .For development (tests, mypy, ruff):
pip install -e '.[dev]'Requires Python 3.11+.
A pipeline file lives at .harmont/<slug>.py in your repo. The simplest one:
import harmont as hm
@hm.pipeline("hello")
def hello() -> hm.Step:
return (
hm.scratch()
.run("echo 'hello from harmont'", label="hello")
.run("uname -a", label="env")
)The DSL primitives:
| Primitive | Returns | What it does |
|---|---|---|
hm.scratch() |
Step |
Start a chain in a fresh container. |
Step.run(cmd, label=...) |
Step |
Run a shell command. Chained .run calls share container state. |
Step.fork(label=...) |
Step |
Branch from a shared base into parallel work. |
hm.wait() |
Step |
Explicit synchronization barrier. |
@hm.pipeline("slug") |
decorator | Register a pipeline. Multiple per file are fine. |
hm.pipeline(*leaves, env=...) |
dict |
Factory form — build the v0 IR dict directly (used in tests). |
Cache policies (hm.ttl, hm.on_change, hm.forever, hm.compose), triggers (hm.push, hm.pull_request, hm.schedule), and matrix axes are documented in the module docstrings; see harmont/__init__.py.
A two-branch example:
@hm.pipeline("ci")
def ci() -> hm.Step:
setup = hm.scratch().run(
"apt-get update && apt-get install -y curl",
label="apt",
)
fetch = setup.fork(label="branch-a").run(
"curl -fsSL https://example.com",
label="fetch",
)
work = setup.fork(label="branch-b").run(
"echo independent work",
label="other",
)
return hm.pipeline(fetch, work, default_image="ubuntu:24.04")Once the file is in place, run the pipeline with the Harmont CLI:
hm run hello --localhm.scratch().run(...).run(...) builds a chain of frozen Step dataclasses. Each .run() returns a new Step carrying the parent reference. The hm.pipeline() factory walks back from each leaf, topo-sorts, and emits a version: "0" IR dict matching the schema in harmont-pipeline (Haskell side).
When used as a decorator, @hm.pipeline("slug") registers the wrapped function with a module-level registry. hm.dump_registry_json() walks every .harmont/*.py, imports each (which triggers the decorators), and returns the full envelope.
The JSON wire format and cache-key algorithm are stable; see the module docstrings under harmont/ for the contract.
python3 -m venv .venv && source .venv/bin/activate
pip install -e '.[dev]'
pytest # all tests
pytest -v --tb=short
mypy --strict harmont
ruff check .pytest is configured to treat warnings as errors (filterwarnings = ["error"]).
harmont-cli— the CLI that consumes the JSON this package emits and runs pipelines locally with Docker (hm run --local).
MIT. See LICENSE.