nab-python 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. nab_python/__init__.py +1 -0
  2. nab_python/_build/__init__.py +1 -0
  3. nab_python/_build/env.py +364 -0
  4. nab_python/_build/errors.py +17 -0
  5. nab_python/_build/runner.py +254 -0
  6. nab_python/_lockfile/__init__.py +1 -0
  7. nab_python/_lockfile/builder.py +339 -0
  8. nab_python/_lockfile/disjointness.py +207 -0
  9. nab_python/_lockfile/pylock.py +323 -0
  10. nab_python/_lockfile/requirements.py +121 -0
  11. nab_python/_packaging_provider.py +98 -0
  12. nab_python/_provider/__init__.py +1 -0
  13. nab_python/_provider/build_remote.py +95 -0
  14. nab_python/_provider/extras.py +231 -0
  15. nab_python/_provider/listing.py +442 -0
  16. nab_python/_provider/lookahead.py +156 -0
  17. nab_python/_provider/metadata_resolver.py +450 -0
  18. nab_python/_provider/priority.py +174 -0
  19. nab_python/_provider/sources.py +215 -0
  20. nab_python/_testing/__init__.py +1 -0
  21. nab_python/_testing/coordinator_fake.py +240 -0
  22. nab_python/_vcs_admission.py +209 -0
  23. nab_python/_vendor/__init__.py +6 -0
  24. nab_python/_vendor/packaging/LICENSE +3 -0
  25. nab_python/_vendor/packaging/LICENSE.APACHE +177 -0
  26. nab_python/_vendor/packaging/LICENSE.BSD +23 -0
  27. nab_python/_vendor/packaging/PROVENANCE.md +73 -0
  28. nab_python/_vendor/packaging/__init__.py +15 -0
  29. nab_python/_vendor/packaging/_elffile.py +108 -0
  30. nab_python/_vendor/packaging/_manylinux.py +265 -0
  31. nab_python/_vendor/packaging/_musllinux.py +88 -0
  32. nab_python/_vendor/packaging/_parser.py +394 -0
  33. nab_python/_vendor/packaging/_structures.py +33 -0
  34. nab_python/_vendor/packaging/_tokenizer.py +196 -0
  35. nab_python/_vendor/packaging/dependency_groups.py +302 -0
  36. nab_python/_vendor/packaging/direct_url.py +325 -0
  37. nab_python/_vendor/packaging/errors.py +94 -0
  38. nab_python/_vendor/packaging/licenses/__init__.py +186 -0
  39. nab_python/_vendor/packaging/licenses/_spdx.py +799 -0
  40. nab_python/_vendor/packaging/markers.py +506 -0
  41. nab_python/_vendor/packaging/metadata.py +964 -0
  42. nab_python/_vendor/packaging/py.typed +0 -0
  43. nab_python/_vendor/packaging/pylock.py +910 -0
  44. nab_python/_vendor/packaging/ranges.py +1803 -0
  45. nab_python/_vendor/packaging/requirements.py +132 -0
  46. nab_python/_vendor/packaging/specifiers.py +1141 -0
  47. nab_python/_vendor/packaging/tags.py +929 -0
  48. nab_python/_vendor/packaging/utils.py +296 -0
  49. nab_python/_vendor/packaging/version.py +1230 -0
  50. nab_python/build_backend.py +184 -0
  51. nab_python/config.py +805 -0
  52. nab_python/download.py +170 -0
  53. nab_python/fetch.py +827 -0
  54. nab_python/lockfile.py +238 -0
  55. nab_python/metadata.py +145 -0
  56. nab_python/provider.py +1235 -0
  57. nab_python/py.typed +0 -0
  58. nab_python/requirements_file.py +180 -0
  59. nab_python/resolve.py +497 -0
  60. nab_python/universal/__init__.py +1 -0
  61. nab_python/universal/matrix.py +235 -0
  62. nab_python/universal/provider.py +214 -0
  63. nab_python/universal/reresolve.py +310 -0
  64. nab_python/universal/resolve.py +508 -0
  65. nab_python/universal/validate.py +439 -0
  66. nab_python/universal/wheel_selection.py +327 -0
  67. nab_python/workspace.py +214 -0
  68. nab_python-0.0.1.dist-info/METADATA +49 -0
  69. nab_python-0.0.1.dist-info/RECORD +71 -0
  70. nab_python-0.0.1.dist-info/WHEEL +4 -0
  71. nab_python-0.0.1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,929 @@
1
+ # This file is dual licensed under the terms of the Apache License, Version
2
+ # 2.0, and the BSD License. See the LICENSE file in the root of this repository
3
+ # for complete details.
4
+
5
+ from __future__ import annotations
6
+
7
+ import logging
8
+ import operator
9
+ import platform
10
+ import re
11
+ import struct
12
+ import subprocess
13
+ import sys
14
+ import sysconfig
15
+ from collections.abc import Iterable, Iterator, Sequence
16
+ from importlib.machinery import EXTENSION_SUFFIXES
17
+ from typing import (
18
+ TYPE_CHECKING,
19
+ TypeVar,
20
+ cast,
21
+ )
22
+
23
+ from . import _manylinux, _musllinux
24
+
25
+ if TYPE_CHECKING:
26
+ from collections.abc import Callable
27
+ from collections.abc import Set as AbstractSet
28
+
29
+
30
+ __all__ = [
31
+ "INTERPRETER_SHORT_NAMES",
32
+ "AppleVersion",
33
+ "PythonVersion",
34
+ "Tag",
35
+ "UnsortedTagsError",
36
+ "android_platforms",
37
+ "compatible_tags",
38
+ "cpython_tags",
39
+ "create_compatible_tags_selector",
40
+ "generic_tags",
41
+ "interpreter_name",
42
+ "interpreter_version",
43
+ "ios_platforms",
44
+ "mac_platforms",
45
+ "parse_tag",
46
+ "platform_tags",
47
+ "sys_tags",
48
+ ]
49
+
50
+
51
+ def __dir__() -> list[str]:
52
+ return __all__
53
+
54
+
55
+ logger = logging.getLogger(__name__)
56
+
57
+ PythonVersion = Sequence[int]
58
+ AppleVersion = tuple[int, int]
59
+ _T = TypeVar("_T")
60
+
61
+ INTERPRETER_SHORT_NAMES: dict[str, str] = {
62
+ "python": "py", # Generic.
63
+ "cpython": "cp",
64
+ "pypy": "pp",
65
+ "ironpython": "ip",
66
+ "jython": "jy",
67
+ }
68
+
69
+
70
+ # This function can be unit tested without reloading the module
71
+ # (Unlike _32_BIT_INTERPRETER)
72
+ def _compute_32_bit_interpreter() -> bool:
73
+ return struct.calcsize("P") == 4
74
+
75
+
76
+ _32_BIT_INTERPRETER = _compute_32_bit_interpreter()
77
+
78
+
79
+ class UnsortedTagsError(ValueError):
80
+ """
81
+ Raised when a tag component is not in sorted order per PEP 425.
82
+ """
83
+
84
+
85
+ class Tag:
86
+ """
87
+ A representation of the tag triple for a wheel.
88
+
89
+ Instances are considered immutable and thus are hashable. Equality checking
90
+ is also supported.
91
+
92
+ Instances are safe to serialize with :mod:`pickle`. They use a stable
93
+ format so the same pickle can be loaded in future packaging releases.
94
+
95
+ .. versionchanged:: 26.2
96
+
97
+ Added a stable pickle format. Pickles created with packaging 26.2+ can
98
+ be unpickled with future releases. Backward compatibility with pickles
99
+ from packaging < 26.2 is supported but may be removed in a future
100
+ release.
101
+ """
102
+
103
+ __slots__ = ["_abi", "_hash", "_interpreter", "_platform"]
104
+
105
+ def __init__(self, interpreter: str, abi: str, platform: str) -> None:
106
+ """
107
+ :param str interpreter: The interpreter name, e.g. ``"py"``
108
+ (see :attr:`INTERPRETER_SHORT_NAMES` for mapping
109
+ well-known interpreter names to their short names).
110
+ :param str abi: The ABI that a wheel supports, e.g. ``"cp37m"``.
111
+ :param str platform: The OS/platform the wheel supports,
112
+ e.g. ``"win_amd64"``.
113
+ """
114
+ self._interpreter = interpreter.lower()
115
+ self._abi = abi.lower()
116
+ self._platform = platform.lower()
117
+ # The __hash__ of every single element in a Set[Tag] will be evaluated each time
118
+ # that a set calls its `.disjoint()` method, which may be called hundreds of
119
+ # times when scanning a page of links for packages with tags matching that
120
+ # Set[Tag]. Pre-computing the value here produces significant speedups for
121
+ # downstream consumers.
122
+ self._hash = hash((self._interpreter, self._abi, self._platform))
123
+
124
+ @property
125
+ def interpreter(self) -> str:
126
+ """
127
+ The interpreter name, e.g. ``"py"`` (see
128
+ :attr:`INTERPRETER_SHORT_NAMES` for mapping well-known interpreter
129
+ names to their short names).
130
+ """
131
+ return self._interpreter
132
+
133
+ @property
134
+ def abi(self) -> str:
135
+ """
136
+ The supported ABI.
137
+ """
138
+ return self._abi
139
+
140
+ @property
141
+ def platform(self) -> str:
142
+ """
143
+ The OS/platform.
144
+ """
145
+ return self._platform
146
+
147
+ def __eq__(self, other: object) -> bool:
148
+ if not isinstance(other, Tag):
149
+ return NotImplemented
150
+
151
+ return (
152
+ (self._hash == other._hash) # Short-circuit ASAP for perf reasons.
153
+ and (self._platform == other._platform)
154
+ and (self._abi == other._abi)
155
+ and (self._interpreter == other._interpreter)
156
+ )
157
+
158
+ def __hash__(self) -> int:
159
+ return self._hash
160
+
161
+ def __str__(self) -> str:
162
+ return f"{self._interpreter}-{self._abi}-{self._platform}"
163
+
164
+ def __repr__(self) -> str:
165
+ return f"<{self} @ {id(self)}>"
166
+
167
+ def __getstate__(self) -> tuple[str, str, str]:
168
+ # Return state as a 3-item tuple: (interpreter, abi, platform).
169
+ # Cache member _hash is excluded and will be recomputed.
170
+ return (self._interpreter, self._abi, self._platform)
171
+
172
+ def __setstate__(self, state: object) -> None:
173
+ if isinstance(state, tuple):
174
+ if len(state) == 3 and all(isinstance(s, str) for s in state):
175
+ # New format (26.2+): (interpreter, abi, platform)
176
+ self._interpreter, self._abi, self._platform = state
177
+ self._hash = hash((self._interpreter, self._abi, self._platform))
178
+ return
179
+ if len(state) == 2 and isinstance(state[1], dict):
180
+ # Old format (packaging <= 26.1, __slots__): (None, {slot: value}).
181
+ _, slots = state
182
+ try:
183
+ interpreter = slots["_interpreter"]
184
+ abi = slots["_abi"]
185
+ platform = slots["_platform"]
186
+ except KeyError:
187
+ raise TypeError(f"Cannot restore Tag from {state!r}") from None
188
+ if not all(
189
+ isinstance(value, str) for value in (interpreter, abi, platform)
190
+ ):
191
+ raise TypeError(f"Cannot restore Tag from {state!r}")
192
+ self._interpreter = interpreter.lower()
193
+ self._abi = abi.lower()
194
+ self._platform = platform.lower()
195
+ self._hash = hash((self._interpreter, self._abi, self._platform))
196
+ return
197
+ raise TypeError(f"Cannot restore Tag from {state!r}")
198
+
199
+
200
+ def parse_tag(tag: str, *, validate_order: bool = False) -> frozenset[Tag]:
201
+ """
202
+ Parses the provided tag (e.g. `py3-none-any`) into a frozenset of
203
+ :class:`Tag` instances.
204
+
205
+ Returning a set is required due to the possibility that the tag is a
206
+ `compressed tag set`_, e.g. ``"py2.py3-none-any"`` which supports both
207
+ Python 2 and Python 3.
208
+
209
+ If **validate_order** is true, compressed tag set components are checked
210
+ to be in sorted order as required by PEP 425.
211
+
212
+ :param str tag: The tag to parse, e.g. ``"py3-none-any"``.
213
+ :param bool validate_order: Check whether compressed tag set components
214
+ are in sorted order.
215
+ :raises UnsortedTagsError: If **validate_order** is true and any compressed tag
216
+ set component is not in sorted order.
217
+
218
+ .. versionadded:: 26.1
219
+ The *validate_order* parameter.
220
+ """
221
+ tags = set()
222
+ interpreters, abis, platforms = tag.split("-")
223
+ if validate_order:
224
+ for component in (interpreters, abis, platforms):
225
+ parts = component.split(".")
226
+ if parts != sorted(parts):
227
+ raise UnsortedTagsError(
228
+ f"Tag component {component!r} is not in sorted order per PEP 425"
229
+ )
230
+ for interpreter in interpreters.split("."):
231
+ for abi in abis.split("."):
232
+ for platform_ in platforms.split("."):
233
+ tags.add(Tag(interpreter, abi, platform_))
234
+ return frozenset(tags)
235
+
236
+
237
+ def _get_config_var(name: str, warn: bool = False) -> int | str | None:
238
+ value: int | str | None = sysconfig.get_config_var(name)
239
+ if value is None and warn:
240
+ logger.debug(
241
+ "Config variable '%s' is unset, Python ABI tag may be incorrect", name
242
+ )
243
+ return value
244
+
245
+
246
+ def _normalize_string(string: str) -> str:
247
+ return string.replace(".", "_").replace("-", "_").replace(" ", "_")
248
+
249
+
250
+ def _is_threaded_cpython(abis: list[str]) -> bool:
251
+ """
252
+ Determine if the ABI corresponds to a threaded (`--disable-gil`) build.
253
+
254
+ The threaded builds are indicated by a "t" in the abiflags.
255
+ """
256
+ if len(abis) == 0:
257
+ return False
258
+ # expect e.g., cp313
259
+ m = re.match(r"cp\d+(.*)", abis[0])
260
+ if not m:
261
+ return False
262
+ abiflags = m.group(1)
263
+ return "t" in abiflags
264
+
265
+
266
+ def _abi3_applies(python_version: PythonVersion, threading: bool) -> bool:
267
+ """
268
+ Determine if the Python version supports abi3.
269
+
270
+ PEP 384 was first implemented in Python 3.2. The free-threaded
271
+ builds do not support abi3.
272
+ """
273
+ return len(python_version) > 1 and tuple(python_version) >= (3, 2) and not threading
274
+
275
+
276
+ def _abi3t_applies(python_version: PythonVersion, threading: bool) -> bool:
277
+ """
278
+ Determine if the Python version supports abi3t.
279
+
280
+ PEP 803 was first implemented in Python 3.15 but, per PEP 803, this
281
+ returns tags going back to Python 3.2 to mirror the abi3
282
+ implementation and leave open the possibility of abi3t wheels
283
+ supporting older Python versions.
284
+
285
+ """
286
+ return len(python_version) > 1 and tuple(python_version) >= (3, 2) and threading
287
+
288
+
289
+ def _cpython_abis(py_version: PythonVersion, warn: bool = False) -> list[str]:
290
+ py_version = tuple(py_version) # To allow for version comparison.
291
+ abis = []
292
+ version = _version_nodot(py_version[:2])
293
+ threading = debug = pymalloc = ucs4 = ""
294
+ with_debug = _get_config_var("Py_DEBUG", warn)
295
+ has_refcount = hasattr(sys, "gettotalrefcount")
296
+ # Windows doesn't set Py_DEBUG, so checking for support of debug-compiled
297
+ # extension modules is the best option.
298
+ # https://github.com/pypa/pip/issues/3383#issuecomment-173267692
299
+ has_ext = "_d.pyd" in EXTENSION_SUFFIXES
300
+ if with_debug or (with_debug is None and (has_refcount or has_ext)):
301
+ debug = "d"
302
+ if py_version >= (3, 13) and _get_config_var("Py_GIL_DISABLED", warn):
303
+ threading = "t"
304
+ if py_version < (3, 8):
305
+ with_pymalloc = _get_config_var("WITH_PYMALLOC", warn)
306
+ if with_pymalloc or with_pymalloc is None:
307
+ pymalloc = "m"
308
+ if py_version < (3, 3):
309
+ unicode_size = _get_config_var("Py_UNICODE_SIZE", warn)
310
+ if unicode_size == 4 or (
311
+ unicode_size is None and sys.maxunicode == 0x10FFFF
312
+ ):
313
+ ucs4 = "u"
314
+ elif debug:
315
+ # Debug builds can also load "normal" extension modules.
316
+ # We can also assume no UCS-4 or pymalloc requirement.
317
+ abis.append(f"cp{version}{threading}")
318
+ abis.insert(0, f"cp{version}{threading}{debug}{pymalloc}{ucs4}")
319
+ return abis
320
+
321
+
322
+ def cpython_tags(
323
+ python_version: PythonVersion | None = None,
324
+ abis: Iterable[str] | None = None,
325
+ platforms: Iterable[str] | None = None,
326
+ *,
327
+ warn: bool = False,
328
+ ) -> Iterator[Tag]:
329
+ """
330
+ Yields the tags for the CPython interpreter.
331
+
332
+ The specific tags generated are:
333
+
334
+ - ``cp<python_version>-<abi>-<platform>``
335
+ - ``cp<python_version>-<stable_abi>-<platform>``
336
+ - ``cp<python_version>-none-<platform>``
337
+ - ``cp<older version>-<stable_abi>-<platform>`` where "older version" is all older
338
+ minor versions down to Python 3.2 (when ``abi3`` was introduced)
339
+
340
+ If ``python_version`` only provides a major-only version then only
341
+ user-provided ABIs via ``abis`` and the ``none`` ABI will be used.
342
+
343
+ The ``stable_abi`` will be either ``abi3`` or ``abi3t`` if `abi` is a
344
+ GIL-enabled ABI like `"cp315"` or a free-threaded ABI like `"cp315t"`,
345
+ respectively.
346
+
347
+ :param Sequence python_version: A one- or two-item sequence representing the
348
+ targeted Python version. Defaults to
349
+ ``sys.version_info[:2]``.
350
+ :param Iterable abis: Iterable of compatible ABIs. Defaults to the ABIs
351
+ compatible with the current system.
352
+ :param Iterable platforms: Iterable of compatible platforms. Defaults to the
353
+ platforms compatible with the current system.
354
+ :param bool warn: Whether warnings should be logged. Defaults to ``False``.
355
+ """
356
+ if not python_version:
357
+ python_version = sys.version_info[:2]
358
+
359
+ interpreter = f"cp{_version_nodot(python_version[:2])}"
360
+
361
+ if abis is None:
362
+ abis = _cpython_abis(python_version, warn) if len(python_version) > 1 else []
363
+ abis = list(abis)
364
+ # 'abi3' and 'none' are explicitly handled later.
365
+ for explicit_abi in ("abi3", "none"):
366
+ try:
367
+ abis.remove(explicit_abi)
368
+ except ValueError: # noqa: PERF203
369
+ pass
370
+
371
+ platforms = list(platforms or platform_tags())
372
+ for abi in abis:
373
+ for platform_ in platforms:
374
+ yield Tag(interpreter, abi, platform_)
375
+
376
+ threading = _is_threaded_cpython(abis)
377
+ use_abi3 = _abi3_applies(python_version, threading)
378
+ use_abi3t = _abi3t_applies(python_version, threading)
379
+
380
+ if use_abi3:
381
+ yield from (Tag(interpreter, "abi3", platform_) for platform_ in platforms)
382
+ if use_abi3t:
383
+ yield from (Tag(interpreter, "abi3t", platform_) for platform_ in platforms)
384
+
385
+ yield from (Tag(interpreter, "none", platform_) for platform_ in platforms)
386
+
387
+ if use_abi3 or use_abi3t:
388
+ for minor_version in range(python_version[1] - 1, 1, -1):
389
+ for platform_ in platforms:
390
+ version = _version_nodot((python_version[0], minor_version))
391
+ interpreter = f"cp{version}"
392
+ if use_abi3:
393
+ yield Tag(interpreter, "abi3", platform_)
394
+ if use_abi3t:
395
+ # Support for abi3t was introduced in Python 3.15, but in
396
+ # principle abi3t wheels are possible for older limited API
397
+ # versions, so allow things like ("cp37", "abi3t", "platform")
398
+ yield Tag(interpreter, "abi3t", platform_)
399
+
400
+
401
+ def _generic_abi() -> list[str]:
402
+ """
403
+ Return the ABI tag based on EXT_SUFFIX.
404
+ """
405
+ # The following are examples of `EXT_SUFFIX`.
406
+ # We want to keep the parts which are related to the ABI and remove the
407
+ # parts which are related to the platform:
408
+ # - linux: '.cpython-310-x86_64-linux-gnu.so' => cp310
409
+ # - mac: '.cpython-310-darwin.so' => cp310
410
+ # - win: '.cp310-win_amd64.pyd' => cp310
411
+ # - win: '.pyd' => cp37 (uses _cpython_abis())
412
+ # - pypy: '.pypy38-pp73-x86_64-linux-gnu.so' => pypy38_pp73
413
+ # - graalpy: '.graalpy-38-native-x86_64-darwin.dylib'
414
+ # => graalpy_38_native
415
+
416
+ ext_suffix = _get_config_var("EXT_SUFFIX", warn=True)
417
+ if not isinstance(ext_suffix, str) or ext_suffix[0] != ".":
418
+ raise SystemError("invalid sysconfig.get_config_var('EXT_SUFFIX')")
419
+ parts = ext_suffix.split(".")
420
+ if len(parts) < 3:
421
+ # CPython3.7 and earlier uses ".pyd" on Windows.
422
+ return _cpython_abis(sys.version_info[:2])
423
+ soabi = parts[1]
424
+ if soabi.startswith("cpython"):
425
+ # non-windows
426
+ abi = "cp" + soabi.split("-")[1]
427
+ elif soabi.startswith("cp"):
428
+ # windows
429
+ abi = soabi.split("-")[0]
430
+ elif soabi.startswith("pypy"):
431
+ abi = "-".join(soabi.split("-")[:2])
432
+ elif soabi.startswith("graalpy"):
433
+ abi = "-".join(soabi.split("-")[:3])
434
+ elif soabi:
435
+ # pyston, ironpython, others?
436
+ abi = soabi
437
+ else:
438
+ return []
439
+ return [_normalize_string(abi)]
440
+
441
+
442
+ def generic_tags(
443
+ interpreter: str | None = None,
444
+ abis: Iterable[str] | None = None,
445
+ platforms: Iterable[str] | None = None,
446
+ *,
447
+ warn: bool = False,
448
+ ) -> Iterator[Tag]:
449
+ """
450
+ Yields the tags for an interpreter which requires no specialization.
451
+
452
+ This function should be used if one of the other interpreter-specific
453
+ functions provided by this module is not appropriate (i.e. not calculating
454
+ tags for a CPython interpreter).
455
+
456
+ The specific tags generated are:
457
+
458
+ - ``<interpreter>-<abi>-<platform>``
459
+
460
+ The ``"none"`` ABI will be added if it was not explicitly provided.
461
+
462
+ :param str interpreter: The name of the interpreter. Defaults to being
463
+ calculated.
464
+ :param Iterable abis: Iterable of compatible ABIs. Defaults to the ABIs
465
+ compatible with the current system.
466
+ :param Iterable platforms: Iterable of compatible platforms. Defaults to the
467
+ platforms compatible with the current system.
468
+ :param bool warn: Whether warnings should be logged. Defaults to ``False``.
469
+ """
470
+ if not interpreter:
471
+ interp_name = interpreter_name()
472
+ interp_version = interpreter_version(warn=warn)
473
+ interpreter = f"{interp_name}{interp_version}"
474
+ abis = _generic_abi() if abis is None else list(abis)
475
+ platforms = list(platforms or platform_tags())
476
+ if "none" not in abis:
477
+ abis.append("none")
478
+ for abi in abis:
479
+ for platform_ in platforms:
480
+ yield Tag(interpreter, abi, platform_)
481
+
482
+
483
+ def _py_interpreter_range(py_version: PythonVersion) -> Iterator[str]:
484
+ """
485
+ Yields Python versions in descending order.
486
+
487
+ After the latest version, the major-only version will be yielded, and then
488
+ all previous versions of that major version.
489
+ """
490
+ if len(py_version) > 1:
491
+ yield f"py{_version_nodot(py_version[:2])}"
492
+ yield f"py{py_version[0]}"
493
+ if len(py_version) > 1:
494
+ for minor in range(py_version[1] - 1, -1, -1):
495
+ yield f"py{_version_nodot((py_version[0], minor))}"
496
+
497
+
498
+ def compatible_tags(
499
+ python_version: PythonVersion | None = None,
500
+ interpreter: str | None = None,
501
+ platforms: Iterable[str] | None = None,
502
+ ) -> Iterator[Tag]:
503
+ """
504
+ Yields the tags for an interpreter compatible with the Python version
505
+ specified by ``python_version``.
506
+
507
+ The specific tags generated are:
508
+
509
+ - ``py*-none-<platform>``
510
+ - ``<interpreter>-none-any`` if ``interpreter`` is provided
511
+ - ``py*-none-any``
512
+
513
+ :param Sequence python_version: A one- or two-item sequence representing the
514
+ compatible version of Python. Defaults to
515
+ ``sys.version_info[:2]``.
516
+ :param str interpreter: The name of the interpreter (if known), e.g.
517
+ ``"cp38"``. Defaults to the current interpreter.
518
+ :param Iterable platforms: Iterable of compatible platforms. Defaults to the
519
+ platforms compatible with the current system.
520
+ """
521
+ if not python_version:
522
+ python_version = sys.version_info[:2]
523
+ platforms = list(platforms or platform_tags())
524
+ for version in _py_interpreter_range(python_version):
525
+ for platform_ in platforms:
526
+ yield Tag(version, "none", platform_)
527
+ if interpreter:
528
+ yield Tag(interpreter, "none", "any")
529
+ for version in _py_interpreter_range(python_version):
530
+ yield Tag(version, "none", "any")
531
+
532
+
533
+ def _mac_arch(arch: str, is_32bit: bool = _32_BIT_INTERPRETER) -> str:
534
+ if not is_32bit:
535
+ return arch
536
+
537
+ if arch.startswith("ppc"):
538
+ return "ppc"
539
+
540
+ return "i386"
541
+
542
+
543
+ def _mac_binary_formats(version: AppleVersion, cpu_arch: str) -> list[str]:
544
+ formats = [cpu_arch]
545
+ if cpu_arch == "x86_64":
546
+ if version < (10, 4):
547
+ return []
548
+ formats.extend(["intel", "fat64", "fat32"])
549
+
550
+ elif cpu_arch == "i386":
551
+ if version < (10, 4):
552
+ return []
553
+ formats.extend(["intel", "fat32", "fat"])
554
+
555
+ elif cpu_arch == "ppc64":
556
+ # TODO: Need to care about 32-bit PPC for ppc64 through 10.2?
557
+ if version > (10, 5) or version < (10, 4):
558
+ return []
559
+ formats.append("fat64")
560
+
561
+ elif cpu_arch == "ppc":
562
+ if version > (10, 6):
563
+ return []
564
+ formats.extend(["fat32", "fat"])
565
+
566
+ if cpu_arch in {"arm64", "x86_64"}:
567
+ formats.append("universal2")
568
+
569
+ if cpu_arch in {"x86_64", "i386", "ppc64", "ppc", "intel"}:
570
+ formats.append("universal")
571
+
572
+ return formats
573
+
574
+
575
+ def mac_platforms(
576
+ version: AppleVersion | None = None, arch: str | None = None
577
+ ) -> Iterator[str]:
578
+ """
579
+ Yields the :attr:`~Tag.platform` tags for macOS.
580
+
581
+ The `version` parameter is a two-item tuple specifying the macOS version to
582
+ generate platform tags for. The `arch` parameter is the CPU architecture to
583
+ generate platform tags for. Both parameters default to the appropriate value
584
+ for the current system.
585
+
586
+ :param tuple version: A two-item tuple representing the version of macOS.
587
+ Defaults to the current system's version.
588
+ :param str arch: The CPU architecture. Defaults to the architecture of the
589
+ current system, e.g. ``"x86_64"``.
590
+
591
+ .. note::
592
+ Equivalent support for the other major platforms is purposefully not
593
+ provided:
594
+
595
+ - On Windows, platform compatibility is statically specified
596
+ - On Linux, code must be run on the system itself to determine
597
+ compatibility
598
+ """
599
+ version_str, _, cpu_arch = platform.mac_ver()
600
+ if version is None:
601
+ version = cast("AppleVersion", tuple(map(int, version_str.split(".")[:2])))
602
+ if version == (10, 16):
603
+ # When built against an older macOS SDK, Python will report macOS 10.16
604
+ # instead of the real version.
605
+ version_str = subprocess.run(
606
+ [
607
+ sys.executable,
608
+ "-sS",
609
+ "-c",
610
+ "import platform; print(platform.mac_ver()[0])",
611
+ ],
612
+ check=True,
613
+ env={"SYSTEM_VERSION_COMPAT": "0"},
614
+ stdout=subprocess.PIPE,
615
+ text=True,
616
+ ).stdout
617
+ version = cast("AppleVersion", tuple(map(int, version_str.split(".")[:2])))
618
+
619
+ if arch is None:
620
+ arch = _mac_arch(cpu_arch)
621
+
622
+ if (10, 0) <= version < (11, 0):
623
+ # Prior to Mac OS 11, each yearly release of Mac OS bumped the
624
+ # "minor" version number. The major version was always 10.
625
+ major_version = 10
626
+ for minor_version in range(version[1], -1, -1):
627
+ compat_version = major_version, minor_version
628
+ binary_formats = _mac_binary_formats(compat_version, arch)
629
+ for binary_format in binary_formats:
630
+ yield f"macosx_{major_version}_{minor_version}_{binary_format}"
631
+
632
+ if version >= (11, 0):
633
+ # Starting with Mac OS 11, each yearly release bumps the major version
634
+ # number. The minor versions are now the midyear updates.
635
+ minor_version = 0
636
+ for major_version in range(version[0], 10, -1):
637
+ compat_version = major_version, minor_version
638
+ binary_formats = _mac_binary_formats(compat_version, arch)
639
+ for binary_format in binary_formats:
640
+ yield f"macosx_{major_version}_{minor_version}_{binary_format}"
641
+
642
+ if version >= (11, 0):
643
+ # Mac OS 11 on x86_64 is compatible with binaries from previous releases.
644
+ # Arm64 support was introduced in 11.0, so no Arm binaries from previous
645
+ # releases exist.
646
+ #
647
+ # However, the "universal2" binary format can have a
648
+ # macOS version earlier than 11.0 when the x86_64 part of the binary supports
649
+ # that version of macOS.
650
+ major_version = 10
651
+ if arch == "x86_64":
652
+ for minor_version in range(16, 3, -1):
653
+ compat_version = major_version, minor_version
654
+ binary_formats = _mac_binary_formats(compat_version, arch)
655
+ for binary_format in binary_formats:
656
+ yield f"macosx_{major_version}_{minor_version}_{binary_format}"
657
+ else:
658
+ for minor_version in range(16, 3, -1):
659
+ compat_version = major_version, minor_version
660
+ binary_format = "universal2"
661
+ yield f"macosx_{major_version}_{minor_version}_{binary_format}"
662
+
663
+
664
+ def ios_platforms(
665
+ version: AppleVersion | None = None, multiarch: str | None = None
666
+ ) -> Iterator[str]:
667
+ """
668
+
669
+ Yields the :attr:`~Tag.platform` tags for iOS.
670
+
671
+ :param tuple version: A two-item tuple representing the version of iOS.
672
+ Defaults to the current system's version.
673
+ :param str multiarch: The CPU architecture+ABI to be used. This should be in
674
+ the format by ``sys.implementation._multiarch`` (e.g.,
675
+ ``arm64_iphoneos`` or ``x86_64_iphonesimulator``).
676
+ Defaults to the current system's multiarch value.
677
+
678
+ .. note::
679
+ Behavior of this method is undefined if invoked on non-iOS platforms
680
+ without providing explicit version and multiarch arguments.
681
+ """
682
+ if version is None:
683
+ # if iOS is the current platform, ios_ver *must* be defined. However,
684
+ # it won't exist for CPython versions before 3.13, which causes a mypy
685
+ # error.
686
+ _, release, _, _ = platform.ios_ver() # type: ignore[attr-defined, unused-ignore]
687
+ version = cast("AppleVersion", tuple(map(int, release.split(".")[:2])))
688
+
689
+ if multiarch is None:
690
+ multiarch = sys.implementation._multiarch
691
+ multiarch = multiarch.replace("-", "_")
692
+
693
+ ios_platform_template = "ios_{major}_{minor}_{multiarch}"
694
+
695
+ # Consider any iOS major.minor version from the version requested, down to
696
+ # 12.0. 12.0 is the first iOS version that is known to have enough features
697
+ # to support CPython. Consider every possible minor release up to X.9. There
698
+ # highest the minor has ever gone is 8 (14.8 and 15.8) but having some extra
699
+ # candidates that won't ever match doesn't really hurt, and it saves us from
700
+ # having to keep an explicit list of known iOS versions in the code. Return
701
+ # the results descending order of version number.
702
+
703
+ # If the requested major version is less than 12, there won't be any matches.
704
+ if version[0] < 12:
705
+ return
706
+
707
+ # Consider the actual X.Y version that was requested.
708
+ yield ios_platform_template.format(
709
+ major=version[0], minor=version[1], multiarch=multiarch
710
+ )
711
+
712
+ # Consider every minor version from X.0 to the minor version prior to the
713
+ # version requested by the platform.
714
+ for minor in range(version[1] - 1, -1, -1):
715
+ yield ios_platform_template.format(
716
+ major=version[0], minor=minor, multiarch=multiarch
717
+ )
718
+
719
+ for major in range(version[0] - 1, 11, -1):
720
+ for minor in range(9, -1, -1):
721
+ yield ios_platform_template.format(
722
+ major=major, minor=minor, multiarch=multiarch
723
+ )
724
+
725
+
726
+ def android_platforms(
727
+ api_level: int | None = None, abi: str | None = None
728
+ ) -> Iterator[str]:
729
+ """
730
+ Yields the :attr:`~Tag.platform` tags for Android. If this function is invoked on
731
+ non-Android platforms, the ``api_level`` and ``abi`` arguments are required.
732
+
733
+ :param int api_level: The maximum `API level
734
+ <https://developer.android.com/tools/releases/platforms>`__ to return. Defaults
735
+ to the current system's version, as returned by ``platform.android_ver``.
736
+ :param str abi: The `Android ABI <https://developer.android.com/ndk/guides/abis>`__,
737
+ e.g. ``arm64_v8a``. Defaults to the current system's ABI , as returned by
738
+ ``sysconfig.get_platform``. Hyphens and periods will be replaced with
739
+ underscores.
740
+ """
741
+ if platform.system() != "Android" and (api_level is None or abi is None):
742
+ raise TypeError(
743
+ "on non-Android platforms, the api_level and abi arguments are required"
744
+ )
745
+
746
+ if api_level is None:
747
+ # Python 3.13 was the first version to return platform.system() == "Android",
748
+ # and also the first version to define platform.android_ver().
749
+ api_level = platform.android_ver().api_level # type: ignore[attr-defined]
750
+
751
+ if abi is None:
752
+ abi = sysconfig.get_platform().split("-")[-1]
753
+ abi = _normalize_string(abi)
754
+
755
+ # 16 is the minimum API level known to have enough features to support CPython
756
+ # without major patching. Yield every API level from the maximum down to the
757
+ # minimum, inclusive.
758
+ min_api_level = 16
759
+ for ver in range(api_level, min_api_level - 1, -1):
760
+ yield f"android_{ver}_{abi}"
761
+
762
+
763
+ def _linux_platforms(is_32bit: bool = _32_BIT_INTERPRETER) -> Iterator[str]:
764
+ linux = _normalize_string(sysconfig.get_platform())
765
+ if not linux.startswith("linux_"):
766
+ # we should never be here, just yield the sysconfig one and return
767
+ yield linux
768
+ return
769
+ if is_32bit:
770
+ if linux == "linux_x86_64":
771
+ linux = "linux_i686"
772
+ elif linux == "linux_aarch64":
773
+ linux = "linux_armv8l"
774
+ _, arch = linux.split("_", 1)
775
+ archs = {"armv8l": ["armv8l", "armv7l"]}.get(arch, [arch])
776
+ yield from _manylinux.platform_tags(archs)
777
+ yield from _musllinux.platform_tags(archs)
778
+ for arch in archs:
779
+ yield f"linux_{arch}"
780
+
781
+
782
+ def _emscripten_platforms() -> Iterator[str]:
783
+ pyemscripten_platform_version = sysconfig.get_config_var(
784
+ "PYEMSCRIPTEN_PLATFORM_VERSION"
785
+ )
786
+ if pyemscripten_platform_version:
787
+ yield f"pyemscripten_{pyemscripten_platform_version}_wasm32"
788
+ yield from _generic_platforms()
789
+
790
+
791
+ def _generic_platforms() -> Iterator[str]:
792
+ yield _normalize_string(sysconfig.get_platform())
793
+
794
+
795
+ def platform_tags() -> Iterator[str]:
796
+ """
797
+ Yields the :attr:`~Tag.platform` tags for the running interpreter.
798
+ """
799
+ if platform.system() == "Darwin":
800
+ return mac_platforms()
801
+ elif platform.system() == "iOS":
802
+ return ios_platforms()
803
+ elif platform.system() == "Android":
804
+ return android_platforms()
805
+ elif platform.system() == "Linux":
806
+ return _linux_platforms()
807
+ elif platform.system() == "Emscripten":
808
+ return _emscripten_platforms()
809
+ else:
810
+ return _generic_platforms()
811
+
812
+
813
+ def interpreter_name() -> str:
814
+ """
815
+ Returns the name of the running interpreter.
816
+
817
+ Some implementations have a reserved, two-letter abbreviation which will
818
+ be returned when appropriate.
819
+
820
+ This typically acts as the prefix to the :attr:`~Tag.interpreter` tag.
821
+ """
822
+ name = sys.implementation.name
823
+ return INTERPRETER_SHORT_NAMES.get(name) or name
824
+
825
+
826
+ def interpreter_version(*, warn: bool = False) -> str:
827
+ """
828
+ Returns the running interpreter's version.
829
+
830
+ This typically acts as the suffix to the :attr:`~Tag.interpreter` tag.
831
+
832
+ :param bool warn: Whether warnings should be logged. Defaults to ``False``.
833
+ """
834
+ version = _get_config_var("py_version_nodot", warn=warn)
835
+ return str(version) if version else _version_nodot(sys.version_info[:2])
836
+
837
+
838
+ def _version_nodot(version: PythonVersion) -> str:
839
+ return "".join(map(str, version))
840
+
841
+
842
+ def sys_tags(*, warn: bool = False) -> Iterator[Tag]:
843
+ """
844
+ Yields the sequence of tag triples that the running interpreter supports.
845
+
846
+ The iterable is ordered so that the best-matching tag is first in the
847
+ sequence. The exact preferential order to tags is interpreter-specific, but
848
+ in general the tag importance is in the order of:
849
+
850
+ 1. Interpreter
851
+ 2. Platform
852
+ 3. ABI
853
+
854
+ This order is due to the fact that an ABI is inherently tied to the
855
+ platform, but platform-specific code is not necessarily tied to the ABI. The
856
+ interpreter is the most important tag as it dictates basic support for any
857
+ wheel.
858
+
859
+ The function returns an iterable in order to allow for the possible
860
+ short-circuiting of tag generation if the entire sequence is not necessary
861
+ and tag calculation happens to be expensive.
862
+
863
+ :param bool warn: Whether warnings should be logged. Defaults to ``False``.
864
+
865
+ .. versionchanged:: 21.3
866
+ Added the `pp3-none-any` tag (:issue:`311`).
867
+ .. versionchanged:: 27.0
868
+ Added the `abi3t` tag (:issue:`1099`).
869
+ """
870
+
871
+ interp_name = interpreter_name()
872
+ if interp_name == "cp":
873
+ yield from cpython_tags(warn=warn)
874
+ else:
875
+ yield from generic_tags()
876
+
877
+ if interp_name == "pp":
878
+ interp = "pp3"
879
+ elif interp_name == "cp":
880
+ interp = "cp" + interpreter_version(warn=warn)
881
+ else:
882
+ interp = None
883
+ yield from compatible_tags(interpreter=interp)
884
+
885
+
886
+ def create_compatible_tags_selector(
887
+ tags: Iterable[Tag],
888
+ ) -> Callable[[Iterable[tuple[_T, AbstractSet[Tag]]]], Iterator[_T]]:
889
+ """Create a callable to select things compatible with supported tags.
890
+
891
+ This function accepts an ordered sequence of tags, with the preferred
892
+ tags first.
893
+
894
+ The returned callable accepts an iterable of tuples (thing, set[Tag]),
895
+ and returns an iterator of things, with the things with the best
896
+ matching tags first.
897
+
898
+ Example to select compatible wheel filenames:
899
+
900
+ >>> from packaging import tags
901
+ >>> from packaging.utils import parse_wheel_filename
902
+ >>> selector = tags.create_compatible_tags_selector(tags.sys_tags())
903
+ >>> filenames = ["foo-1.0-py3-none-any.whl", "foo-1.0-py2-none-any.whl"]
904
+ >>> list(selector([
905
+ ... (filename, parse_wheel_filename(filename)[-1]) for filename in filenames
906
+ ... ]))
907
+ ['foo-1.0-py3-none-any.whl']
908
+
909
+ .. versionadded:: 26.1
910
+ """
911
+ tag_ranks: dict[Tag, int] = {}
912
+ for rank, tag in enumerate(tags):
913
+ tag_ranks.setdefault(tag, rank) # ignore duplicate tags, keep first
914
+ supported_tags = tag_ranks.keys()
915
+
916
+ def selector(
917
+ tagged_things: Iterable[tuple[_T, AbstractSet[Tag]]],
918
+ ) -> Iterator[_T]:
919
+ ranked_things: list[tuple[_T, int]] = []
920
+ for thing, thing_tags in tagged_things:
921
+ supported_thing_tags = thing_tags & supported_tags
922
+ if supported_thing_tags:
923
+ thing_rank = min(tag_ranks[t] for t in supported_thing_tags)
924
+ ranked_things.append((thing, thing_rank))
925
+ return iter(
926
+ thing for thing, _ in sorted(ranked_things, key=operator.itemgetter(1))
927
+ )
928
+
929
+ return selector