Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 35 additions & 5 deletions src/packaging/tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,12 +206,25 @@ def _abi3_applies(python_version: PythonVersion, threading: bool) -> bool:
"""
Determine if the Python version supports abi3.

PEP 384 was first implemented in Python 3.2. The threaded (`--disable-gil`)
PEP 384 was first implemented in Python 3.2. The free-threaded
builds do not support abi3.
"""
return len(python_version) > 1 and tuple(python_version) >= (3, 2) and not threading


def _abi3t_applies(python_version: PythonVersion, threading: bool) -> bool:
"""
Determine if the Python version supports abi3t.

PEP 803 was first implemented in Python 3.15 but, per PEP 803, this
returns tags going back to Python 3.2 to mirror the abi3
implementation and leave open the possibility of abi3t wheels
supporting older Python versions.

"""
return len(python_version) > 1 and tuple(python_version) >= (3, 2) and threading


def _cpython_abis(py_version: PythonVersion, warn: bool = False) -> list[str]:
py_version = tuple(py_version) # To allow for version comparison.
abis = []
Expand Down Expand Up @@ -258,14 +271,18 @@ def cpython_tags(
The specific tags generated are:

- ``cp<python_version>-<abi>-<platform>``
- ``cp<python_version>-abi3-<platform>``
- ``cp<python_version>-<stable_abi>-<platform>``
- ``cp<python_version>-none-<platform>``
- ``cp<older version>-abi3-<platform>`` where "older version" is all older
- ``cp<older version>-<stable_abi>-<platform>`` where "older version" is all older
minor versions down to Python 3.2 (when ``abi3`` was introduced)

If ``python_version`` only provides a major-only version then only
user-provided ABIs via ``abis`` and the ``none`` ABI will be used.

The ``stable_abi`` will be either ``abi3`` or ``abi3t`` if `abi` is a
GIL-enabled ABI like `"cp315"` or a free-threaded ABI like `"cp315t"`,
respectively.

:param Sequence python_version: A one- or two-item sequence representing the
targeted Python version. Defaults to
``sys.version_info[:2]``.
Expand Down Expand Up @@ -297,16 +314,27 @@ def cpython_tags(

threading = _is_threaded_cpython(abis)
use_abi3 = _abi3_applies(python_version, threading)
use_abi3t = _abi3t_applies(python_version, threading)

if use_abi3:
yield from (Tag(interpreter, "abi3", platform_) for platform_ in platforms)
if use_abi3t:
yield from (Tag(interpreter, "abi3t", platform_) for platform_ in platforms)

yield from (Tag(interpreter, "none", platform_) for platform_ in platforms)

if use_abi3:
if use_abi3 or use_abi3t:
for minor_version in range(python_version[1] - 1, 1, -1):
for platform_ in platforms:
version = _version_nodot((python_version[0], minor_version))
interpreter = f"cp{version}"
yield Tag(interpreter, "abi3", platform_)
if use_abi3:
yield Tag(interpreter, "abi3", platform_)
if use_abi3t:
# Support for abi3t was introduced in Python 3.15, but in
# principle abi3t wheels are possible for older limited API
# versions, so allow things like ("cp37", "abi3t", "platform")
yield Tag(interpreter, "abi3t", platform_)


def _generic_abi() -> list[str]:
Expand Down Expand Up @@ -764,6 +792,8 @@ def sys_tags(*, warn: bool = False) -> Iterator[Tag]:

.. versionchanged:: 21.3
Added the `pp3-none-any` tag (:issue:`311`).
.. versionchanged:: 27.0
Added the `abi3t` tag (:issue:`1099`).
"""

interp_name = interpreter_name()
Expand Down
94 changes: 94 additions & 0 deletions tests/test_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -1038,10 +1038,104 @@ def test_all_args(self) -> None:
assert result == [
tags.Tag("cp313", "cp313t", "plat1"),
tags.Tag("cp313", "cp313t", "plat2"),
tags.Tag("cp313", "abi3t", "plat1"),
tags.Tag("cp313", "abi3t", "plat2"),
tags.Tag("cp313", "none", "plat1"),
tags.Tag("cp313", "none", "plat2"),
tags.Tag("cp312", "abi3t", "plat1"),
tags.Tag("cp312", "abi3t", "plat2"),
tags.Tag("cp311", "abi3t", "plat1"),
tags.Tag("cp311", "abi3t", "plat2"),
tags.Tag("cp310", "abi3t", "plat1"),
tags.Tag("cp310", "abi3t", "plat2"),
tags.Tag("cp39", "abi3t", "plat1"),
tags.Tag("cp39", "abi3t", "plat2"),
tags.Tag("cp38", "abi3t", "plat1"),
tags.Tag("cp38", "abi3t", "plat2"),
tags.Tag("cp37", "abi3t", "plat1"),
tags.Tag("cp37", "abi3t", "plat2"),
tags.Tag("cp36", "abi3t", "plat1"),
tags.Tag("cp36", "abi3t", "plat2"),
tags.Tag("cp35", "abi3t", "plat1"),
tags.Tag("cp35", "abi3t", "plat2"),
tags.Tag("cp34", "abi3t", "plat1"),
tags.Tag("cp34", "abi3t", "plat2"),
tags.Tag("cp33", "abi3t", "plat1"),
tags.Tag("cp33", "abi3t", "plat2"),
tags.Tag("cp32", "abi3t", "plat1"),
tags.Tag("cp32", "abi3t", "plat2"),
]

result = list(tags.cpython_tags((3, 15), ["cp315t"], ["platform"]))
assert result == [
tags.Tag("cp315", "cp315t", "platform"),
tags.Tag("cp315", "abi3t", "platform"),
tags.Tag("cp315", "none", "platform"),
tags.Tag("cp314", "abi3t", "platform"),
tags.Tag("cp313", "abi3t", "platform"),
tags.Tag("cp312", "abi3t", "platform"),
tags.Tag("cp311", "abi3t", "platform"),
tags.Tag("cp310", "abi3t", "platform"),
tags.Tag("cp39", "abi3t", "platform"),
tags.Tag("cp38", "abi3t", "platform"),
tags.Tag("cp37", "abi3t", "platform"),
tags.Tag("cp36", "abi3t", "platform"),
tags.Tag("cp35", "abi3t", "platform"),
tags.Tag("cp34", "abi3t", "platform"),
tags.Tag("cp33", "abi3t", "platform"),
tags.Tag("cp32", "abi3t", "platform"),
]

result = list(tags.cpython_tags((3, 16), ["cp316t"], ["platform"]))
assert result == [
tags.Tag("cp316", "cp316t", "platform"),
tags.Tag("cp316", "abi3t", "platform"),
tags.Tag("cp316", "none", "platform"),
tags.Tag("cp315", "abi3t", "platform"),
tags.Tag("cp314", "abi3t", "platform"),
tags.Tag("cp313", "abi3t", "platform"),
tags.Tag("cp312", "abi3t", "platform"),
tags.Tag("cp311", "abi3t", "platform"),
tags.Tag("cp310", "abi3t", "platform"),
tags.Tag("cp39", "abi3t", "platform"),
tags.Tag("cp38", "abi3t", "platform"),
tags.Tag("cp37", "abi3t", "platform"),
tags.Tag("cp36", "abi3t", "platform"),
tags.Tag("cp35", "abi3t", "platform"),
tags.Tag("cp34", "abi3t", "platform"),
tags.Tag("cp33", "abi3t", "platform"),
tags.Tag("cp32", "abi3t", "platform"),
]

result = list(tags.cpython_tags((3, 16), ["cp316"], ["platform"]))
assert result == [
tags.Tag("cp316", "cp316", "platform"),
tags.Tag("cp316", "abi3", "platform"),
tags.Tag("cp316", "none", "platform"),
tags.Tag("cp315", "abi3", "platform"),
tags.Tag("cp314", "abi3", "platform"),
tags.Tag("cp313", "abi3", "platform"),
tags.Tag("cp312", "abi3", "platform"),
tags.Tag("cp311", "abi3", "platform"),
tags.Tag("cp310", "abi3", "platform"),
tags.Tag("cp39", "abi3", "platform"),
tags.Tag("cp38", "abi3", "platform"),
tags.Tag("cp37", "abi3", "platform"),
tags.Tag("cp36", "abi3", "platform"),
tags.Tag("cp35", "abi3", "platform"),
tags.Tag("cp34", "abi3", "platform"),
tags.Tag("cp33", "abi3", "platform"),
tags.Tag("cp32", "abi3", "platform"),
]

def test_no_abi3t_in_non_threaded_interpreter(self) -> None:
result = list(tags.cpython_tags((3, 16), ["cp316"], ["platform"]))
assert all(t.abi in ("cp316", "none", "abi3") for t in result)

def test_no_abi3_in_threaded_interpreter(self) -> None:
result = list(tags.cpython_tags((3, 16), ["cp316t"], ["platform"]))
assert all(t.abi in ("cp316t", "none", "abi3t") for t in result)

def test_python_version_defaults(self) -> None:
tag = next(tags.cpython_tags(abis=["abi3"], platforms=["any"]))
interpreter = "cp" + tags._version_nodot(sys.version_info[:2])
Expand Down