Skip to content

Commit e892020

Browse files
authored
feat: add support for abi3.abi3t tag from PEP 803 (#1099)
1 parent 15fdefc commit e892020

2 files changed

Lines changed: 129 additions & 5 deletions

File tree

src/packaging/tags.py

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -206,12 +206,25 @@ def _abi3_applies(python_version: PythonVersion, threading: bool) -> bool:
206206
"""
207207
Determine if the Python version supports abi3.
208208
209-
PEP 384 was first implemented in Python 3.2. The threaded (`--disable-gil`)
209+
PEP 384 was first implemented in Python 3.2. The free-threaded
210210
builds do not support abi3.
211211
"""
212212
return len(python_version) > 1 and tuple(python_version) >= (3, 2) and not threading
213213

214214

215+
def _abi3t_applies(python_version: PythonVersion, threading: bool) -> bool:
216+
"""
217+
Determine if the Python version supports abi3t.
218+
219+
PEP 803 was first implemented in Python 3.15 but, per PEP 803, this
220+
returns tags going back to Python 3.2 to mirror the abi3
221+
implementation and leave open the possibility of abi3t wheels
222+
supporting older Python versions.
223+
224+
"""
225+
return len(python_version) > 1 and tuple(python_version) >= (3, 2) and threading
226+
227+
215228
def _cpython_abis(py_version: PythonVersion, warn: bool = False) -> list[str]:
216229
py_version = tuple(py_version) # To allow for version comparison.
217230
abis = []
@@ -258,14 +271,18 @@ def cpython_tags(
258271
The specific tags generated are:
259272
260273
- ``cp<python_version>-<abi>-<platform>``
261-
- ``cp<python_version>-abi3-<platform>``
274+
- ``cp<python_version>-<stable_abi>-<platform>``
262275
- ``cp<python_version>-none-<platform>``
263-
- ``cp<older version>-abi3-<platform>`` where "older version" is all older
276+
- ``cp<older version>-<stable_abi>-<platform>`` where "older version" is all older
264277
minor versions down to Python 3.2 (when ``abi3`` was introduced)
265278
266279
If ``python_version`` only provides a major-only version then only
267280
user-provided ABIs via ``abis`` and the ``none`` ABI will be used.
268281
282+
The ``stable_abi`` will be either ``abi3`` or ``abi3t`` if `abi` is a
283+
GIL-enabled ABI like `"cp315"` or a free-threaded ABI like `"cp315t"`,
284+
respectively.
285+
269286
:param Sequence python_version: A one- or two-item sequence representing the
270287
targeted Python version. Defaults to
271288
``sys.version_info[:2]``.
@@ -297,16 +314,27 @@ def cpython_tags(
297314

298315
threading = _is_threaded_cpython(abis)
299316
use_abi3 = _abi3_applies(python_version, threading)
317+
use_abi3t = _abi3t_applies(python_version, threading)
318+
300319
if use_abi3:
301320
yield from (Tag(interpreter, "abi3", platform_) for platform_ in platforms)
321+
if use_abi3t:
322+
yield from (Tag(interpreter, "abi3t", platform_) for platform_ in platforms)
323+
302324
yield from (Tag(interpreter, "none", platform_) for platform_ in platforms)
303325

304-
if use_abi3:
326+
if use_abi3 or use_abi3t:
305327
for minor_version in range(python_version[1] - 1, 1, -1):
306328
for platform_ in platforms:
307329
version = _version_nodot((python_version[0], minor_version))
308330
interpreter = f"cp{version}"
309-
yield Tag(interpreter, "abi3", platform_)
331+
if use_abi3:
332+
yield Tag(interpreter, "abi3", platform_)
333+
if use_abi3t:
334+
# Support for abi3t was introduced in Python 3.15, but in
335+
# principle abi3t wheels are possible for older limited API
336+
# versions, so allow things like ("cp37", "abi3t", "platform")
337+
yield Tag(interpreter, "abi3t", platform_)
310338

311339

312340
def _generic_abi() -> list[str]:
@@ -764,6 +792,8 @@ def sys_tags(*, warn: bool = False) -> Iterator[Tag]:
764792
765793
.. versionchanged:: 21.3
766794
Added the `pp3-none-any` tag (:issue:`311`).
795+
.. versionchanged:: 27.0
796+
Added the `abi3t` tag (:issue:`1099`).
767797
"""
768798

769799
interp_name = interpreter_name()

tests/test_tags.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1038,10 +1038,104 @@ def test_all_args(self) -> None:
10381038
assert result == [
10391039
tags.Tag("cp313", "cp313t", "plat1"),
10401040
tags.Tag("cp313", "cp313t", "plat2"),
1041+
tags.Tag("cp313", "abi3t", "plat1"),
1042+
tags.Tag("cp313", "abi3t", "plat2"),
10411043
tags.Tag("cp313", "none", "plat1"),
10421044
tags.Tag("cp313", "none", "plat2"),
1045+
tags.Tag("cp312", "abi3t", "plat1"),
1046+
tags.Tag("cp312", "abi3t", "plat2"),
1047+
tags.Tag("cp311", "abi3t", "plat1"),
1048+
tags.Tag("cp311", "abi3t", "plat2"),
1049+
tags.Tag("cp310", "abi3t", "plat1"),
1050+
tags.Tag("cp310", "abi3t", "plat2"),
1051+
tags.Tag("cp39", "abi3t", "plat1"),
1052+
tags.Tag("cp39", "abi3t", "plat2"),
1053+
tags.Tag("cp38", "abi3t", "plat1"),
1054+
tags.Tag("cp38", "abi3t", "plat2"),
1055+
tags.Tag("cp37", "abi3t", "plat1"),
1056+
tags.Tag("cp37", "abi3t", "plat2"),
1057+
tags.Tag("cp36", "abi3t", "plat1"),
1058+
tags.Tag("cp36", "abi3t", "plat2"),
1059+
tags.Tag("cp35", "abi3t", "plat1"),
1060+
tags.Tag("cp35", "abi3t", "plat2"),
1061+
tags.Tag("cp34", "abi3t", "plat1"),
1062+
tags.Tag("cp34", "abi3t", "plat2"),
1063+
tags.Tag("cp33", "abi3t", "plat1"),
1064+
tags.Tag("cp33", "abi3t", "plat2"),
1065+
tags.Tag("cp32", "abi3t", "plat1"),
1066+
tags.Tag("cp32", "abi3t", "plat2"),
10431067
]
10441068

1069+
result = list(tags.cpython_tags((3, 15), ["cp315t"], ["platform"]))
1070+
assert result == [
1071+
tags.Tag("cp315", "cp315t", "platform"),
1072+
tags.Tag("cp315", "abi3t", "platform"),
1073+
tags.Tag("cp315", "none", "platform"),
1074+
tags.Tag("cp314", "abi3t", "platform"),
1075+
tags.Tag("cp313", "abi3t", "platform"),
1076+
tags.Tag("cp312", "abi3t", "platform"),
1077+
tags.Tag("cp311", "abi3t", "platform"),
1078+
tags.Tag("cp310", "abi3t", "platform"),
1079+
tags.Tag("cp39", "abi3t", "platform"),
1080+
tags.Tag("cp38", "abi3t", "platform"),
1081+
tags.Tag("cp37", "abi3t", "platform"),
1082+
tags.Tag("cp36", "abi3t", "platform"),
1083+
tags.Tag("cp35", "abi3t", "platform"),
1084+
tags.Tag("cp34", "abi3t", "platform"),
1085+
tags.Tag("cp33", "abi3t", "platform"),
1086+
tags.Tag("cp32", "abi3t", "platform"),
1087+
]
1088+
1089+
result = list(tags.cpython_tags((3, 16), ["cp316t"], ["platform"]))
1090+
assert result == [
1091+
tags.Tag("cp316", "cp316t", "platform"),
1092+
tags.Tag("cp316", "abi3t", "platform"),
1093+
tags.Tag("cp316", "none", "platform"),
1094+
tags.Tag("cp315", "abi3t", "platform"),
1095+
tags.Tag("cp314", "abi3t", "platform"),
1096+
tags.Tag("cp313", "abi3t", "platform"),
1097+
tags.Tag("cp312", "abi3t", "platform"),
1098+
tags.Tag("cp311", "abi3t", "platform"),
1099+
tags.Tag("cp310", "abi3t", "platform"),
1100+
tags.Tag("cp39", "abi3t", "platform"),
1101+
tags.Tag("cp38", "abi3t", "platform"),
1102+
tags.Tag("cp37", "abi3t", "platform"),
1103+
tags.Tag("cp36", "abi3t", "platform"),
1104+
tags.Tag("cp35", "abi3t", "platform"),
1105+
tags.Tag("cp34", "abi3t", "platform"),
1106+
tags.Tag("cp33", "abi3t", "platform"),
1107+
tags.Tag("cp32", "abi3t", "platform"),
1108+
]
1109+
1110+
result = list(tags.cpython_tags((3, 16), ["cp316"], ["platform"]))
1111+
assert result == [
1112+
tags.Tag("cp316", "cp316", "platform"),
1113+
tags.Tag("cp316", "abi3", "platform"),
1114+
tags.Tag("cp316", "none", "platform"),
1115+
tags.Tag("cp315", "abi3", "platform"),
1116+
tags.Tag("cp314", "abi3", "platform"),
1117+
tags.Tag("cp313", "abi3", "platform"),
1118+
tags.Tag("cp312", "abi3", "platform"),
1119+
tags.Tag("cp311", "abi3", "platform"),
1120+
tags.Tag("cp310", "abi3", "platform"),
1121+
tags.Tag("cp39", "abi3", "platform"),
1122+
tags.Tag("cp38", "abi3", "platform"),
1123+
tags.Tag("cp37", "abi3", "platform"),
1124+
tags.Tag("cp36", "abi3", "platform"),
1125+
tags.Tag("cp35", "abi3", "platform"),
1126+
tags.Tag("cp34", "abi3", "platform"),
1127+
tags.Tag("cp33", "abi3", "platform"),
1128+
tags.Tag("cp32", "abi3", "platform"),
1129+
]
1130+
1131+
def test_no_abi3t_in_non_threaded_interpreter(self) -> None:
1132+
result = list(tags.cpython_tags((3, 16), ["cp316"], ["platform"]))
1133+
assert all(t.abi in ("cp316", "none", "abi3") for t in result)
1134+
1135+
def test_no_abi3_in_threaded_interpreter(self) -> None:
1136+
result = list(tags.cpython_tags((3, 16), ["cp316t"], ["platform"]))
1137+
assert all(t.abi in ("cp316t", "none", "abi3t") for t in result)
1138+
10451139
def test_python_version_defaults(self) -> None:
10461140
tag = next(tags.cpython_tags(abis=["abi3"], platforms=["any"]))
10471141
interpreter = "cp" + tags._version_nodot(sys.version_info[:2])

0 commit comments

Comments
 (0)