Skip to content

Merge pull request #18 from link-foundation/issue-17-ace5a094a724 #17

Merge pull request #18 from link-foundation/issue-17-ace5a094a724

Merge pull request #18 from link-foundation/issue-17-ace5a094a724 #17

Workflow file for this run

name: Python CI/CD
on:
push:
branches:
- main
paths:
- 'python/**'
- '.github/workflows/python.yml'
pull_request:
types: [opened, synchronize, reopened]
paths:
- 'python/**'
- '.github/workflows/python.yml'
workflow_dispatch:
inputs:
bump_type:
description: 'Version bump type'
required: true
type: choice
options:
- patch
- minor
- major
description:
description: 'Release description (optional)'
required: false
type: string
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
# REQUIRED CI CHECKS - All must pass before release
# These jobs ensure code quality and tests pass before any release
# Linting and formatting
lint:
name: Lint and Format Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.13'
- name: Install dependencies
working-directory: ./python
run: |
python -m pip install --upgrade pip
pip install -e ".[dev]"
- name: Run Ruff linting
working-directory: ./python
run: ruff check src tests
- name: Check Ruff formatting
working-directory: ./python
run: ruff format --check src tests
- name: Run mypy
working-directory: ./python
run: mypy src
- name: Check file size limit
working-directory: ./python
run: python scripts/check_file_size.py
# Test on latest Python version only
test:
name: Test (Python 3.13)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.13'
- name: Install dependencies
working-directory: ./python
run: |
python -m pip install --upgrade pip
pip install -e ".[dev]"
- name: Run tests
working-directory: ./python
run: pytest tests/ -v --cov=src --cov-report=xml --cov-report=term
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
file: ./python/coverage.xml
fail_ci_if_error: false
# Build package - only runs if lint and test pass
build:
name: Build Package
runs-on: ubuntu-latest
needs: [lint, test]
steps:
- uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.13'
- name: Install build dependencies
run: |
python -m pip install --upgrade pip
pip install build twine
- name: Build package
working-directory: ./python
run: python -m build
- name: Check package
working-directory: ./python
run: twine check dist/*
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: dist
path: python/dist/
# Check for changelog fragments in PRs (similar to changesets check)
changelog:
name: Changelog Fragment Check
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.13"
- name: Install scriv
run: pip install "scriv[toml]"
- name: Check for changelog fragments
working-directory: ./python
run: |
# Get list of fragment files (excluding README and template)
FRAGMENTS=$(find changelog.d -name "*.md" ! -name "README.md" ! -name "*.j2" 2>/dev/null | wc -l)
# Get changed files in PR
CHANGED_FILES=$(git diff --name-only origin/${{ github.base_ref }}...HEAD)
# Check if any source files changed (excluding docs and config)
SOURCE_CHANGED=$(echo "$CHANGED_FILES" | grep -E "^python/(src/|tests/|scripts/)" | wc -l)
if [ "$SOURCE_CHANGED" -gt 0 ] && [ "$FRAGMENTS" -eq 0 ]; then
echo "::warning::No changelog fragment found. Please run 'scriv create' and document your changes."
echo ""
echo "To create a changelog fragment:"
echo " cd python"
echo " pip install 'scriv[toml]'"
echo " scriv create"
echo ""
echo "This is similar to adding a changeset in JavaScript projects."
echo "See python/changelog.d/README.md for more information."
# Note: This is a warning, not a failure, to allow flexibility
# Change 'exit 0' to 'exit 1' to make it required
exit 0
fi
echo "✓ Changelog check passed"
# RELEASE JOBS - Only run after all CI checks pass
# Automatic release on push to main (if version changed)
auto-release:
name: Auto Release
needs: [lint, test, build]
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.13'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install build twine
- name: Check if version changed
id: version_check
working-directory: ./python
run: |
# Get current version from pyproject.toml
CURRENT_VERSION=$(grep -Po '(?<=^version = ")[^"]*' pyproject.toml)
echo "current_version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
# Check if tag exists
if git rev-parse "python-v$CURRENT_VERSION" >/dev/null 2>&1; then
echo "Tag python-v$CURRENT_VERSION already exists, skipping release"
echo "should_release=false" >> $GITHUB_OUTPUT
else
echo "New version detected: $CURRENT_VERSION"
echo "should_release=true" >> $GITHUB_OUTPUT
fi
- name: Download artifacts
if: steps.version_check.outputs.should_release == 'true'
uses: actions/download-artifact@v4
with:
name: dist
path: python/dist/
- name: Publish to PyPI
if: steps.version_check.outputs.should_release == 'true'
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: python/dist/
- name: Create GitHub Release
if: steps.version_check.outputs.should_release == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
working-directory: ./python
run: |
python scripts/create_github_release.py \
--version "${{ steps.version_check.outputs.current_version }}" \
--repository "${{ github.repository }}" \
--tag-prefix "python-v"
# Manual release via workflow_dispatch - only after CI passes
manual-release:
name: Manual Release
needs: [lint, test, build]
if: github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.13'
- name: Install dependencies
working-directory: ./python
run: |
python -m pip install --upgrade pip
pip install build twine "scriv[toml]"
- name: Configure git
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Collect changelog fragments
working-directory: ./python
run: |
# Check if there are any fragments to collect
FRAGMENTS=$(find changelog.d -name "*.md" ! -name "README.md" ! -name "*.j2" 2>/dev/null | wc -l)
if [ "$FRAGMENTS" -gt 0 ]; then
echo "Found $FRAGMENTS changelog fragment(s), collecting..."
scriv collect --version "${{ github.event.inputs.bump_type }}"
else
echo "No changelog fragments found, skipping collection"
fi
- name: Version and commit
id: version
working-directory: ./python
run: |
python scripts/version_and_commit.py \
--bump-type "${{ github.event.inputs.bump_type }}" \
--description "${{ github.event.inputs.description }}"
- name: Build package
if: steps.version.outputs.version_committed == 'true' || steps.version.outputs.already_released == 'true'
working-directory: ./python
run: python -m build
- name: Check package
if: steps.version.outputs.version_committed == 'true' || steps.version.outputs.already_released == 'true'
working-directory: ./python
run: twine check dist/*
- name: Publish to PyPI
if: steps.version.outputs.version_committed == 'true' || steps.version.outputs.already_released == 'true'
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: python/dist/
- name: Create GitHub Release
if: steps.version.outputs.version_committed == 'true' || steps.version.outputs.already_released == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
working-directory: ./python
run: |
python scripts/create_github_release.py \
--version "${{ steps.version.outputs.new_version }}" \
--repository "${{ github.repository }}" \
--tag-prefix "python-v"