Skip to content

chore(release): v0.7.0 (#60) #9

chore(release): v0.7.0 (#60)

chore(release): v0.7.0 (#60) #9

Workflow file for this run

name: Release
# Tag conventions:
# - v0.5.0-rc1, v0.5.0-rc2, ... → TestPyPI only (staging / dry-run)
# - v0.5.0 → PyPI + GitHub Release
#
# pyproject.toml's version field MUST match the tag (minus the leading "v").
# PEP 440 normalizes "0.5.0-rc1" to "0.5.0rc1" at build time, so either form
# is accepted in pyproject; the tag uses the dash form for readability.
on:
push:
tags:
- "v[0-9]*.[0-9]*.[0-9]*"
# Read-only by default. Each publishing job opts in to id-token: write
# (Trusted Publishing OIDC); the github-release job opts in to contents:
# write to create the release.
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
# Conformance fixtures live in the openarmature-spec submodule.
submodules: recursive
- name: Install uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
enable-cache: true
# ``--all-extras`` matches ``ci.yml`` so the OTel-gated tests'
# imports resolve at type-check time. Without it pyright sees
# ``Unknown`` for every OTel-typed expression in
# ``tests/{conformance,unit}/test_observability*.py`` and the
# whole job fails (pyright doesn't honor pytest.importorskip).
# ``--group examples`` mirrors ``ci.yml`` so the
# ``tests/test_examples_smoke.py`` modules can import ``openai``
# at load time (the examples themselves are runnable demos).
- name: Sync deps
run: uv sync --frozen --all-extras --group examples
# Fail fast if pyproject.toml's version doesn't match the pushed
# tag. Both sides go through `packaging.version.Version` so PEP 440
# equivalences like "0.5.0-rc1" ≡ "0.5.0rc1" are accepted.
- name: Verify pyproject.toml version matches tag
run: |
uv run python <<'PY'
import os
import sys
import tomllib
from packaging.version import Version
tag = os.environ["GITHUB_REF_NAME"].removeprefix("v")
with open("pyproject.toml", "rb") as f:
pyproj = tomllib.load(f)["project"]["version"]
if Version(pyproj) != Version(tag):
sys.exit(
f"::error::version mismatch: pyproject={pyproj!r} vs "
f"tag={tag!r} (normalized: pyproject={Version(pyproj)} "
f"tag={Version(tag)})"
)
print(
f"OK: pyproject={pyproj} matches tag={tag} "
f"(normalized: {Version(pyproj)})"
)
PY
- name: Lint (ruff check)
run: uv run ruff check .
- name: Format check (ruff format --check)
run: uv run ruff format --check .
- name: Type check (pyright)
run: uv run pyright src/ tests/
- name: Run tests (pytest)
run: uv run pytest -q
build:
needs: test
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
submodules: recursive
- name: Install uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
enable-cache: true
- name: Sync deps
run: uv sync --frozen
- name: Build package
run: uv build
- name: Upload dist artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: dist
path: dist/
# Pre-release tags (vX.Y.Z-rc*) publish to TestPyPI for verification.
# The job is gated on the tag's name containing "-rc"; non-RC tags skip
# this job and proceed to publish-pypi instead.
publish-testpypi:
needs: build
if: contains(github.ref_name, '-rc')
runs-on: ubuntu-latest
environment: testpypi
permissions:
id-token: write
steps:
- name: Download dist artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: dist
path: dist/
- name: Publish to TestPyPI
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1 branch tip
with:
repository-url: https://test.pypi.org/legacy/
# Real-release tags (vX.Y.Z, no suffix) publish to PyPI. Gated on the
# tag containing NO `-` so that any pre-release suffix (`-rc`, `-beta`,
# `-alpha`, `-dev`, ...) is failsafe — only `-rc` lands on TestPyPI;
# the rest do nothing and require an explicit retag, preventing an
# unintended PyPI upload from a misnamed tag. The pypi environment is
# the recommended place to add a "required reviewers" protection rule
# so the job pauses for manual approval before any real-PyPI upload.
publish-pypi:
needs: build
if: ${{ !contains(github.ref_name, '-') }}
runs-on: ubuntu-latest
environment: pypi
permissions:
id-token: write
steps:
- name: Download dist artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: dist
path: dist/
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1 branch tip
# GitHub Release with auto-generated notes from commits since the last
# tag. Only fires on real releases (no suffix); pre-release tags leave
# no GitHub Release behind.
github-release:
needs: publish-pypi
if: ${{ !contains(github.ref_name, '-') }}
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Download dist artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: dist
path: dist/
- name: Create GitHub Release
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
with:
files: dist/*
generate_release_notes: true