Skip to content

DRY Patterns

ghagen definitions are plain code, so standard techniques for reducing duplication work naturally. This guide covers common patterns for keeping your workflow code concise and maintainable.

Define a function that returns a job to create reusable job templates:

from ghagen import Job, Step, checkout
def lint_job(tool: str, cmd: str) -> Job:
return Job(
name=f"Lint ({tool})",
runs_on="ubuntu-latest",
steps=[
checkout(),
Step(name=tool, run=cmd),
],
)

Use it to stamp out multiple jobs without repeating the structure:

jobs = {
"ruff": lint_job("Ruff", "ruff check ."),
"mypy": lint_job("Mypy", "mypy src/"),
"black": lint_job("Black", "black --check ."),
}

You can parameterize anything — runner labels, Python versions, dependency install commands, or entire step lists.

Extract repeated values into variables to ensure consistency and simplify updates:

RUNNER = "ubuntu-latest"
PYTHON_VERSION = "3.12"
CHECKOUT_ACTION = "actions/checkout@v4"
SETUP_PYTHON_ACTION = "actions/setup-python@v5"
Job(
runs_on=RUNNER,
steps=[
Step(uses=CHECKOUT_ACTION),
Step(uses=SETUP_PYTHON_ACTION, with_={"python-version": PYTHON_VERSION}),
],
)

When a new action version is released, update the constant in one place.

Use iteration to generate repetitive structures:

services = ["api", "web", "worker"]
jobs = {
f"build-{svc}": Job(
name=f"Build {svc}",
runs_on="ubuntu-latest",
steps=[
checkout(),
Step(name="Build", run=f"docker build -t {svc} ./services/{svc}"),
],
)
for svc in services
}

This works equally well for generating step lists:

linters = [("ruff", "ruff check ."), ("mypy", "mypy src/")]
steps = [checkout()] + [
Step(name=name, run=cmd) for name, cmd in linters
]