From b230b39c35551f6e9791f4f5968996c0dc5eb5e4 Mon Sep 17 00:00:00 2001 From: Michael Nefedov Date: Mon, 15 Jun 2026 10:52:17 +0200 Subject: [PATCH] chore: add local `python scripts/release.py` command (build + twine + tag), drop tag-triggered workflow --- .github/workflows/release.yml | 33 -------------------- README.md | 17 ++++++++++ scripts/release.py | 58 +++++++++++++++++++++++++++++++++++ 3 files changed, 75 insertions(+), 33 deletions(-) delete mode 100644 .github/workflows/release.yml create mode 100644 scripts/release.py diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index ae40dbc..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: Release - -on: - push: - tags: - - "v*" - -permissions: - id-token: write # required for PyPI Trusted Publishing - -jobs: - build-and-publish: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Set up Python 3.11 - uses: actions/setup-python@v5 - with: - python-version: "3.11" - - - name: Install build - run: pip install build - - - name: Build wheel and sdist - run: python -m build - - - name: Publish to PyPI (Trusted Publishing) - uses: pypa/gh-action-pypi-publish@release/v1 - # Fallback: if Trusted Publishing is not configured, set the - # PYPI_API_TOKEN repository secret and uncomment the lines below: - # with: - # password: ${{ secrets.PYPI_API_TOKEN }} diff --git a/README.md b/README.md index e93c84b..b338b96 100644 --- a/README.md +++ b/README.md @@ -321,6 +321,23 @@ client = UnisonBrain(max_retries=0) # disable retries +## Releasing + +Set a PyPI token — either configure `~/.pypirc` or export: + +```sh +export TWINE_USERNAME=__token__ +export TWINE_PASSWORD=pypi-... +``` + +Then: + +```sh +python scripts/release.py +``` + +Builds, publishes `unisonlabs` to PyPI (idempotent — skips if the version is already live), then tags and pushes `v`. + ## Contributing Open issues and pull requests at [github.com/unison-labs-ai/python-sdk](https://github.com/unison-labs-ai/python-sdk). diff --git a/scripts/release.py b/scripts/release.py new file mode 100644 index 0000000..5e5829e --- /dev/null +++ b/scripts/release.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +"""Release: build, publish to PyPI (idempotent — skips if the version is already +on PyPI), then git-tag and push. + +Needs a PyPI token. Either configure ~/.pypirc, or: + export TWINE_USERNAME=__token__ + export TWINE_PASSWORD=pypi-... +Then: + python scripts/release.py +""" +import glob +import json +import re +import shutil +import subprocess +import sys +import urllib.error +import urllib.request +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent +text = (ROOT / "pyproject.toml").read_text() +name = re.search(r'(?m)^name = "([^"]+)"', text).group(1) +version = re.search(r'(?m)^version = "([^"]+)"', text).group(1) + + +def on_pypi(pkg: str, ver: str) -> bool: + try: + with urllib.request.urlopen(f"https://pypi.org/pypi/{pkg}/json", timeout=10) as r: + return ver in json.load(r).get("releases", {}) + except urllib.error.HTTPError as e: + if e.code == 404: + return False + raise + except Exception: + return False + + +def run(*cmd: str) -> None: + print("$", " ".join(cmd)) + subprocess.run(cmd, cwd=ROOT, check=True) + + +if on_pypi(name, version): + print(f"{name}=={version} is already on PyPI — nothing to do.") + sys.exit(0) + +run(sys.executable, "-m", "pip", "install", "--quiet", "--upgrade", "build", "twine") +shutil.rmtree(ROOT / "dist", ignore_errors=True) +run(sys.executable, "-m", "build") +run(sys.executable, "-m", "twine", "upload", *glob.glob(str(ROOT / "dist" / "*"))) + +try: + run("git", "tag", f"v{version}") + run("git", "push", "origin", f"v{version}") +except subprocess.CalledProcessError: + print(f"Published, but could not push tag v{version} — tag it manually.") +print(f"Released {name}=={version}.")