Skip to content

Commit 950812d

Browse files
authored
PyPI staging: exclude pre-release 3.15 wheels and strip the 3.15 classifier (#8245)
## What Two related changes to the PyPI staging path so pre-release Python 3.15 does not break / leak into PyPI uploads: 1. **Exclude cp315 / cp315t wheels from staging** (`release/pypi/upload_pypi_to_staging.sh`) -- 3.15 is pre-release, so its wheel files should not be promoted to PyPI at all. 2. **Strip the `Programming Language :: Python :: 3.15` classifier** from every staged wheel's METADATA (`release/pypi/prep_binary_for_pypi.py`) -- non-3.15 wheels can still *declare* the 3.15 classifier, which PyPI rejects. ## Why - PyPI validates trove classifiers against a fixed list and **rejects unknown ones**; `Programming Language :: Python :: 3.15` is not recognized yet (pre-release), so any wheel declaring it fails to upload with an "invalid classifier" error. - Independently, the actual 3.15 wheel binaries shouldn't be published to PyPI while the version is pre-release. Both are easy to revert once 3.15 ships (drop the classifier from `CLASSIFIERS_TO_REMOVE` and remove the `-cp315` exclusion). ## How **`upload_pypi_to_staging.sh`** -- add `grep -v -- "-cp315"` to the package-selection pipeline. Both regular (`cp315`) and free-threaded (`cp315t`) 3.15 wheels carry the `-cp315` interpreter tag, so one exclusion drops both; `cp310`..`cp314` (incl. `cp314t`) are untouched. **`prep_binary_for_pypi.py`** -- new `remove_disallowed_classifiers(metadata_file)` + `CLASSIFIERS_TO_REMOVE` set. Called from `process_wheel` after the version-suffix rewrite and **before** the `.dist-info` rename (while `metadata_file` still resolves); auditwheel regenerates `RECORD` on context exit. Matches the **full** classifier value, so `:: 3`, `:: 3.1`, `:: 3.13`, `:: 3.14` and non-Python classifiers are left intact. ## Test plan - `bash -n upload_pypi_to_staging.sh`, `python3 -m py_compile` + `black --check` on the prep script -- all clean. - Filter check: a wheel list of `cp310 / cp314 / cp314t / cp315 / cp315t` passes through the exclusion leaving `cp310, cp314, cp314t` and dropping both `cp315` lines. - Classifier check on a sample METADATA: only `:: 3.15` is dropped; `:: 3`, `:: 3.1`, `:: 3.13`, `:: 3.14` and other (`License ::`) classifiers preserved. ## Note Both changes affect the staging path that repackages suffixed wheels (`VERSION_SUFFIX` set). Wheels moved through without repackaging (no version suffix) are not touched, matching existing behavior.
1 parent 9b13f83 commit 950812d

2 files changed

Lines changed: 44 additions & 0 deletions

File tree

release/pypi/prep_binary_for_pypi.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,40 @@
1717
from pathlib import Path
1818

1919

20+
# PyPI validates trove classifiers against a fixed list and rejects unknown
21+
# ones. Python 3.15 is still pre-release and not yet a recognized classifier, so
22+
# wheels built with 3.15 support fail to upload with an "invalid classifier"
23+
# error. Strip these classifier values from the METADATA of every staged wheel.
24+
CLASSIFIERS_TO_REMOVE = frozenset(
25+
{
26+
"Programming Language :: Python :: 3.15",
27+
}
28+
)
29+
30+
31+
def remove_disallowed_classifiers(metadata_file):
32+
"""Drop Classifier lines PyPI would reject (e.g. pre-release Python 3.15)."""
33+
with open(metadata_file, "r", encoding="utf-8") as f:
34+
lines = f.readlines()
35+
36+
kept = []
37+
removed = []
38+
for line in lines:
39+
# METADATA classifier entries are formatted as "Classifier: <value>".
40+
if line.startswith("Classifier:"):
41+
value = line.split(":", 1)[1].strip()
42+
if value in CLASSIFIERS_TO_REMOVE:
43+
removed.append(value)
44+
continue
45+
kept.append(line)
46+
47+
if removed:
48+
with open(metadata_file, "w", encoding="utf-8") as f:
49+
f.writelines(kept)
50+
for value in removed:
51+
print(f"Removed disallowed classifier: {value}")
52+
53+
2054
def process_wheel(whl_file, output_dir=None):
2155
# Check if auditwheel is installed
2256
try:
@@ -106,6 +140,11 @@ def process_wheel(whl_file, output_dir=None):
106140
# Skip binary files
107141
pass
108142

143+
# Strip classifiers PyPI will not accept (e.g. pre-release 3.15)
144+
# before upload. Done while metadata_file still points at the
145+
# current (pre-rename) dist-info directory.
146+
remove_disallowed_classifiers(metadata_file)
147+
109148
# Rename the dist-info directory
110149
new_dist_info_dir = dist_info_dir.replace(
111150
version_with_suffix, version_no_suffix

release/pypi/upload_pypi_to_staging.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,15 @@ ARCH=${ARCH:-cpu}
2424
# This extract links to packages from the index.html
2525
# We strip all extra characters including final sha256 char
2626
echo "Retrieving packages for promotion for ${PACKAGE_NAME} ${PACKAGE_VERSION}"
27+
# Python 3.15 is still pre-release, so do not stage its wheels to PyPI. Exclude
28+
# both the regular (cp315-cp315) and free-threaded (cp315-cp315t) 3.15 wheels:
29+
# the "-cp315-" clause drops both by interpreter tag, and the explicit "-cp315t-"
30+
# clause also drops any wheel tagged free-threaded by ABI.
2731
pkgs_to_promote=$(\
2832
curl -fsSL "https://download-s3.pytorch.org/whl/test/${ARCH}/${PACKAGE_NAME}/index.html" \
2933
| grep "${PACKAGE_NAME}-${PACKAGE_VERSION}${VERSION_SUFFIX}-" \
3034
| grep "${PLATFORM}" \
35+
| grep -Ev -- "-cp315-|-cp315t-" \
3136
| cut -d '"' -f2 \
3237
| cut -d "#" -f1
3338
)

0 commit comments

Comments
 (0)