Skip to content

CI with GitHub Actions

If you have set up GitHub Actions for a TypeScript project, the pattern is: checkout → setup-node → install → lint → type-check → test. Python CI follows the exact same four-step logic, with Python-specific action names and tool invocations.

TypeScript
# .github/workflows/ci.yml (Node / TypeScript)
name: CI
on:
push:
branches: [main, dev]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Lint
run: npx eslint src/ --max-warnings 0
- name: Type check
run: npx tsc --noEmit
- name: Test
run: npx jest --coverage
Python
# .github/workflows/ci.yml (Python)
name: CI
on:
push:
branches: [main, dev]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: 'pip'
- name: Install dependencies
run: pip install -r requirements.txt
- name: Lint & format check (ruff)
run: |
ruff check .
ruff format --check .
- name: Type check (mypy)
run: mypy src/ --strict
- name: Test (pytest)
run: pytest --tb=short -q

Here is a more complete workflow with caching, coverage reporting, and matrix testing across multiple Python versions:

.github/workflows/ci.yml
name: CI
on:
push:
branches: [main, dev]
pull_request:
branches: [main]
jobs:
quality:
name: Lint & Type Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: 'pip'
- name: Install dev dependencies
run: |
pip install --upgrade pip
pip install -r requirements-dev.txt
- name: Lint with ruff
run: ruff check . --output-format=github
- name: Check formatting with ruff
run: ruff format --check .
- name: Type check with mypy
run: mypy src/ --strict
test:
name: Test (Python ${{ matrix.python-version }})
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.11', '3.12']
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
- name: Install dependencies
run: |
pip install --upgrade pip
pip install -r requirements.txt
pip install -r requirements-dev.txt
- name: Run tests with coverage
run: pytest --cov=src --cov-report=xml --cov-fail-under=80 -v
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
file: ./coverage.xml

Replacing pip with uv in CI cuts install time from 30–90 seconds to 2–5 seconds on most projects:

- name: Install uv
uses: astral-sh/setup-uv@v3
with:
version: "latest"
- name: Install dependencies with uv
run: uv sync --frozen # equivalent to npm ci — exact lockfile install

Playground note: GitHub Actions YAML files define workflows that run on GitHub’s servers — they cannot be demonstrated in a browser playground. Commit the .github/workflows/ci.yml file to your repository and push to see it run.

Which GitHub Actions action sets up a specific Python version in CI?
What ruff command checks formatting without modifying files (for CI)?
What is the uv equivalent of "npm ci" — reproducible install from a lockfile?
In a pytest CI command, which flag makes the run fail if coverage drops below 80%?