From 0e1a4e09a459cd3883cc3340fd0d17bf5e885919 Mon Sep 17 00:00:00 2001 From: Eric Hare Date: Mon, 23 Feb 2026 10:51:25 -0800 Subject: [PATCH 1/4] feat: More robust release workflow for PyPi --- .github/workflows/_test_release.yml | 3 +- .github/workflows/lint.yml | 13 ++++----- .github/workflows/local.yml | 12 ++++---- .github/workflows/main.yml | 12 ++++---- .github/workflows/release.yml | 44 +++++++++++++++++++++++++++-- .github/workflows/unit.yml | 10 +++---- 6 files changed, 63 insertions(+), 31 deletions(-) diff --git a/.github/workflows/_test_release.yml b/.github/workflows/_test_release.yml index d2ea9087..d983e2bc 100644 --- a/.github/workflows/_test_release.yml +++ b/.github/workflows/_test_release.yml @@ -83,5 +83,4 @@ jobs: repository-url: https://test.pypi.org/legacy/ # This setting ONLY IN CI AND ON TEST PYPI! See https://github.com/pypa/gh-action-pypi-publish#tolerating-release-package-file-duplicates skip-existing: true - # TODO determine whether to enable attestations later on, and how - attestations: false + attestations: true diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 69becbf1..8ab2b7a6 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -14,16 +14,15 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 + + - name: Set up Python + uv + uses: "./.github/actions/uv_setup" with: - python-version: '3.11' # Or any version you prefer + python-version: "3.11" - name: Install dependencies - run: | - python -m pip install --upgrade pip - pipx install uv - make venv + run: uv sync --dev + shell: bash - name: Ruff Linting AstraPy run: | diff --git a/.github/workflows/local.yml b/.github/workflows/local.yml index 73f334a4..fdc38321 100644 --- a/.github/workflows/local.yml +++ b/.github/workflows/local.yml @@ -50,16 +50,14 @@ jobs: - name: Checkout code uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 + - name: Set up Python + uv + uses: "./.github/actions/uv_setup" with: - python-version: 3.12 + python-version: "3.12" - name: Install dependencies - run: | - python -m pip install --upgrade pip - pipx install uv - make venv + run: uv sync --dev + shell: bash - name: Configure AWS credentials from OIDC uses: aws-actions/configure-aws-credentials@v4 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6960b409..c9cd0c40 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -32,16 +32,14 @@ jobs: - name: Checkout code uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 + - name: Set up Python + uv + uses: "./.github/actions/uv_setup" with: - python-version: 3.12 + python-version: "3.12" - name: Install dependencies - run: | - python -m pip install --upgrade pip - pipx install uv - make venv + run: uv sync --dev + shell: bash - name: Run pytest run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index daea2767..8efcb3fc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -23,6 +23,7 @@ jobs: outputs: pkg-name: ${{ steps.check-version.outputs.pkg-name }} version: ${{ steps.check-version.outputs.version }} + version-exists: ${{ steps.check-pypi.outputs.version-exists }} steps: - uses: actions/checkout@v4 @@ -61,9 +62,43 @@ jobs: f.write(f"pkg-name={pkg_name}\n") f.write(f"version={version}\n") + - name: Check if version exists on PyPI + id: check-pypi + shell: python + env: + PKG_NAME: ${{ steps.check-version.outputs.pkg-name }} + VERSION: ${{ steps.check-version.outputs.version }} + run: | + import json + import os + import urllib.request + import urllib.error + + pkg_name = os.environ["PKG_NAME"] + version = os.environ["VERSION"] + + try: + url = f"https://pypi.org/pypi/{pkg_name}/{version}/json" + with urllib.request.urlopen(url) as response: + # If we get here, the version exists on PyPI + print(f"Version {version} already exists on PyPI") + version_exists = "true" + except urllib.error.HTTPError as e: + if e.code == 404: + # Version doesn't exist, we can proceed + print(f"Version {version} does not exist on PyPI, proceeding with release") + version_exists = "false" + else: + # Other HTTP error, fail the workflow + raise + + with open(os.environ["GITHUB_OUTPUT"], "a") as f: + f.write(f"version-exists={version_exists}\n") + test-pypi-publish: needs: - build + if: needs.build.outputs.version-exists == 'false' uses: ./.github/workflows/_test_release.yml permissions: write-all @@ -75,6 +110,7 @@ jobs: needs: - build - test-pypi-publish + if: needs.build.outputs.version-exists == 'false' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -132,6 +168,7 @@ jobs: needs: - build - test-pypi-publish + if: needs.build.outputs.version-exists == 'false' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -169,8 +206,11 @@ jobs: - test-pypi-publish - pre-release-checks - pre-release-unit-lowest-python + if: needs.build.outputs.version-exists == 'false' runs-on: ubuntu-latest # This requires an 'environment' with this name on the github repo (and is best practice to restrict permissions) + # NOTE: Once this trusted publishing workflow is fully verified with actual releases, + # revoke any legacy PyPI API tokens/keys for astrapy for enhanced security. environment: pypi permissions: # Needed by trusted publish: https://blog.pypi.org/posts/2023-04-20-introducing-trusted-publishers/ @@ -196,8 +236,7 @@ jobs: packages-dir: dist/ verbose: true print-hash: true - # TODO determine whether to enable attestations later on, and how - attestations: false + attestations: true mark-release: needs: @@ -206,6 +245,7 @@ jobs: - pre-release-checks - pre-release-unit-lowest-python - publish + if: needs.build.outputs.version-exists == 'false' runs-on: ubuntu-latest permissions: # Needed by `ncipollo/release-action` for creating the release diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml index d9f67f95..c94bdd92 100644 --- a/.github/workflows/unit.yml +++ b/.github/workflows/unit.yml @@ -42,16 +42,14 @@ jobs: - name: Checkout code uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 + - name: Set up Python + uv + uses: "./.github/actions/uv_setup" with: python-version: ${{ matrix.python-version }} - name: Install dependencies - run: | - python -m pip install --upgrade pip - pipx install uv - make venv + run: uv sync --dev + shell: bash - name: Run pytest run: | From 6d40ff681bde14c38ec3db275a3bed907e71d3e1 Mon Sep 17 00:00:00 2001 From: Eric Hare Date: Tue, 10 Mar 2026 11:12:21 -0700 Subject: [PATCH 2/4] Update release.yml --- .github/workflows/release.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8efcb3fc..00f99880 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -69,7 +69,6 @@ jobs: PKG_NAME: ${{ steps.check-version.outputs.pkg-name }} VERSION: ${{ steps.check-version.outputs.version }} run: | - import json import os import urllib.request import urllib.error @@ -79,7 +78,7 @@ jobs: try: url = f"https://pypi.org/pypi/{pkg_name}/{version}/json" - with urllib.request.urlopen(url) as response: + with urllib.request.urlopen(url, timeout=30) as response: # If we get here, the version exists on PyPI print(f"Version {version} already exists on PyPI") version_exists = "true" From 4e77023d7890f464fc012445c564d7e44bee51ed Mon Sep 17 00:00:00 2001 From: Eric Hare Date: Mon, 15 Jun 2026 09:07:18 -0700 Subject: [PATCH 3/4] ci: restore success() gate on release jobs + migrate codecov_aggregator to uv_setup The explicit `if: needs.build.outputs.version-exists == 'false'` added to the release.yml downstream jobs replaced GitHub Actions' implicit success() check on `needs`, so a failed test-pypi-publish no longer blocked pre-release-checks, pre-release-unit-lowest-python, publish, or mark-release. A transient TestPyPI failure could therefore lead to a production PyPI publish that skipped the test-publish gate. Re-add success() to every gated job (`if: success() && needs.build.outputs. version-exists == 'false'`). success() automatically requires all of each job's `needs` to have succeeded, restoring the prior safety semantics while keeping the skip-when-already-published behavior, and stays correct if dependencies are added later. Also migrate codecov_aggregator.yml off the legacy setup-python + pipx + make venv pattern onto the shared ./.github/actions/uv_setup composite action + `uv sync --dev`, matching lint/local/main/unit. --- .github/workflows/codecov_aggregator.yml | 12 +++++------- .github/workflows/release.yml | 10 +++++----- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/.github/workflows/codecov_aggregator.yml b/.github/workflows/codecov_aggregator.yml index bacdf548..2f491573 100644 --- a/.github/workflows/codecov_aggregator.yml +++ b/.github/workflows/codecov_aggregator.yml @@ -75,18 +75,16 @@ jobs: # needed to retrieve the parents of the merge commit (hence the base commit on main) fetch-depth: 2 - - name: Set up Python + - name: Set up Python + uv if: ${{ github.event.action != 'closed' }} - uses: actions/setup-python@v2 + uses: "./.github/actions/uv_setup" with: - python-version: 3.11 + python-version: "3.11" - name: Install dependencies if: ${{ github.event.action != 'closed' }} - run: | - python -m pip install --upgrade pip - pipx install uv - make venv + run: uv sync --dev + shell: bash - name: Download all coverage artifacts from this run if: ${{ github.event.action != 'closed' }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7b121675..dce39218 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -97,7 +97,7 @@ jobs: test-pypi-publish: needs: - build - if: needs.build.outputs.version-exists == 'false' + if: success() && needs.build.outputs.version-exists == 'false' uses: ./.github/workflows/_test_release.yml permissions: write-all @@ -109,7 +109,7 @@ jobs: needs: - build - test-pypi-publish - if: needs.build.outputs.version-exists == 'false' + if: success() && needs.build.outputs.version-exists == 'false' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -167,7 +167,7 @@ jobs: needs: - build - test-pypi-publish - if: needs.build.outputs.version-exists == 'false' + if: success() && needs.build.outputs.version-exists == 'false' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -205,7 +205,7 @@ jobs: - test-pypi-publish - pre-release-checks - pre-release-unit-lowest-python - if: needs.build.outputs.version-exists == 'false' + if: success() && needs.build.outputs.version-exists == 'false' runs-on: ubuntu-latest # This requires an 'environment' with this name on the github repo (and is best practice to restrict permissions) # NOTE: Once this trusted publishing workflow is fully verified with actual releases, @@ -244,7 +244,7 @@ jobs: - pre-release-checks - pre-release-unit-lowest-python - publish - if: needs.build.outputs.version-exists == 'false' + if: success() && needs.build.outputs.version-exists == 'false' runs-on: ubuntu-latest permissions: # Needed by `ncipollo/release-action` for creating the release From a1747cebf65591e55f9a550a4c01f3d1404de770 Mon Sep 17 00:00:00 2001 From: Eric Hare Date: Mon, 15 Jun 2026 11:30:13 -0700 Subject: [PATCH 4/4] ci: split CI tooling migration out of this release-hardening PR Revert the uv_setup composite-action migration of the lint, local, main, unit, and codecov_aggregator workflows from this branch; that mechanical migration now lives in its own PR (branch ci-uv-setup-migration). This PR is narrowed to the release-workflow hardening only: - release.yml: check-pypi version-exists gate + success() guard + attestations - _test_release.yml: attestations --- .github/workflows/codecov_aggregator.yml | 12 +++++++----- .github/workflows/lint.yml | 13 +++++++------ .github/workflows/local.yml | 12 +++++++----- .github/workflows/main.yml | 12 +++++++----- .github/workflows/unit.yml | 10 ++++++---- 5 files changed, 34 insertions(+), 25 deletions(-) diff --git a/.github/workflows/codecov_aggregator.yml b/.github/workflows/codecov_aggregator.yml index 2f491573..bacdf548 100644 --- a/.github/workflows/codecov_aggregator.yml +++ b/.github/workflows/codecov_aggregator.yml @@ -75,16 +75,18 @@ jobs: # needed to retrieve the parents of the merge commit (hence the base commit on main) fetch-depth: 2 - - name: Set up Python + uv + - name: Set up Python if: ${{ github.event.action != 'closed' }} - uses: "./.github/actions/uv_setup" + uses: actions/setup-python@v2 with: - python-version: "3.11" + python-version: 3.11 - name: Install dependencies if: ${{ github.event.action != 'closed' }} - run: uv sync --dev - shell: bash + run: | + python -m pip install --upgrade pip + pipx install uv + make venv - name: Download all coverage artifacts from this run if: ${{ github.event.action != 'closed' }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 8ab2b7a6..69becbf1 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -14,15 +14,16 @@ jobs: steps: - uses: actions/checkout@v2 - - - name: Set up Python + uv - uses: "./.github/actions/uv_setup" + - name: Set up Python + uses: actions/setup-python@v2 with: - python-version: "3.11" + python-version: '3.11' # Or any version you prefer - name: Install dependencies - run: uv sync --dev - shell: bash + run: | + python -m pip install --upgrade pip + pipx install uv + make venv - name: Ruff Linting AstraPy run: | diff --git a/.github/workflows/local.yml b/.github/workflows/local.yml index 41af29fd..7deac31f 100644 --- a/.github/workflows/local.yml +++ b/.github/workflows/local.yml @@ -50,14 +50,16 @@ jobs: - name: Checkout code uses: actions/checkout@v2 - - name: Set up Python + uv - uses: "./.github/actions/uv_setup" + - name: Set up Python + uses: actions/setup-python@v2 with: - python-version: "3.12" + python-version: 3.12 - name: Install dependencies - run: uv sync --dev - shell: bash + run: | + python -m pip install --upgrade pip + pipx install uv + make venv - name: Configure AWS credentials from OIDC uses: aws-actions/configure-aws-credentials@v4 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 322439a5..e2e3c061 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -32,14 +32,16 @@ jobs: - name: Checkout code uses: actions/checkout@v2 - - name: Set up Python + uv - uses: "./.github/actions/uv_setup" + - name: Set up Python + uses: actions/setup-python@v2 with: - python-version: "3.12" + python-version: 3.12 - name: Install dependencies - run: uv sync --dev - shell: bash + run: | + python -m pip install --upgrade pip + pipx install uv + make venv - name: Run pytest run: | diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml index c94bdd92..d9f67f95 100644 --- a/.github/workflows/unit.yml +++ b/.github/workflows/unit.yml @@ -42,14 +42,16 @@ jobs: - name: Checkout code uses: actions/checkout@v2 - - name: Set up Python + uv - uses: "./.github/actions/uv_setup" + - name: Set up Python + uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - name: Install dependencies - run: uv sync --dev - shell: bash + run: | + python -m pip install --upgrade pip + pipx install uv + make venv - name: Run pytest run: |