Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .env

This file was deleted.

174 changes: 95 additions & 79 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -1,102 +1,118 @@
name: Tag & Publish Python SDK (PyPI)
name: Publish Python SDK

on:
workflow_dispatch:
inputs:
version_override:
description: "Specify version (leave empty for auto-increment)"
required: false
default: ""

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}
cancel-in-progress: false

env:
PYTHON_VERSION: "3.11"
UV_VERSION: "0.13.7"

jobs:
tag-and-publish:
build:
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write # for PyPI Trusted Publishing
timeout-minutes: 15
outputs:
version: ${{ steps.version.outputs.version }}

steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

# Setup Python
- uses: actions/setup-python@v4
- name: Checkout
uses: actions/checkout@v7
with:
python-version: "3.10"
persist-credentials: false

# Install Poetry
- uses: snok/install-poetry@v1
- name: Install uv
uses: astral-sh/setup-uv@v10.3.3
with:
version: latest
virtualenvs-create: false
virtualenvs-in-project: false
version: ${{ env.UV_VERSION }}
enable-cache: true
python-version: ${{ env.PYTHON_VERSION }}

- name: Get package name and determine version
id: package_info
- name: Read version
id: version
run: |
# Extract package name from pyproject.toml
PACKAGE_NAME=$(poetry version -n | awk '{print $1}')
echo "Package name: $PACKAGE_NAME"

# Check if manual version override was provided
if [[ "${{ github.event.inputs.version_override }}" != "" ]]; then
VERSION="${{ github.event.inputs.version_override }}"
echo "Using manually specified version: $VERSION"
else
# Try to get the current version from PyPI
PYPI_VERSION=$(pip index versions $PACKAGE_NAME 2>/dev/null | grep -A1 "Available versions:" | head -1 | tr -d '[:space:]' | sed 's/Availableversions://g' | sed 's/%//g')
echo "Current PyPI version: $PYPI_VERSION"

# If PyPI version is less than 1.0.0, start at 1.0.0
if [[ $(echo "$PYPI_VERSION" | awk -F. '{print $1}') -lt 1 ]]; then
VERSION="1.0.0"
echo "Starting at version 1.0.0"
else
# Increment the patch version
IFS='.' read -r major minor patch <<< "$PYPI_VERSION"
VERSION="$major.$minor.$((patch + 1))"
echo "Incrementing to version $VERSION"
fi
set -euo pipefail
VERSION="$(uv version --short)"
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
if [ -n "$(git ls-remote --tags origin "refs/tags/v${VERSION}")" ]; then
echo "Tag v${VERSION} already exists"
exit 1
fi

echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "tag=v$VERSION" >> "$GITHUB_OUTPUT"

# Update pyproject.toml version
- name: Update pyproject.toml version
run: poetry version ${{ steps.package_info.outputs.version }}
- name: Install the project
run: uv sync --locked --group dev

# Install dependencies
- name: Install dependencies
run: poetry install
- name: Run unit tests
run: uv run pytest --unit -q

# Build the package
- name: Build package
run: poetry build
run: uv build

# Create tag only after successful build
- name: Create new tag
id: create_tag
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
git tag -a ${{ steps.package_info.outputs.tag }} -m "Release ${{ steps.package_info.outputs.tag }}"
git push origin ${{ steps.package_info.outputs.tag }}
echo "tag_created=true" >> "$GITHUB_OUTPUT"
- name: Smoke test (wheel)
run: uv run --isolated --no-project --with dist/*.whl scripts/smoke_test.py

# Publish to PyPI
- name: Publish to PyPI
id: pypi_publish
uses: pypa/gh-action-pypi-publish@release/v1
- name: Smoke test (source distribution)
run: uv run --isolated --no-project --with dist/*.tar.gz scripts/smoke_test.py

- name: Upload distributions
uses: actions/upload-artifact@v7
with:
name: dist
path: dist/

publish:
needs: build
runs-on: ubuntu-latest
timeout-minutes: 15
environment:
name: pypi
url: https://pypi.org/p/perigon/
permissions:
id-token: write

steps:
- name: Install uv
uses: astral-sh/setup-uv@v10.3.3
with:
version: ${{ env.UV_VERSION }}
enable-cache: false

- name: Download distributions
uses: actions/download-artifact@v8
with:
packages-dir: dist/
verbose: true
name: dist
path: dist/

- name: Generate PEP 740 attestations
uses: astral-sh/attest-action@v0.0.7

- name: Publish to PyPI
run: uv publish --trusted-publishing always

# Remove tag if publishing fails
- name: Remove tag if publish failed
if: failure() && steps.create_tag.outputs.tag_created == 'true'
tag:
needs: [build, publish]
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: write

steps:
- name: Checkout
uses: actions/checkout@v7

- name: Create tag
env:
VERSION: ${{ needs.build.outputs.version }}
run: |
git push --delete origin ${{ steps.package_info.outputs.tag }}
git tag -d ${{ steps.package_info.outputs.tag }}
echo "Removed tag due to workflow failure"
set -euo pipefail
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
git tag -a "v${VERSION}" -m "Release v${VERSION}"
git push origin "v${VERSION}"
108 changes: 84 additions & 24 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,43 +3,103 @@ name: Python SDK Tests
on:
push:
branches: [main]
paths: ["perigon/**", "tests/**"]
pull_request:
branches: [main]
paths: ["perigon/**", "tests/**"]
workflow_dispatch:

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

env:
PYTHON_VERSION: "3.11"
UV_VERSION: "0.13.7"

jobs:
test:
lint:
runs-on: ubuntu-latest
timeout-minutes: 10

steps:
- uses: actions/checkout@v4
- name: Checkout
uses: actions/checkout@v7
with:
persist-credentials: false

- name: Set up Python
uses: actions/setup-python@v4
- name: Install uv
uses: astral-sh/setup-uv@v10.3.3
with:
python-version: "3.11"
cache: "pip"
version: ${{ env.UV_VERSION }}
enable-cache: true
python-version: ${{ env.PYTHON_VERSION }}

- name: Install the project
run: uv sync --locked --group dev

- name: Run ruff
run: uv run ruff check

- name: Check formatting
run: uv run ruff format --check

- name: Run mypy
run: uv run mypy

unit:
needs: lint
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
python-version: ["3.11", "3.12", "3.13", "3.14", "3.15"]

- name: Install Poetry
uses: snok/install-poetry@v1
steps:
- name: Checkout
uses: actions/checkout@v7
with:
persist-credentials: false

- name: Install uv
uses: astral-sh/setup-uv@v10.3.3
with:
version: ${{ env.UV_VERSION }}
enable-cache: true
python-version: ${{ matrix.python-version }}

- name: Install the project
run: uv sync --locked --group dev

- name: Run unit tests
run: uv run pytest --unit -q

integration:
needs: [lint, unit]
if: >-
github.ref == 'refs/heads/main' &&
(github.event_name == 'push' || github.event_name == 'workflow_dispatch')
runs-on: ubuntu-latest
timeout-minutes: 20

steps:
- name: Checkout
uses: actions/checkout@v7
with:
version: 1.5.1
virtualenvs-create: true
virtualenvs-in-project: true
persist-credentials: false

- name: Load cached dependencies
id: cached-poetry-dependencies
uses: actions/cache@v3
- name: Install uv
uses: astral-sh/setup-uv@v10.3.3
with:
path: .venv
key: ${{ runner.os }}-python-3.11-${{ hashFiles('**/poetry.lock') }}
version: ${{ env.UV_VERSION }}
enable-cache: true
python-version: ${{ env.PYTHON_VERSION }}

- name: Install dependencies
if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
run: poetry install --no-interaction
- name: Install the project
run: uv sync --locked --group dev

- name: Run tests
run: poetry run pytest -q
- name: Run integration tests
run: uv run pytest --integration -q
env:
PERIGON_API_KEY: ${{ secrets.PERIGON_API_KEY }}
Loading
Loading