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.
Job template functions
Section titled “Job template functions”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 ."),}import { job, step } from "@ghagen/ghagen";import type { JobModel } from "@ghagen/ghagen";
function lintJob(tool: string, cmd: string): JobModel {return job({name: `Lint (${tool})`,runsOn: "ubuntu-latest",steps: [step({ uses: "actions/checkout@v4" }),step({ name: tool, run: cmd }),],});}
const jobs = {ruff: lintJob("Ruff", "ruff check ."),mypy: lintJob("Mypy", "mypy src/"),black: lintJob("Black", "black --check ."),};You can parameterize anything — runner labels, Python versions, dependency install commands, or entire step lists.
Shared constants
Section titled “Shared constants”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}), ],)import { job, step } from "@ghagen/ghagen";
const RUNNER = "ubuntu-latest";const PYTHON_VERSION = "3.12";const CHECKOUT_ACTION = "actions/checkout@v4";const SETUP_PYTHON_ACTION = "actions/setup-python@v5";
job({runsOn: 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]import { job, step } from "@ghagen/ghagen";
const services = ["api", "web", "worker"];
const jobs = Object.fromEntries(services.map((svc) => [`build-${svc}`,job({name: `Build ${svc}`,runsOn: "ubuntu-latest",steps: [step({ uses: "actions/checkout@v4" }),step({ name: "Build", run: `docker build -t ${svc} ./services/${svc}` }),],}),]));