From 9e7b92b3659d91e1c15670efbb27dddefc403d36 Mon Sep 17 00:00:00 2001 From: yossi <54272821+Apakottur@users.noreply.github.com> Date: Fri, 10 Jul 2026 16:35:04 +0000 Subject: [PATCH 1/4] Add scripts/release.py to cut releases safely Interactive helper that dogfoods shpyx to run the git commands. It: - verifies the working tree is clean and on `main`; - fetches and fast-forwards to `origin/main`; - reads the latest published version from PyPI and prompts for the next one (patch / minor / major); - offers to delete a pre-existing tag (e.g. from a failed release run) before recreating it; - creates and pushes the `v*` tag that triggers the Release workflow. The shebang uses `uv run`, so `./scripts/release.py` works without a manually activated virtualenv. Ignore T201 (print) under scripts/. Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 15 +++---- linters/ruff.toml | 3 ++ scripts/release.py | 99 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 107 insertions(+), 10 deletions(-) create mode 100755 scripts/release.py diff --git a/README.md b/README.md index faceebb..d48a064 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@

-[![PyPI](https://img.shields.io/pypi/v/shpyx?logo=pypi&logoColor=white&style=for-the-badge)](https://pypi.org/project/shpyx/) -[![Downloads](https://img.shields.io/pypi/dm/shpyx?logo=pypi&logoColor=white&style=for-the-badge)](https://pypi.org/project/shpyx/) -[![Python](https://img.shields.io/pypi/pyversions/shpyx?logo=pypi&logoColor=white&style=for-the-badge)](https://pypi.org/project/shpyx/) +[![image](https://img.shields.io/pypi/v/shpyx.svg)](https://pypi.python.org/pypi/shpyx) +[![image](https://img.shields.io/pypi/l/shpyx.svg)](https://github.com/Apakottur/shpyx/blob/main/LICENSE) +[![image](https://img.shields.io/pypi/pyversions/shpyx.svg)](https://pypi.python.org/pypi/shpyx) **shpyx** is a simple, lightweight and typed library for running shell commands in Python. @@ -207,12 +207,7 @@ ty check --config-file linters/ty.toml src tests ### Releasing -The package version is derived from the git tag (via `hatch-vcs`), so there is nothing to bump in -`pyproject.toml`. To release a new version, push a `v`-prefixed tag from `main`: +To release a new version, run the interactive script: ```shell -git tag v0.0.37 -git push origin v0.0.37 +./scripts/release.py ``` - -This triggers the `Release` workflow, which builds the package, publishes it to PyPI using Trusted -Publishing (OIDC, no stored token), and creates a GitHub Release with auto-generated notes. diff --git a/linters/ruff.toml b/linters/ruff.toml index 8d998f9..9373e79 100644 --- a/linters/ruff.toml +++ b/linters/ruff.toml @@ -126,3 +126,6 @@ ban-relative-imports = "all" "tests/*" = [ "S101", # Use of `assert` detected ] +"scripts/*" = [ + "T201", # `print` found (scripts are CLIs that talk to the user via stdout) +] diff --git a/scripts/release.py b/scripts/release.py new file mode 100755 index 0000000..48fdf06 --- /dev/null +++ b/scripts/release.py @@ -0,0 +1,99 @@ +#!/usr/bin/env -S uv run python +""" +Cut a new shpyx release. + +The published version is derived from the git tag (via hatch-vcs), so releasing +is simply a matter of pushing a `v*` tag to `main`. This script does that safely: + + 1. Verify the working tree is clean and on `main`. + 2. Fetch and fast-forward to `origin/main`. + 3. Look up the latest version currently on PyPI. + 4. Let the user pick the next version (patch / minor / major). + 5. If the tag already exists (e.g. a previous release run failed), offer to + delete it first. + 6. Create the tag and push it, which triggers the `Release` GitHub Action. + +Run from the repository root with `./scripts/release.py` (the shebang uses `uv run`, +so shpyx and its environment are set up automatically). +""" + +import json +import sys +import urllib.request + +import shpyx + +PYPI_URL = "https://pypi.org/pypi/shpyx/json" +MAIN_BRANCH = "main" + + +def abort(message: str) -> None: + """Print an error and exit with a non-zero status.""" + print(f"\n❌ {message}") + sys.exit(1) + + +def confirm(question: str) -> bool: + """Ask a yes/no question, defaulting to 'no'.""" + return input(f"{question} [y/N] ").strip().lower() in ("y", "yes") + + +def main() -> None: + # Verify we are on a clean, up-to-date `main` before tagging. + branch = shpyx.run("git rev-parse --abbrev-ref HEAD").stdout.strip() + if branch != MAIN_BRANCH: + abort(f"Must be on the '{MAIN_BRANCH}' branch, but currently on '{branch}'.") + + if shpyx.run("git status --porcelain").stdout.strip(): + abort("Working tree is not clean. Commit or stash your changes first.") + + print("Fetching from origin...") + shpyx.run("git fetch origin --tags --prune", log_output=True) + # Fast-forward only: aborts if local `main` has diverged from origin. + shpyx.run(f"git pull --ff-only origin {MAIN_BRANCH}", log_output=True) + + # Look up the latest published version on PyPI. + with urllib.request.urlopen(PYPI_URL) as response: # noqa: S310 (trusted, hardcoded https URL) + version = json.load(response)["info"]["version"] + parts = version.split(".") + if len(parts) != 3 or not all(part.isdigit() for part in parts): + abort(f"Cannot parse PyPI version {version!r} as 'major.minor.patch'.") + major, minor, patch = (int(part) for part in parts) + + # Let the user pick the next version. + bumps = { + "1": ("patch", f"{major}.{minor}.{patch + 1}"), + "2": ("minor", f"{major}.{minor + 1}.0"), + "3": ("major", f"{major + 1}.0.0"), + } + print(f"\nLatest version on PyPI: {major}.{minor}.{patch}") + print("Select the next version:") + for key, (name, next_version) in bumps.items(): + print(f" {key}) {name:<5} -> {next_version}") + while (choice := input("Choice [1/2/3]: ").strip()) not in bumps: + print("Invalid choice, please enter 1, 2 or 3.") + tag = f"v{bumps[choice][1]}" + + # Handle a pre-existing tag (e.g. from a release run that failed after tagging). + local = shpyx.run(f"git tag --list {tag}").stdout.strip() + remote = shpyx.run(f"git ls-remote --tags origin {tag}").stdout.strip() + if local or remote: + print(f"\n⚠️ Tag {tag} already exists (a previous release may have failed).") + if not confirm(f"Delete the existing {tag} and recreate it?"): + abort("Aborted: tag already exists.") + # Local delete may fail if the tag only exists on the remote; ignore that. + shpyx.run(f"git tag --delete {tag}", verify_return_code=False) + shpyx.run(f"git push --delete origin {tag}", verify_return_code=False, log_output=True) + + if not confirm(f"\nCreate and push tag {tag} to trigger the release?"): + abort("Aborted by user.") + + shpyx.run(f"git tag {tag}") + shpyx.run(f"git push origin {tag}", log_output=True) + + print(f"\n✅ Pushed {tag}. The Release workflow is now running:") + print(" https://github.com/Apakottur/shpyx/actions/workflows/release.yml") + + +if __name__ == "__main__": + main() From cc24e0240f141783f38b460413371e3f55e8d3bd Mon Sep 17 00:00:00 2001 From: yossi <54272821+Apakottur@users.noreply.github.com> Date: Fri, 10 Jul 2026 16:35:10 +0000 Subject: [PATCH 2/4] Add Python version classifiers The shields.io pyversions badge reads the `Programming Language :: Python :: 3.x` trove classifiers from PyPI metadata, which were missing, so the badge was broken. Add classifiers for 3.10-3.14, matching `requires-python` and the CI matrix. The badge will recover once the next release publishes this metadata. Co-Authored-By: Claude Opus 4.8 (1M context) --- pyproject.toml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 4501afc..018893e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,6 +29,12 @@ classifiers = [ "Topic :: Software Development :: Libraries :: Python Modules", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", ] [project.urls] From cf36b2fa530ffeababe945c4ec40bc3fff5722ab Mon Sep 17 00:00:00 2001 From: yossi <54272821+Apakottur@users.noreply.github.com> Date: Fri, 10 Jul 2026 16:44:28 +0000 Subject: [PATCH 3/4] Fixes --- linters/cspell/words.txt | 1 + pyproject.toml | 1 + scripts/release.py | 68 +++++++++++++++----------------------- uv.lock | 71 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 99 insertions(+), 42 deletions(-) diff --git a/linters/cspell/words.txt b/linters/cspell/words.txt index 502735f..4f5818a 100644 --- a/linters/cspell/words.txt +++ b/linters/cspell/words.txt @@ -34,3 +34,4 @@ anyio multibyte haia softprops +httpx diff --git a/pyproject.toml b/pyproject.toml index 018893e..5179895 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,6 +4,7 @@ requires = ["hatchling", "hatch-vcs"] [dependency-groups] dev = [ + "httpx==0.28.1", "mypy==2.2.0", "pre-commit==4.6.0", "pytest-cov==7.1.0", diff --git a/scripts/release.py b/scripts/release.py index 48fdf06..ecca741 100755 --- a/scripts/release.py +++ b/scripts/release.py @@ -1,63 +1,48 @@ -#!/usr/bin/env -S uv run python +#!/usr/bin/env python """ -Cut a new shpyx release. - -The published version is derived from the git tag (via hatch-vcs), so releasing -is simply a matter of pushing a `v*` tag to `main`. This script does that safely: - - 1. Verify the working tree is clean and on `main`. - 2. Fetch and fast-forward to `origin/main`. - 3. Look up the latest version currently on PyPI. - 4. Let the user pick the next version (patch / minor / major). - 5. If the tag already exists (e.g. a previous release run failed), offer to - delete it first. - 6. Create the tag and push it, which triggers the `Release` GitHub Action. - -Run from the repository root with `./scripts/release.py` (the shebang uses `uv run`, -so shpyx and its environment are set up automatically). +Create a new shpyx release. """ -import json import sys -import urllib.request + +import httpx import shpyx -PYPI_URL = "https://pypi.org/pypi/shpyx/json" -MAIN_BRANCH = "main" +_PYPI_URL = "https://pypi.org/pypi/shpyx/json" +_MAIN_BRANCH = "main" -def abort(message: str) -> None: - """Print an error and exit with a non-zero status.""" +def _abort(message: str) -> None: print(f"\n❌ {message}") sys.exit(1) -def confirm(question: str) -> bool: - """Ask a yes/no question, defaulting to 'no'.""" - return input(f"{question} [y/N] ").strip().lower() in ("y", "yes") +def _confirm(question: str) -> None: + user_input = input(f"{question} [y/N] ").strip().lower() + if user_input != "y": + _abort("Aborted by user.") def main() -> None: # Verify we are on a clean, up-to-date `main` before tagging. branch = shpyx.run("git rev-parse --abbrev-ref HEAD").stdout.strip() - if branch != MAIN_BRANCH: - abort(f"Must be on the '{MAIN_BRANCH}' branch, but currently on '{branch}'.") - + if branch != _MAIN_BRANCH: + _abort(f"Must be on the '{_MAIN_BRANCH}' branch, but currently on '{branch}'.") if shpyx.run("git status --porcelain").stdout.strip(): - abort("Working tree is not clean. Commit or stash your changes first.") + _abort("Working tree is not clean. Commit or stash your changes first.") + # Fetch and fast-forward to origin/main. print("Fetching from origin...") - shpyx.run("git fetch origin --tags --prune", log_output=True) - # Fast-forward only: aborts if local `main` has diverged from origin. - shpyx.run(f"git pull --ff-only origin {MAIN_BRANCH}", log_output=True) + shpyx.run("git pull", log_output=True) # Look up the latest published version on PyPI. - with urllib.request.urlopen(PYPI_URL) as response: # noqa: S310 (trusted, hardcoded https URL) - version = json.load(response)["info"]["version"] + pypi_response = httpx.get(_PYPI_URL) + pypi_response.raise_for_status() + version = pypi_response.json()["info"]["version"] parts = version.split(".") if len(parts) != 3 or not all(part.isdigit() for part in parts): - abort(f"Cannot parse PyPI version {version!r} as 'major.minor.patch'.") + _abort(f"Cannot parse PyPI version {version!r} as 'major.minor.patch'.") major, minor, patch = (int(part) for part in parts) # Let the user pick the next version. @@ -79,18 +64,17 @@ def main() -> None: remote = shpyx.run(f"git ls-remote --tags origin {tag}").stdout.strip() if local or remote: print(f"\n⚠️ Tag {tag} already exists (a previous release may have failed).") - if not confirm(f"Delete the existing {tag} and recreate it?"): - abort("Aborted: tag already exists.") + _confirm(f"Delete the existing {tag} and recreate it?") # Local delete may fail if the tag only exists on the remote; ignore that. shpyx.run(f"git tag --delete {tag}", verify_return_code=False) - shpyx.run(f"git push --delete origin {tag}", verify_return_code=False, log_output=True) - - if not confirm(f"\nCreate and push tag {tag} to trigger the release?"): - abort("Aborted by user.") + shpyx.run(f"git push --delete origin {tag}", verify_return_code=False) + # Create and push the tag. + _confirm(f"\nCreate and push tag {tag} to trigger the release?") shpyx.run(f"git tag {tag}") - shpyx.run(f"git push origin {tag}", log_output=True) + shpyx.run(f"git push origin {tag}") + # Print the release URL. print(f"\n✅ Pushed {tag}. The Release workflow is now running:") print(" https://github.com/Apakottur/shpyx/actions/workflows/release.yml") diff --git a/uv.lock b/uv.lock index af28884..85ec3ad 100644 --- a/uv.lock +++ b/uv.lock @@ -6,6 +6,20 @@ resolution-markers = [ "python_full_version < '3.15'", ] +[[package]] +name = "anyio" +version = "4.14.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "idna" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3b/72/5562aabb8dd7181e8e860622a38bea08d17842b99ecd4c91f84ac95251b0/anyio-4.14.1.tar.gz", hash = "sha256:8d648a3544c1a700e3ff78615cd679e4c5c3f149904287e73687b2596963629e", size = 254831, upload-time = "2026-06-24T20:56:06.017Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b0/7b/90df4a0a816d98d6ea26f559d87836d494a2cf1fcf063be67df50a7bcc30/anyio-4.14.1-py3-none-any.whl", hash = "sha256:4e5533c5b8ff0a24f5d7a176cbe6877129cd183893f66b537f8f227d10527d72", size = 124875, upload-time = "2026-06-24T20:56:04.413Z" }, +] + [[package]] name = "ast-serialize" version = "0.6.0" @@ -47,6 +61,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/66/40/c53deb2cd0c9b0fb636d24d9f40924cf2e65028e6b20b10cd5c1eeb2c730/ast_serialize-0.6.0-cp39-abi3-win_arm64.whl", hash = "sha256:ccd132fe8db56f61fe743b1f644d01b8d65b83248a8da506f3132bda86d6ed5e", size = 1072965, upload-time = "2026-06-30T20:02:54.097Z" }, ] +[[package]] +name = "certifi" +version = "2026.6.17" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/c7/424b75da314c1045981bd9777432fad05a9e0c69daa4ed7e308bbaffe405/certifi-2026.6.17.tar.gz", hash = "sha256:024c88eeec92ca068db80f02b8b07c9cef7b9fe261d1d535abfd5abd6f6af432", size = 134594, upload-time = "2026-06-17T10:31:07.894Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/2f/c5464532e965badff2f4c4c1a3a83f5697f0d7c407ed0cda44aaa99bb451/certifi-2026.6.17-py3-none-any.whl", hash = "sha256:2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db", size = 133289, upload-time = "2026-06-17T10:31:06.348Z" }, +] + [[package]] name = "cfgv" version = "3.5.0" @@ -198,6 +221,43 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/60/02/be4a57b60c7149b55b9e3b3c13f609cd8eb5307c751f22bd8fb8d262e75b/filelock-3.29.7-py3-none-any.whl", hash = "sha256:987db6f789a3a2a59f55081801b2b3697cb97e2a736b5f1a9e99b559285fbc51", size = 46036, upload-time = "2026-07-08T05:46:57.53Z" }, ] +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + [[package]] name = "identify" version = "2.6.19" @@ -207,6 +267,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.py3-none-any.whl", hash = "sha256:20e6a87f786f768c092a721ad107fc9df0eb89347be9396cadf3f4abbd1fb78a", size = 99397, upload-time = "2026-04-17T18:39:49.221Z" }, ] +[[package]] +name = "idna" +version = "3.18" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" }, +] + [[package]] name = "iniconfig" version = "2.3.0" @@ -594,6 +663,7 @@ source = { editable = "." } [package.dev-dependencies] dev = [ + { name = "httpx" }, { name = "mypy" }, { name = "pre-commit" }, { name = "pytest" }, @@ -607,6 +677,7 @@ dev = [ [package.metadata.requires-dev] dev = [ + { name = "httpx", specifier = "==0.28.1" }, { name = "mypy", specifier = "==2.2.0" }, { name = "pre-commit", specifier = "==4.6.0" }, { name = "pytest", specifier = "==9.1.1" }, From 1a887692c12392e86d3452219d654a2567f90c0b Mon Sep 17 00:00:00 2001 From: yossi <54272821+Apakottur@users.noreply.github.com> Date: Fri, 10 Jul 2026 16:48:11 +0000 Subject: [PATCH 4/4] Derive repo slug and package name from the origin remote Instead of hardcoding "Apakottur/shpyx" and the PyPI package name, parse the GitHub "owner/repo" slug from `git remote get-url origin` (handles both SSH and HTTPS URLs). The PyPI lookup URL and the Actions URL are then built from it. Co-Authored-By: Claude Opus 4.8 (1M context) --- linters/ruff.toml | 2 +- scripts/release.py | 13 ++++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/linters/ruff.toml b/linters/ruff.toml index 9373e79..6a41689 100644 --- a/linters/ruff.toml +++ b/linters/ruff.toml @@ -127,5 +127,5 @@ ban-relative-imports = "all" "S101", # Use of `assert` detected ] "scripts/*" = [ - "T201", # `print` found (scripts are CLIs that talk to the user via stdout) + "T201", # `print` found ] diff --git a/scripts/release.py b/scripts/release.py index ecca741..b6f5bad 100755 --- a/scripts/release.py +++ b/scripts/release.py @@ -9,7 +9,6 @@ import shpyx -_PYPI_URL = "https://pypi.org/pypi/shpyx/json" _MAIN_BRANCH = "main" @@ -36,8 +35,16 @@ def main() -> None: print("Fetching from origin...") shpyx.run("git pull", log_output=True) + # Derive the GitHub "owner/repo" slug (and PyPI package name) from the origin remote, + # supporting both SSH (git@github.com:owner/repo.git) and HTTPS URLs. + remote_url = shpyx.run("git remote get-url origin").stdout.strip() + slug = remote_url.removesuffix(".git").split("github.com")[-1].strip(":/") + if slug.count("/") != 1: + _abort(f"Cannot parse a GitHub 'owner/repo' slug from origin URL {remote_url!r}.") + package_name = slug.split("/")[1] + # Look up the latest published version on PyPI. - pypi_response = httpx.get(_PYPI_URL) + pypi_response = httpx.get(f"https://pypi.org/pypi/{package_name}/json") pypi_response.raise_for_status() version = pypi_response.json()["info"]["version"] parts = version.split(".") @@ -76,7 +83,7 @@ def main() -> None: # Print the release URL. print(f"\n✅ Pushed {tag}. The Release workflow is now running:") - print(" https://github.com/Apakottur/shpyx/actions/workflows/release.yml") + print(f" https://github.com/{slug}/actions/workflows/release.yml") if __name__ == "__main__":