Skip to content

Commit 2ddab4a

Browse files
Jammy2211Jammy2211claude
authored
fix: tombstone releases so sub-3.12 pip install fails loudly (#240)
* feat: tombstone releases so sub-3.12 pip install fails loudly Raising `requires-python` does not retract anything. 2026.7.29.2 was the first release published with `Requires-Python >=3.12`; everything at or below 2026.7.29.1 was published with `>=3.9`, and PyPI metadata is immutable, so those releases stay valid pip candidates forever. On 3.9/3.10/3.11 `pip install autolens` therefore does not fail — it backtracks to 2026.7.29.1 and installs the whole stack silently, no JAX and no warning. The install docs claim a "no matching distribution" error that does not happen. Adds `autohands/tombstone.py`: builds one sdist-only release per package at 2026.7.29.1.post1 with `Requires-Python <3.12`, whose build raises with an explanation naming the user's Python version. It outranks every sub-floor candidate and is invisible at or above the floor, so pip below 3.12 reports why instead of quietly installing stale code. Verified against the real PyPI candidate set with the real artifacts: py3.9/3.10/3.11 pip install autolens -> loud failure, correct version named py3.12 pip install autolens -> 2026.8.17.1, unaffected py3.10 autolens==2026.7.29.1 -> still resolves (pins keep working) The one hole — `--only-binary=:all:` skips sdists and still lands on the old wheel — is documented rather than hidden; no packaging mechanism closes it. One-off publish, deliberately not wired into release.yml: future releases all declare >=3.12, so the tombstone stays the top sub-floor candidate forever. Issue: #238 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * feat: manual-dispatch workflow to publish the tombstone sdists The tombstones cannot be published from a laptop — the PyPI tokens live in Actions secrets, and they should stay there. This adds a workflow_dispatch-only job that builds via autohands/tombstone.py and uploads with the same twine version, retry policy and --skip-existing behaviour as release.yml. Deliberately not wired into release.yml: the tombstones are one-off, and republishing a deliberately-broken artifact on every release would be noise. Publishing to the real index requires typing `publish tombstones` as the confirm input. A released filename is permanent — TestPyPI is the rehearsal. Issue: #238 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: drop the hardcoded org URL from the tombstone metadata The tenant firewall flagged `url="https://github.com/PyAutoLabs"` as an instance fact hardcoded in organ code. Removing the field is the right fix rather than allowlisting the file: a tombstone's PyPI page has no use for a homepage link, and its README already carries the whole explanation. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Jammy2211 <JNightingale2211@gmail.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
1 parent b64176d commit 2ddab4a

3 files changed

Lines changed: 733 additions & 0 deletions

File tree

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# Publish the sub-floor tombstone releases (see autohands/tombstone.py).
2+
#
3+
# Manual dispatch only, and deliberately NOT part of release.yml: the tombstones
4+
# are a one-off. Future releases all declare `Requires-Python >=3.12`, so they
5+
# are invisible below the floor and the tombstone stays the top sub-floor
6+
# candidate indefinitely. Re-running this on every release would republish a
7+
# deliberately-broken artifact for no reason.
8+
#
9+
# It uploads distributions that CANNOT be installed — that is their entire
10+
# purpose. Rehearse on TestPyPI first; the `confirm` input is the speed bump
11+
# that stops a stray click from doing it on the real index.
12+
name: publish_tombstone
13+
14+
on:
15+
workflow_dispatch:
16+
inputs:
17+
repository:
18+
description: "Index to upload to"
19+
required: true
20+
default: testpypi
21+
type: choice
22+
options:
23+
- testpypi
24+
- pypi
25+
confirm:
26+
description: >-
27+
Type `publish tombstones` to confirm. Required for pypi.
28+
required: false
29+
default: ""
30+
type: string
31+
32+
jobs:
33+
publish_tombstone:
34+
runs-on: ubuntu-latest
35+
env:
36+
TWINE_USERNAME: __token__
37+
steps:
38+
- uses: actions/checkout@v4
39+
40+
- uses: actions/setup-python@v5
41+
with:
42+
python-version: "3.12"
43+
44+
- name: Guard the real index
45+
if: ${{ inputs.repository == 'pypi' }}
46+
run: |
47+
if [ "${{ inputs.confirm }}" != "publish tombstones" ]; then
48+
echo "Refusing to publish to PyPI without the exact confirmation phrase."
49+
echo "These artifacts are deliberately uninstallable and cannot be deleted"
50+
echo "once uploaded — a released filename is permanent. Re-run with"
51+
echo "confirm: publish tombstones once you have rehearsed on TestPyPI."
52+
exit 1
53+
fi
54+
55+
- name: Build the tombstone sdists
56+
run: |
57+
python3 -m pip install --upgrade build twine==6.0.1
58+
python3 -m autohands.tombstone --out dist-tombstone
59+
60+
- name: Check metadata
61+
# twine check catches a malformed long_description before upload; the
62+
# Requires-Python bound is verified by tombstone.build_all itself, which
63+
# reads it back out of each built artifact rather than trusting input.
64+
run: python3 -m twine check dist-tombstone/*.tar.gz
65+
66+
- name: Upload
67+
env:
68+
TWINE_REPOSITORY: ${{ inputs.repository }}
69+
TWINE_PASSWORD: >-
70+
${{ inputs.repository == 'pypi' && secrets.PYPI || secrets.TEST_PYPI }}
71+
# --skip-existing: a re-run after a partial upload steps over the
72+
# filenames already published instead of hard-failing on the duplicate.
73+
run: |
74+
set -euo pipefail
75+
for attempt in 1 2 3; do
76+
if python3 -m twine upload --skip-existing --verbose dist-tombstone/*.tar.gz; then
77+
break
78+
fi
79+
if [ "$attempt" -eq 3 ]; then
80+
echo "twine upload failed after 3 attempts."
81+
exit 1
82+
fi
83+
DELAY=$(( attempt * 30 ))
84+
echo "twine upload failed (attempt $attempt) — retrying in ${DELAY}s"
85+
sleep "$DELAY"
86+
done
87+
88+
- name: Report
89+
run: |
90+
echo "Uploaded to ${{ inputs.repository }}:"
91+
ls -1 dist-tombstone/*.tar.gz
92+
echo
93+
echo "Verify on an interpreter below the floor, e.g.:"
94+
echo " python3.10 -m venv /tmp/v && /tmp/v/bin/python -m pip install autolens"
95+
echo "It must fail with the tombstone message, not install 2026.7.29.1."

0 commit comments

Comments
 (0)