Workflow Architecture
GitHub Actions workflows for CI/CD automation in pyrig projects.
Overview
Pyrig provides a declarative API for building GitHub Actions workflows through
the Workflow base class. All workflow config files inherit from this class and
generate YAML files in .github/workflows/.
Inheritance Hierarchy
Workflow Base Class
The Workflow class provides:
Core Structure
- Jobs: Define workflow jobs with dependencies, strategies, and steps
- Steps: Individual actions or shell commands within jobs
- Triggers: Events that start the workflow (push, PR, schedule, workflow_run)
- Permissions: GitHub token permissions for the workflow
- Matrix Strategies: Run jobs across OS and Python version combinations
Workflow Lifecycle
Declarative API
Instead of writing YAML manually, you define workflows in Python:
class MyWorkflow(Workflow):
@classmethod
def get_jobs(cls) -> dict[str, Any]:
return cls.get_job(
job_func=cls.my_job,
runs_on=cls.UBUNTU_LATEST,
steps=[
cls.step_checkout_repository(),
cls.step_setup_package_manager(python_version="3.12"),
cls.step_run_tests(),
]
)
Naming Conventions
- Workflow name: Generated from class name (e.g.,
HealthCheckWorkflow→"Health Check") - Job IDs: Generated from method names (e.g.,
job_health_check_matrix→"health_check_matrix") - Step IDs: Generated from method names (e.g.,
step_run_tests→"run_tests")
Opt-Out Mechanism
Workflows can be opted out by replacing all steps with
step_opt_out_of_workflow(). This creates a valid workflow that never runs,
allowing users to disable workflows without deleting files. Or if you empty the
file it will be regenerated on next uv run pyrig mkroot with the opt-out steps
for you. So just empty the file and run uv run pyrig mkroot to opt out.
Concrete Workflows
Pyrig provides four workflows that form a complete CI/CD pipeline:
1. Health Check Workflow
File: .github/workflows/health_check.yml
Triggers:
- Pull requests
- Pushes to main
- Scheduled (daily, staggered by dependency depth)
Jobs:
- health_checks: Runs quality checks and applies branch protection rules
- Pre-commit checks (ruff lint/format, ty, bandit, rumdl)
- Dependency security audit (pip-audit)
- matrix_health_checks: Runs across OS (Ubuntu, Windows, macOS) and Python versions
- Tests with coverage (pytest)
- Coverage upload (codecov)
- health_check: Aggregates health_checks and matrix_health_checks results (required status check for PRs)
Purpose: Continuous integration - ensures code quality on every change.
2. Build Workflow
File: .github/workflows/build.yml
Triggers:
- After health check completes successfully on main
- Excludes cron-triggered health checks (only push/dispatch triggers build)
Jobs:
- build_artifacts: Builds project artifacts across OS matrix
- build_container_image: Builds container image (Ubuntu only)
Purpose: Creates distributable artifacts after CI passes.
3. Release Workflow
File: .github/workflows/release.yml
Triggers:
- After build workflow completes successfully
Jobs:
- release: Creates GitHub release
- Bumps patch version
- Commits and pushes changes
- Creates and pushes git tag
- Downloads artifacts from build workflow
- Generates changelog
- Creates GitHub release with artifacts
Permissions: contents: write, actions: read
Purpose: Automates versioning and GitHub releases.
4. Deploy Workflow
File: .github/workflows/deploy.yml
Triggers:
- After release workflow completes successfully
Jobs:
- publish_package: Publishes to PyPI
- Builds wheel
- Publishes with PYPI_TOKEN (if configured)
- deploy_documentation: Deploys to GitHub Pages
- Builds MkDocs site
- Uploads and deploys to Pages
Permissions: pages: write, id-token: write (for docs job)
Purpose: Distributes package and documentation.
Creating Custom Workflows
To create your own workflow, subclass Workflow and implement get_jobs():
# myapp/dev/configs/workflows/custom.py
from typing import Any
from pyrig.dev.configs.base.workflow import Workflow
class CustomWorkflow(Workflow):
"""Custom workflow that runs on manual trigger."""
@classmethod
def get_workflow_triggers(cls) -> dict[str, Any]:
"""Trigger manually via workflow_dispatch."""
triggers = super().get_workflow_triggers()
triggers.update(cls.on_workflow_dispatch())
return triggers
@classmethod
def get_jobs(cls) -> dict[str, Any]:
"""Define the workflow jobs."""
jobs: dict[str, Any] = {}
jobs.update(cls.job_custom_task())
return jobs
@classmethod
def job_custom_task(cls) -> dict[str, Any]:
"""Custom job that runs a script."""
return cls.get_job(
job_func=cls.job_custom_task,
runs_on=cls.UBUNTU_LATEST,
steps=[
cls.step_checkout_repository(),
cls.step_setup_version_control(),
cls.step_setup_package_manager(python_version="3.12"),
cls.step_install_dependencies(),
{
"name": "Run custom script",
"run": "uv run python scripts/custom_task.py",
},
],
)
After creating the file, run uv run pyrig mkroot to generate
.github/workflows/custom.yml.
Best Practices
- Don't edit YAML directly: Modify the Python workflow classes instead by subclassing them as you can with all ConfigFiles
- Use opt-out for customization: Replace steps with
step_opt_out_of_workflow()to disable - Configure secrets: Add REPO_TOKEN, PYPI_TOKEN, CODECOV_TOKEN to repository secrets (see Getting Started)
- Test locally: Run
uv run pyrig mkrootto regenerate workflows after changes