Python DSL for Harmont CI pipelines — emits v0 IR JSON
Project description
harmont-py
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. The companion harmont-cli consumes that IR and runs the pipeline locally in Docker or on the hosted Harmont cloud.
The package installs as harmont and you import it as harmont:
import harmont as hm
Quick start
1. Write a pipeline
A pipeline file lives at .harmont/<slug>.py in your repo:
import harmont as hm
@hm.pipeline("hello")
def hello() -> hm.Step:
return (
hm.sh("echo 'hello from harmont'", label="hello")
.sh("uname -a", label="env")
)
2. Install
Not yet on PyPI. Install from source (Python 3.11+):
git clone https://github.com/harmont-dev/harmont-py
cd harmont-py
pip install -e .
If you arrived here from the harmont-cli Quick start, you already did this — skip to Step 3.
Development extras (pytest, mypy, ruff):
pip install -e '.[dev]'
3. Run
Use the Harmont CLI:
hm run hello
hm run walks .harmont/*.py, imports each file (triggering the decorators), renders the registered pipeline to JSON, and executes it (locally in Docker by default, or against the cloud via hm cloud run).
DSL surface
| Primitive | Returns | What it does |
|---|---|---|
hm.sh(cmd, cwd=..., label=...) |
Step |
Start a chain in one call (= hm.scratch().sh(cmd, ...)) |
hm.scratch() |
Step |
Empty root; chain with .sh(...) for an explicit start |
Step.sh(cmd, cwd=..., ...) |
Step |
Run a shell command; chained .sh shares container state |
Step.fork(label=...) |
Step |
Branch a shared base into parallel work |
hm.wait() |
Step |
Explicit synchronization barrier |
@hm.target() |
decorator | Reusable, memoized building block |
@hm.pipeline("slug") |
decorator | Register a pipeline (multiple per file are fine) |
hm.pipeline(*leaves, env=..., default_image=...) |
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; start at harmont/__init__.py.
Language toolchains
harmont ships first-class wrappers for the common toolchains. Each exposes the actions that make sense for that ecosystem (e.g. .build(), .test(), .clippy(), .fmt() for Rust; .test(), .lint(), .fmt(), .typecheck() for Python):
| Call | Project type |
|---|---|
hm.rust(path=..., version="stable") |
cargo + clippy + rustfmt |
hm.haskell(ghc="9.6.7", cabal="latest") |
cabal (call .cabal(path) to build a package) |
hm.python(path=..., uv_version="latest") |
uv-based Python project |
hm.go(path=..., version="1.23.2") |
go build/test/vet/fmt |
hm.npm(path=..., version="20") |
npm + arbitrary scripts |
hm.gradle(path=..., jdk="21", kotlin=False) |
Java or Kotlin via Gradle |
hm.cmake(path=..., lang="c"|"cpp") |
C/C++ via CMake + CTest |
hm.dotnet(path=..., channel="8.0") |
.NET via dotnet CLI |
hm.ruby(path=..., version="default") |
Bundler + Rake |
hm.ocaml(path=..., compiler="5.1.1") |
opam + Dune |
hm.zig(path=..., version="0.13.0") |
zig build/test/fmt |
hm.perl(path=...) |
cpanm + prove |
hm.composer(path=..., laravel=False) |
PHP / Laravel via Composer |
hm.elm(path=..., elm_version="0.19.1") |
Elm |
Working examples for each toolchain live in harmont-cli/examples/.
Composing with targets
For larger pipelines, factor toolchain setup into @hm.target() and let pipelines depend on them by parameter name. Target[T] and Annotated[Step, BaseImage("...")] are typed markers that unwrap cleanly under mypy and pyright.
from typing import Annotated
import harmont as hm
from harmont.haskell import HaskellPackage, HaskellToolchain
@hm.target()
def apt_base(base: Annotated[hm.Step, hm.BaseImage("ubuntu-24.04")]) -> hm.Step:
return base.sh("apt-get update").sh("apt-get install -y python3")
@hm.target()
def ghc() -> HaskellToolchain:
return hm.haskell(ghc="9.6.7")
@hm.target()
def api(ghc: hm.Target[HaskellToolchain]) -> HaskellPackage:
return ghc.cabal(path="api")
@hm.pipeline("ci")
def ci(
apt_base: hm.Target[hm.Step],
api: hm.Target[HaskellPackage],
) -> tuple[hm.Step, ...]:
return (apt_base.sh("./run-smoke"), api)
Every fixture parameter must carry a marker or default value; unmarked parameters raise at decoration time. Memoization scope is one dump_registry_json render, so two targets that depend on the same apt_base share a single step.
How rendering works
hm.sh(...).sh(...) builds a chain of frozen Step dataclasses. Each .sh() 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.
A chain edge — parent.sh(cmd, ...) — emits builds_in: "<parent key>" in the v0 IR JSON. The edge encodes synchronisation and state inheritance: the local executor reuses the parent's container; the cloud planner boots from its snapshot. A step rooted at scratch() has builds_in: null and boots from image="..." (or the pipeline's default_image) locally; the cloud planner ignores image (it always boots from the Freestyle base).
The JSON wire format and cache-key algorithm are stable; see module docstrings under harmont/ for the contract.
Build & test
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"]).
See also
harmont-cli— the CLI that runs pipelines defined with this package (hm run).
License
MIT. See LICENSE.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file harmont-0.0.1.tar.gz.
File metadata
- Download URL: harmont-0.0.1.tar.gz
- Upload date:
- Size: 63.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0dab3e1b1548e639839d850f64423df11db809f9a0f557f9cf55e75f2939cf73
|
|
| MD5 |
0dac306b39cbc092cae632027def5d8f
|
|
| BLAKE2b-256 |
e8dce0f2e7902b5ca0c0a487205454518064585176bfba0d9c46583095ea4d1e
|
Provenance
The following attestation bundles were made for harmont-0.0.1.tar.gz:
Publisher:
release.yml on harmont-dev/harmont-py
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
harmont-0.0.1.tar.gz -
Subject digest:
0dab3e1b1548e639839d850f64423df11db809f9a0f557f9cf55e75f2939cf73 - Sigstore transparency entry: 1590970446
- Sigstore integration time:
-
Permalink:
harmont-dev/harmont-py@6df386c98c81d100b4a477e9b206d77e1c2a6f12 -
Branch / Tag:
refs/tags/v0.0.1 - Owner: https://github.com/harmont-dev
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@6df386c98c81d100b4a477e9b206d77e1c2a6f12 -
Trigger Event:
push
-
Statement type:
File details
Details for the file harmont-0.0.1-py3-none-any.whl.
File metadata
- Download URL: harmont-0.0.1-py3-none-any.whl
- Upload date:
- Size: 52.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6a2dc72e8d4955051d02d8b81606775ddb7eae3383587b902d4a4ac2d60c4b97
|
|
| MD5 |
ebb53d08347047f2571580b436f16d38
|
|
| BLAKE2b-256 |
9932d5b3a43025c503f5c01ba6fe266160fe6feb899325a4e5d749cfc9d90229
|
Provenance
The following attestation bundles were made for harmont-0.0.1-py3-none-any.whl:
Publisher:
release.yml on harmont-dev/harmont-py
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
harmont-0.0.1-py3-none-any.whl -
Subject digest:
6a2dc72e8d4955051d02d8b81606775ddb7eae3383587b902d4a4ac2d60c4b97 - Sigstore transparency entry: 1590970450
- Sigstore integration time:
-
Permalink:
harmont-dev/harmont-py@6df386c98c81d100b4a477e9b206d77e1c2a6f12 -
Branch / Tag:
refs/tags/v0.0.1 - Owner: https://github.com/harmont-dev
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@6df386c98c81d100b4a477e9b206d77e1c2a6f12 -
Trigger Event:
push
-
Statement type: