Skip to content

Publish to PyPI

Publish to PyPI #59

Workflow file for this run

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