github made easy
Project description
gheasy
Install
pip install gheasy
Quickstart
One call generates a complete Python CI workflow:
from gheasy.workflow import uv_ci
print(uv_ci("ci", lint_cmd=None).to_yaml())
name: ci
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup uv
uses: astral-sh/setup-uv@v5
- name: Install dependencies
run: uv sync --frozen
- name: Test
run: uv run pytest
Library CI
gheasy, dockeasy, and every *easy package uses this pattern. Lint on
push → test (needs lint) → publish to PyPI (needs test):
from gheasy.workflow import Workflow
wfb = Workflow("ci")
wfb.on.push(branches=["main"]).pull_request()
wfb.uv_lint_job()
wfb.uv_test_job(needs="lint")
wfb.uv_pypi_job(needs="test")
print(wfb.build().to_yaml())
name: ci
on:
push:
branches:
- main
pull_request:
jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup uv
uses: astral-sh/setup-uv@v5
- name: Install dependencies
run: uv sync --frozen
- name: Lint
run: uv run ruff check . && uv run ruff format --check .
test:
needs: lint
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup uv
uses: astral-sh/setup-uv@v5
- name: Install dependencies
run: uv sync --frozen
- name: Test
run: uv run pytest
publish:
needs: test
runs-on: ubuntu-latest
if: github.event_name == 'release'
permissions:
id-token: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup uv
uses: astral-sh/setup-uv@v5
- name: Build
run: uv build
- name: Publish
uses: pypa/gh-action-pypi-publish@release/v1
App Pipeline
For web apps (FastHTML, Django, etc.) — test on every push, deploy to Fly.io on main. Uses the DSL directly for custom step logic:
wfb = Workflow("deploy")
wfb.on.push(branches=["main"])
wfb.uv_test_job()
wfb.job("deploy").needs("test").runs_on("ubuntu-latest")\
.checkout().end_step()\
.setup_uv().end_step()\
.step("Deploy to Fly.io").run("fly deploy --remote-only").end_job()
print(wfb.build().to_yaml())
name: deploy
on:
push:
branches:
- main
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup uv
uses: astral-sh/setup-uv@v5
- name: Install dependencies
run: uv sync --frozen
- name: Test
run: uv run pytest
deploy:
needs: test
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup uv
uses: astral-sh/setup-uv@v5
- name: Deploy to Fly.io
run: fly deploy --remote-only
Project setup: LFS & secrets
Not every project needs CI. Sometimes you just need git-lfs tracking and
GitHub secrets synced from your local .env. For an app like
vedicreader with media files and OAuth
secrets:
from gheasy.core import gh_lfs, gh_secrets_from_file, gh_push_env, GheasyConfig
# 1. Track binary/media files in git-lfs
gh_lfs(['*.mp3', '*.ogg', '*.wav', '*.png', '*.jpg', '*.webp', '*.xml', '*.db'])
# 2. Push .env values to GitHub — None-default keys become secrets, string-default become variables
cfg = GheasyConfig(app='vedicreader', env_schema={
# variables (have sane defaults)
'MODE': 'dev', 'PORT': '5001', 'DOMAIN': 'http://localhost:5001',
'WANT_GOOGLE': 'true', 'WANT_GIT': 'false',
'NEED_BACKUP': 'false', 'RC_TYPE': 's3', 'RC_PROVIDER': 'Cloudflare',
# secrets (no default — must be set)
'JWT_SCRT': None, 'RESEND_API_KEY': None,
'GOOGLE_CLI': None, 'GOOGLE_SCRT': None,
'CF_ACCESS_KEY_ID': None, 'CF_SCRT_ACCESS_KEY': None, 'CF_ENDPOINT': None,
})
cfg.save()
# reads local .env, routes each key: None-schema → gh secret set, string-schema → gh variable set
import os
gh_push_env(dict(os.environ))
Or push everything from as secrets (no schema needed):
# Push all KEY=VALUE pairs from .env as GitHub secrets
gh_secrets_from_file('.env')
# With dry-run to preview what would be pushed
gh_secrets_from_file('.env', dry_run=True)
Cross-package pipeline
gheasy generates workflows that call your own Python code. Here: a workflow that uses dockeasy to generate a Dockerfile, then builds and pushes to GHCR — triggered only when the relevant source files change:
wfb = Workflow("Build caddy-sqlite image")
wfb.on.push(branches=["main"], paths=["dockeasy/proxy.py", "nbs/01_proxy.ipynb"])
wfb.on.workflow_dispatch()
wfb.job("build-and-push").runs_on("ubuntu-latest")\
.permissions(contents="read", packages="write")\
.checkout().end_step()\
.setup_uv().end_step()\
.step("Install deps").run("uv sync").end_step()\
.step("Generate Dockerfile").run(
'uv run python -c "\n'
'from dockeasy.proxy import caddy_sqlite_dockerfile\n'
'caddy_sqlite_dockerfile().save(\'Dockerfile\')\n'
'"'
).end_step()\
.step("Log in to GHCR").uses("docker/login-action@v3")\
.with_(registry="ghcr.io",
username="${{ github.actor }}",
password="${{ secrets.GITHUB_TOKEN }}").end_step()\
.step("Build and push").uses("docker/build-push-action@v5")\
.with_(context=".", push=True,
tags="ghcr.io/vedicreader/caddy-sqlite:latest").end_job()
print(wfb.build().to_yaml())
name: Build caddy-sqlite image
on:
push:
branches:
- main
paths:
- dockeasy/proxy.py
- nbs/01_proxy.ipynb
workflow_dispatch:
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup uv
uses: astral-sh/setup-uv@v5
- name: Install deps
run: uv sync
- name: Generate Dockerfile
run: "uv run python -c \"\nfrom dockeasy.proxy import caddy_sqlite_dockerfile\ncaddy_sqlite_dockerfile().save('Dockerfile')\n\
\""
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ghcr.io/vedicreader/caddy-sqlite:latest
Project details
Release history Release notifications | RSS feed
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 gheasy-0.0.1.tar.gz.
File metadata
- Download URL: gheasy-0.0.1.tar.gz
- Upload date:
- Size: 26.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9e9f6b01ca82417a34f4350e18198110ec4920db8cb87781dd5b8b285043a88c
|
|
| MD5 |
58cade3737b166bd96c8b43e736a0059
|
|
| BLAKE2b-256 |
3faf5d72b15671ef8c1382093c1ff51a15adfa637c19dbaa1608e0af2f106fc1
|
File details
Details for the file gheasy-0.0.1-py3-none-any.whl.
File metadata
- Download URL: gheasy-0.0.1-py3-none-any.whl
- Upload date:
- Size: 27.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
681e0a4b69089f81f617bbd333166f759fcfd40210b26fe278288ccb79932122
|
|
| MD5 |
f82c8671ea5267858e6d4ed45cfeb029
|
|
| BLAKE2b-256 |
7ff581c4dd6a7aa6c6d7ffc25e94cf4296305aff852c7d0ed9cadb8f10239dca
|