diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml deleted file mode 100644 index 3e74077..0000000 --- a/.github/workflows/publish.yml +++ /dev/null @@ -1,57 +0,0 @@ -name: Publish to PyPI - -on: - release: - types: [published] - -permissions: - contents: read - -jobs: - test: - runs-on: ubuntu-latest - strategy: - matrix: - python-version: ["3.11", "3.12", "3.13"] - steps: - - uses: actions/checkout@v4 - - - uses: astral-sh/setup-uv@v6 - with: - python-version: ${{ matrix.python-version }} - - - name: Install dependencies - run: uv sync --extra dev - - - name: Lint - run: uv run ruff check . - - - name: Type check - run: uv run mypy src/ - - - name: Test - run: uv run pytest - - publish: - needs: test - runs-on: ubuntu-latest - environment: pypi - permissions: - contents: read - id-token: write - steps: - - uses: actions/checkout@v4 - - - uses: astral-sh/setup-uv@v6 - - - name: Set version from release tag - run: | - VERSION="${GITHUB_REF_NAME#v}" - echo "Publishing version: $VERSION" - sed -i "s/^version = \".*\"/version = \"$VERSION\"/" pyproject.toml - - - name: Build package - run: uv build - - - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml new file mode 100644 index 0000000..b61d81d --- /dev/null +++ b/.github/workflows/release-please.yml @@ -0,0 +1,48 @@ +name: Release + +# Independent release automation for this SDK (decoupled from the gateway's own +# releases). release-please watches main: each merged conventional commit +# accumulates into a release PR that bumps the version in pyproject.toml and +# updates CHANGELOG.md. Merging that release PR tags the release and creates a +# GitHub Release, and the publish job below ships it to PyPI in the same run +# (so no PAT is needed to chain a separate workflow off the release event). + +on: + push: + branches: [main] + +permissions: + contents: write + pull-requests: write + +jobs: + release-please: + runs-on: ubuntu-latest + outputs: + release_created: ${{ steps.release.outputs.release_created }} + steps: + - uses: googleapis/release-please-action@v4 + id: release + with: + token: ${{ secrets.GITHUB_TOKEN }} + + publish: + needs: release-please + if: ${{ needs.release-please.outputs.release_created == 'true' }} + runs-on: ubuntu-latest + environment: pypi + permissions: + contents: read + id-token: write + steps: + - uses: actions/checkout@v4 + + - uses: astral-sh/setup-uv@v6 + + - name: Build package + # release-please already committed the version bump to pyproject.toml on + # main, so the build picks it up with no tag-to-file patching needed. + run: uv build + + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 0000000..466df71 --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "0.1.0" +} diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 0000000..46d7c3e --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,40 @@ +# Releasing + +This SDK is versioned independently of the otari gateway, with its own semver. +Releases are automated with [release-please](https://github.com/googleapis/release-please). + +## How a release happens + +1. Merge changes to `main` using [Conventional Commits](https://www.conventionalcommits.org/) + (`feat:`, `fix:`, etc.). This includes the gateway codegen's regeneration PRs + and ordinary shell PRs. +2. release-please opens or updates a single **release PR** that bumps the version + in `pyproject.toml` (`[project].version`) and writes `CHANGELOG.md`. +3. Review and merge the release PR. That tags the release and creates a GitHub + Release. +4. The same workflow run (`.github/workflows/release-please.yml`, gated on + `release_created`) builds the package and publishes it to PyPI. + +## Configuration + +- **Registry:** PyPI (`otari`). +- **Auth:** PyPI trusted publishing via OIDC (`environment: pypi`, `id-token: write`). + No API token secret is stored; the trusted publisher must be configured on PyPI. +- **Version file:** `pyproject.toml` `[project].version` (the only place to change + the version; do not edit it by hand, release-please owns it). + +## Prerequisites (one time, repo settings) + +- Enable **Settings to Actions: "Allow GitHub Actions to create and approve pull + requests"** so release-please can open its release PR. +- Configure the PyPI trusted publisher for this repo and the `pypi` environment. + +## If the publish fails + +The release tag and GitHub Release already exist, so re-run the failed `publish` +job from the Actions tab to retry publishing the same version. Avoid cutting a +release by hand; the automated path keeps `pyproject.toml`, the tag, and the +changelog in sync. + +See the gateway's [SDK release coordination and compatibility](https://github.com/mozilla-ai/otari/blob/main/docs/sdk-compatibility.md) +for the cross-repo policy, the spec-version model, and the end-to-end flow. diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 0000000..0dde12b --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,10 @@ +{ + "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", + "include-v-in-tag": false, + "packages": { + ".": { + "release-type": "python", + "package-name": "otari" + } + } +} diff --git a/src/otari/__init__.py b/src/otari/__init__.py index f6e9895..29a5bed 100644 --- a/src/otari/__init__.py +++ b/src/otari/__init__.py @@ -18,6 +18,10 @@ from importlib.metadata import PackageNotFoundError, version +# Gateway/spec version the generated core was built from, stamped into the core by +# the gateway codegen pipeline. Surfaced here so callers can check which gateway +# spec this SDK targets (see https://github.com/mozilla-ai/otari spec compatibility). +from otari._client._spec_version import __spec_version__ as __spec_version__ from otari.async_client import AsyncOtariClient from otari.client import OtariClient from otari.control_plane import ControlPlane diff --git a/src/otari/_client/_spec_version.py b/src/otari/_client/_spec_version.py new file mode 100644 index 0000000..cdc514d --- /dev/null +++ b/src/otari/_client/_spec_version.py @@ -0,0 +1 @@ +__spec_version__ = "0.0.0-dev" diff --git a/tests/unit/test_spec_version.py b/tests/unit/test_spec_version.py new file mode 100644 index 0000000..7b41de7 --- /dev/null +++ b/tests/unit/test_spec_version.py @@ -0,0 +1,19 @@ +"""The gateway/spec version is surfaced on the public package. + +The gateway codegen stamps the spec version into the generated core +(``otari._client._spec_version``); the package re-exports it as +``otari.__spec_version__`` so callers can tell which gateway spec this SDK +targets. This guards the wiring (a stale literal in ``__init__`` would diverge +from the generated marker). +""" + +from __future__ import annotations + +import otari +from otari._client._spec_version import __spec_version__ as marker + + +def test_spec_version_is_surfaced() -> None: + assert otari.__spec_version__ == marker + assert isinstance(otari.__spec_version__, str) + assert otari.__spec_version__