Publish to PyPI #72
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: Publish to PyPI | |
| on: | |
| release: | |
| types: [published] | |
| workflow_dispatch: # Enable manual trigger. | |
| inputs: | |
| version: | |
| description: 'Package version to publish (e.g., 4.0.1). Required for manual triggers.' | |
| required: true | |
| type: string | |
| jobs: | |
| build-and-publish: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| id-token: write # Mandatory for OIDC. | |
| contents: read | |
| steps: | |
| - name: Checkout (official GitHub action) | |
| uses: actions/checkout@v4 | |
| with: | |
| # Important for versioning plugins: | |
| fetch-depth: 0 | |
| - name: Install uv (official Astral action) | |
| uses: astral-sh/setup-uv@v5 | |
| with: | |
| version: "0.9.5" | |
| enable-cache: true | |
| python-version: "3.12" | |
| - name: Set up Python (using uv) | |
| run: uv python install | |
| - name: Install all dependencies | |
| run: uv sync --all-extras | |
| - name: Run tests | |
| run: uv run pytest | |
| - name: Verify pyproject.toml is correct | |
| run: | | |
| # Verify pyproject.toml has the correct package name | |
| if ! grep -q 'name = "python-package-folder"' pyproject.toml; then | |
| echo "Error: pyproject.toml does not have correct package name 'python-package-folder'" | |
| echo "Current content:" | |
| grep '^name =' pyproject.toml || echo "No name field found" | |
| exit 1 | |
| fi | |
| # Verify packages points to the correct location | |
| if ! grep -q 'packages = \["src/python_package_folder"\]' pyproject.toml; then | |
| echo "Error: pyproject.toml does not have correct packages configuration" | |
| echo "Current packages:" | |
| grep -A 1 '\[tool.hatch.build.targets.wheel\]' pyproject.toml || echo "No packages found" | |
| exit 1 | |
| fi | |
| echo "✓ pyproject.toml is correct" | |
| - name: Clean dist directory | |
| run: | | |
| # Remove any existing distribution files to prevent publishing test artifacts | |
| rm -rf dist/ | |
| echo "✓ Cleaned dist/ directory" | |
| - name: Extract version from release tag or input | |
| id: version | |
| run: | | |
| if [ "${{ github.event_name }}" = "release" ]; then | |
| # Extract from release tag | |
| VERSION="${{ github.event.release.tag_name }}" | |
| # Strip 'v' prefix if present | |
| VERSION="${VERSION#v}" | |
| echo "version=$VERSION" >> $GITHUB_OUTPUT | |
| echo "Extracted version from release tag: $VERSION" | |
| else | |
| # For workflow_dispatch, use the provided input | |
| VERSION="${{ github.event.inputs.version }}" | |
| if [ -z "$VERSION" ]; then | |
| echo "Error: Version is required for manual workflow_dispatch triggers" | |
| exit 1 | |
| fi | |
| # Strip 'v' prefix if present (user might include it) | |
| VERSION="${VERSION#v}" | |
| echo "version=$VERSION" >> $GITHUB_OUTPUT | |
| echo "Using version from workflow input: $VERSION" | |
| fi | |
| - name: Set version for release | |
| if: steps.version.outputs.version != '' | |
| run: | | |
| python -c " | |
| from pathlib import Path | |
| from python_package_folder.version import VersionManager | |
| version_manager = VersionManager(project_root=Path('.')) | |
| version_manager.set_version('${{ steps.version.outputs.version }}') | |
| print(f'✓ Set version to ${{ steps.version.outputs.version }}') | |
| " | |
| - name: Build package | |
| run: uv build | |
| - name: Verify distribution files | |
| run: | | |
| # List all distribution files that will be published | |
| echo "Distribution files to be published:" | |
| ls -la dist/ || (echo "No dist/ directory found" && exit 1) | |
| # Check for unexpected files (exclude .gitignore and expected package files) | |
| UNEXPECTED_FILES=0 | |
| for file in dist/*.whl dist/*.tar.gz; do | |
| if [ -f "$file" ]; then | |
| filename=$(basename "$file") | |
| # Check if file starts with python_package_folder (package directory name) | |
| if [[ ! "$filename" =~ ^python_package_folder- ]]; then | |
| echo "Error: Found unexpected distribution file: $filename" | |
| UNEXPECTED_FILES=$((UNEXPECTED_FILES + 1)) | |
| fi | |
| fi | |
| done | |
| if [ $UNEXPECTED_FILES -gt 0 ]; then | |
| echo "Error: Found $UNEXPECTED_FILES unexpected distribution file(s)" | |
| echo "Files in dist/:" | |
| ls -la dist/ | |
| exit 1 | |
| fi | |
| # Verify at least one expected file exists | |
| if ! ls dist/python_package_folder-*.whl dist/python_package_folder-*.tar.gz 2>/dev/null | grep -q .; then | |
| echo "Error: No python_package_folder distribution files found" | |
| echo "Files in dist/:" | |
| ls -la dist/ | |
| exit 1 | |
| fi | |
| echo "✓ Only python_package_folder distribution files found" | |
| - name: Publish to PyPI | |
| run: uv publish --trusted-publishing always | |
| # Although uv is newer and faster, the "official" publishing option is the one from PyPA, | |
| # which uses twine. If desired, replace `uv publish` with: | |
| # uses: pypa/gh-action-pypi-publish@release/v1 |