Skip to content

harmont-dev/harmont-py

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

64 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

harmont-py

license

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 hm

Install

harmont 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+.

Quickstart

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 --local

How it works

hm.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.

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 consumes the JSON this package emits and runs pipelines locally with Docker (hm run --local).

License

MIT. See LICENSE.

About

Python DSL for Harmont CI pipelines

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages