From python3-development
Sets up GitHub Actions or GitLab CI pipelines for Python packages, including ruff linting, ty type checking, pytest testing with coverage, and automated PyPI publishing on git tags.
npx claudepluginhub jamie-bitflight/claude_skills --plugin python3-developmentThis skill uses the workspace's default tool permissions.
<pipeline_target>$ARGUMENTS</pipeline_target>
Sets up CI/CD pipelines for publishing Python packages to PyPI using GitHub Actions or GitLab CI. Includes testing, linting with ruff/ty/pytest, and automated releases on tags.
Builds, tests, lints, versions, and publishes production Python packages to PyPI using setuptools, hatchling, flit, poetry backends, mypy, ruff, pre-commit, and GitHub Actions CI/CD.
Provides GitHub Actions templates for Python CI/CD with uv caching, matrix testing across Python versions, PyPI publishing, code coverage, and security scanning.
Share bugs, ideas, or general feedback.
<pipeline_target>$ARGUMENTS</pipeline_target>
The model configures CI/CD pipelines for automated Python package publishing.
<pipeline_target/>
If no argument provided, detect from repository (look for .github/ or .gitlab-ci.yml).
Consult ../python3-development/references/python3-standards.md when applying shared architecture, typing, testing, or CLI rules; full standards, graphs, and amendment process are documented there.
.github/
├── workflows/
│ ├── ci.yml # Run on every push/PR
│ ├── release.yml # Run on version tags
│ └── docs.yml # Optional: documentation
└── dependabot.yml # Optional: dependency updates
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v4
with:
enable-cache: true
- name: Set up Python
run: uv python install 3.11
- name: Install dependencies
run: uv sync --all-extras
- name: Run ruff
run: |
uv run ruff check src/ tests/
uv run ruff format --check src/ tests/
- name: Run type check (ty default; swap to mypy if hooks/CI run mypy — not merely [tool.mypy])
run: uv run ty check src/ tests/
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.11", "3.12", "3.13"]
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v4
with:
enable-cache: true
- name: Set up Python ${{ matrix.python-version }}
run: uv python install ${{ matrix.python-version }}
- name: Install dependencies
run: uv sync --all-extras
- name: Run tests
run: uv run pytest tests/ -v --cov=src --cov-report=xml
- name: Upload coverage
uses: codecov/codecov-action@v4
with:
files: coverage.xml
fail_ci_if_error: false
name: Release
on:
push:
tags:
- "v*.*.*"
permissions:
contents: write
id-token: write # Required for trusted publishing
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v4
- name: Set up Python
run: uv python install 3.11
- name: Build package
run: uv build
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
# Option 1: Trusted Publishing (Recommended)
publish-pypi:
needs: build
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/p/your-package
steps:
- name: Download artifacts
uses: actions/download-artifact@v4
with:
name: dist
path: dist/
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
# No token needed with trusted publishing!
# Option 2: Token-based Publishing (Alternative)
# publish-pypi:
# needs: build
# runs-on: ubuntu-latest
# steps:
# - name: Download artifacts
# uses: actions/download-artifact@v4
# with:
# name: dist
# path: dist/
#
# - name: Publish to PyPI
# uses: pypa/gh-action-pypi-publish@release/v1
# with:
# password: ${{ secrets.PYPI_API_TOKEN }}
github-release:
needs: [build, publish-pypi]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Download artifacts
uses: actions/download-artifact@v4
with:
name: dist
path: dist/
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
files: dist/*
generate_release_notes: true
stages:
- lint
- test
- build
- publish
variables:
UV_CACHE_DIR: "$CI_PROJECT_DIR/.uv-cache"
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.pip-cache"
default:
image: python:3.11-slim
before_script:
- pip install uv
- uv sync --all-extras
cache:
key: "${CI_JOB_NAME}"
paths:
- .uv-cache/
- .pip-cache/
- .venv/
lint:
stage: lint
script:
- uv run ruff check src/ tests/
- uv run ruff format --check src/ tests/
- uv run ty check src/ tests/
test:
stage: test
parallel:
matrix:
- PYTHON_VERSION: ["3.11", "3.12", "3.13"]
image: python:${PYTHON_VERSION}-slim
script:
- uv run pytest tests/ -v --cov=src --cov-report=xml --junitxml=report.xml
coverage: '/TOTAL.*\s+(\d+%)/'
artifacts:
reports:
junit: report.xml
coverage_report:
coverage_format: cobertura
path: coverage.xml
build:
stage: build
script:
- uv build
artifacts:
paths:
- dist/
expire_in: 1 week
# Publish to PyPI on tags
publish:pypi:
stage: publish
rules:
- if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/
script:
- uv publish --token $PYPI_TOKEN
environment:
name: pypi
url: https://pypi.org/project/your-package
# Publish to GitLab Package Registry on tags
publish:gitlab:
stage: publish
rules:
- if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/
script:
- |
TWINE_PASSWORD=${CI_JOB_TOKEN} \
TWINE_USERNAME=gitlab-ci-token \
uv publish --publish-url ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/pypi
environment:
name: gitlab-registry
Go to PyPI → Your Project → Publishing
Add GitHub Publisher:
your-usernameyour-reporelease.ymlpypi (optional but recommended)Create GitHub Environment:
pypiIf trusted publishing isn't available:
Create PyPI Token:
Add to Repository Secrets:
PYPI_API_TOKENPYPI_TOKENUpdate version in pyproject.toml before tagging:
[project]
version = "1.2.3"
Using hatch-vcs:
[build-system]
requires = ["hatchling", "hatch-vcs"]
build-backend = "hatchling.build"
[project]
dynamic = ["version"]
[tool.hatch.version]
source = "vcs"
[tool.hatch.build.hooks.vcs]
version-file = "src/my_package/_version.py"
# 1. Update CHANGELOG.md
# 2. Commit changes
git add -A
git commit -m "Prepare release v1.2.3"
# 3. Create annotated tag
git tag -a v1.2.3 -m "Release v1.2.3"
# 4. Push with tags
git push origin main --tags
publish-testpypi:
needs: build
runs-on: ubuntu-latest
environment:
name: testpypi
url: https://test.pypi.org/p/your-package
steps:
- uses: actions/download-artifact@v4
with:
name: dist
path: dist/
- uses: pypa/gh-action-pypi-publish@release/v1
with:
repository-url: https://test.pypi.org/legacy/
# Build
uv build
# Upload to TestPyPI
uv publish --publish-url https://test.pypi.org/legacy/ --token $TESTPYPI_TOKEN
# Test install
uv pip install --index-url https://test.pypi.org/simple/ your-package
your-package/
├── pyproject.toml # Package metadata and build config
├── README.md # Required by PyPI
├── LICENSE # Required for distribution
├── src/
│ └── your_package/
│ ├── __init__.py
│ └── py.typed # PEP 561 marker
└── .github/
└── workflows/
├── ci.yml
└── release.yml
- [ ] [build-system] with requires and build-backend
- [ ] [project] with name, version, description
- [ ] readme = "README.md"
- [ ] license specified
- [ ] requires-python = ">=3.11"
- [ ] authors with name and email
- [ ] classifiers (Development Status, License, Python versions)
- [ ] dependencies list
- [ ] [project.urls] with Documentation, Issues, Source
- [ ] [project.scripts] if CLI tool
# Check package metadata
uv run python -m build --no-isolation
uvx twine check dist/*
# Verify token
echo $PYPI_TOKEN | head -c 10
# Check package name availability
curl https://pypi.org/pypi/your-package/json
PyPI doesn't allow re-uploading the same version. Increment version and create new tag.