Merge pull request #18 from link-foundation/issue-17-ace5a094a724 #17
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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" |