From python-engineering
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.
npx claudepluginhub jamie-bitflight/claude_skills --plugin python-engineeringThis skill uses the workspace's default tool permissions.
<pipeline_target>$ARGUMENTS</pipeline_target>
Generates design tokens/docs from CSS/Tailwind/styled-components codebases, audits visual consistency across 10 dimensions, detects AI slop in UI.
Records polished WebM UI demo videos of web apps using Playwright with cursor overlay, natural pacing, and three-phase scripting. Activates for demo, walkthrough, screen recording, or tutorial requests.
Delivers idiomatic Kotlin patterns for null safety, immutability, sealed classes, coroutines, Flows, extensions, DSL builders, and Gradle DSL. Use when writing, reviewing, refactoring, or designing Kotlin code.
<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-core/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.