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.
- nab_python/__init__.py +1 -0
- nab_python/_build/__init__.py +1 -0
- nab_python/_build/env.py +364 -0
- nab_python/_build/errors.py +17 -0
- nab_python/_build/runner.py +254 -0
- nab_python/_lockfile/__init__.py +1 -0
- nab_python/_lockfile/builder.py +339 -0
- nab_python/_lockfile/disjointness.py +207 -0
- nab_python/_lockfile/pylock.py +323 -0
- nab_python/_lockfile/requirements.py +121 -0
- nab_python/_packaging_provider.py +98 -0
- nab_python/_provider/__init__.py +1 -0
- nab_python/_provider/build_remote.py +95 -0
- nab_python/_provider/extras.py +231 -0
- nab_python/_provider/listing.py +442 -0
- nab_python/_provider/lookahead.py +156 -0
- nab_python/_provider/metadata_resolver.py +450 -0
- nab_python/_provider/priority.py +174 -0
- nab_python/_provider/sources.py +215 -0
- nab_python/_testing/__init__.py +1 -0
- nab_python/_testing/coordinator_fake.py +240 -0
- nab_python/_vcs_admission.py +209 -0
- nab_python/_vendor/__init__.py +6 -0
- nab_python/_vendor/packaging/LICENSE +3 -0
- nab_python/_vendor/packaging/LICENSE.APACHE +177 -0
- nab_python/_vendor/packaging/LICENSE.BSD +23 -0
- nab_python/_vendor/packaging/PROVENANCE.md +73 -0
- nab_python/_vendor/packaging/__init__.py +15 -0
- nab_python/_vendor/packaging/_elffile.py +108 -0
- nab_python/_vendor/packaging/_manylinux.py +265 -0
- nab_python/_vendor/packaging/_musllinux.py +88 -0
- nab_python/_vendor/packaging/_parser.py +394 -0
- nab_python/_vendor/packaging/_structures.py +33 -0
- nab_python/_vendor/packaging/_tokenizer.py +196 -0
- nab_python/_vendor/packaging/dependency_groups.py +302 -0
- nab_python/_vendor/packaging/direct_url.py +325 -0
- nab_python/_vendor/packaging/errors.py +94 -0
- nab_python/_vendor/packaging/licenses/__init__.py +186 -0
- nab_python/_vendor/packaging/licenses/_spdx.py +799 -0
- nab_python/_vendor/packaging/markers.py +506 -0
- nab_python/_vendor/packaging/metadata.py +964 -0
- nab_python/_vendor/packaging/py.typed +0 -0
- nab_python/_vendor/packaging/pylock.py +910 -0
- nab_python/_vendor/packaging/ranges.py +1803 -0
- nab_python/_vendor/packaging/requirements.py +132 -0
- nab_python/_vendor/packaging/specifiers.py +1141 -0
- nab_python/_vendor/packaging/tags.py +929 -0
- nab_python/_vendor/packaging/utils.py +296 -0
- nab_python/_vendor/packaging/version.py +1230 -0
- nab_python/build_backend.py +184 -0
- nab_python/config.py +805 -0
- nab_python/download.py +170 -0
- nab_python/fetch.py +827 -0
- nab_python/lockfile.py +238 -0
- nab_python/metadata.py +145 -0
- nab_python/provider.py +1235 -0
- nab_python/py.typed +0 -0
- nab_python/requirements_file.py +180 -0
- nab_python/resolve.py +497 -0
- nab_python/universal/__init__.py +1 -0
- nab_python/universal/matrix.py +235 -0
- nab_python/universal/provider.py +214 -0
- nab_python/universal/reresolve.py +310 -0
- nab_python/universal/resolve.py +508 -0
- nab_python/universal/validate.py +439 -0
- nab_python/universal/wheel_selection.py +327 -0
- nab_python/workspace.py +214 -0
- nab_python-0.0.1.dist-info/METADATA +49 -0
- nab_python-0.0.1.dist-info/RECORD +71 -0
- nab_python-0.0.1.dist-info/WHEEL +4 -0
- nab_python-0.0.1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"""Matrix expansion for user-declared universal resolution.
|
|
2
|
+
|
|
3
|
+
Expands a python version range plus a platform list into a finite
|
|
4
|
+
list of tuples, each a complete PEP 508 marker environment the
|
|
5
|
+
single-environment resolver can run against. Every PEP 508 variable
|
|
6
|
+
appearing in any marker on the dep graph must have a value in every
|
|
7
|
+
tuple. Wheel-tag and ``Requires-Python`` filtering happens elsewhere.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from typing import TYPE_CHECKING
|
|
14
|
+
|
|
15
|
+
from .._vendor.packaging.specifiers import SpecifierSet
|
|
16
|
+
from .._vendor.packaging.version import Version
|
|
17
|
+
from .wheel_selection import PlatformSpec
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from collections.abc import Iterable
|
|
21
|
+
|
|
22
|
+
# Common Python minor releases. An unrecognized minor declared in the
|
|
23
|
+
# user range raises.
|
|
24
|
+
__all__ = [
|
|
25
|
+
"Matrix",
|
|
26
|
+
"MatrixTuple",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
_KNOWN_PYTHON_MINORS: tuple[str, ...] = (
|
|
31
|
+
"3.8",
|
|
32
|
+
"3.9",
|
|
33
|
+
"3.10",
|
|
34
|
+
"3.11",
|
|
35
|
+
"3.12",
|
|
36
|
+
"3.13",
|
|
37
|
+
"3.14",
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# Defaults filled in for marker keys the user did not specify. These
|
|
42
|
+
# come from the most common PEP 508 environment values for the named
|
|
43
|
+
# OS/arch. They are used only for marker *evaluation*; the resolver
|
|
44
|
+
# does not consume them as constraints on its own.
|
|
45
|
+
_PLATFORM_DEFAULTS: dict[str, dict[str, str]] = {
|
|
46
|
+
"linux_x86_64": {
|
|
47
|
+
"sys_platform": "linux",
|
|
48
|
+
"platform_system": "Linux",
|
|
49
|
+
"platform_machine": "x86_64",
|
|
50
|
+
"os_name": "posix",
|
|
51
|
+
"platform_python_implementation": "CPython",
|
|
52
|
+
"implementation_name": "cpython",
|
|
53
|
+
},
|
|
54
|
+
"linux_aarch64": {
|
|
55
|
+
"sys_platform": "linux",
|
|
56
|
+
"platform_system": "Linux",
|
|
57
|
+
"platform_machine": "aarch64",
|
|
58
|
+
"os_name": "posix",
|
|
59
|
+
"platform_python_implementation": "CPython",
|
|
60
|
+
"implementation_name": "cpython",
|
|
61
|
+
},
|
|
62
|
+
"macos_arm64": {
|
|
63
|
+
"sys_platform": "darwin",
|
|
64
|
+
"platform_system": "Darwin",
|
|
65
|
+
"platform_machine": "arm64",
|
|
66
|
+
"os_name": "posix",
|
|
67
|
+
"platform_python_implementation": "CPython",
|
|
68
|
+
"implementation_name": "cpython",
|
|
69
|
+
},
|
|
70
|
+
"macos_x86_64": {
|
|
71
|
+
"sys_platform": "darwin",
|
|
72
|
+
"platform_system": "Darwin",
|
|
73
|
+
"platform_machine": "x86_64",
|
|
74
|
+
"os_name": "posix",
|
|
75
|
+
"platform_python_implementation": "CPython",
|
|
76
|
+
"implementation_name": "cpython",
|
|
77
|
+
},
|
|
78
|
+
"windows_amd64": {
|
|
79
|
+
"sys_platform": "win32",
|
|
80
|
+
"platform_system": "Windows",
|
|
81
|
+
"platform_machine": "AMD64",
|
|
82
|
+
"os_name": "nt",
|
|
83
|
+
"platform_python_implementation": "CPython",
|
|
84
|
+
"implementation_name": "cpython",
|
|
85
|
+
},
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@dataclass(frozen=True)
|
|
90
|
+
class MatrixTuple:
|
|
91
|
+
"""A single point in the universal-resolution matrix."""
|
|
92
|
+
|
|
93
|
+
python_version: str
|
|
94
|
+
platform_id: str
|
|
95
|
+
environment: dict[str, str] = field(hash=False, compare=False)
|
|
96
|
+
platform_spec: PlatformSpec = field(
|
|
97
|
+
hash=False,
|
|
98
|
+
compare=False,
|
|
99
|
+
default_factory=lambda: PlatformSpec("linux_x86_64"),
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
@property
|
|
103
|
+
def label(self) -> str:
|
|
104
|
+
"""Return a short human-readable id like ``py311-linux_x86_64``."""
|
|
105
|
+
return f"py{self.python_version.replace('.', '')}-{self.platform_id}"
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def marker_string(self) -> str:
|
|
109
|
+
"""Return a PEP 508 marker that selects this tuple.
|
|
110
|
+
|
|
111
|
+
Combines ``python_version``, ``sys_platform``, and
|
|
112
|
+
``platform_machine`` into a conjunction. Universal lockfiles
|
|
113
|
+
attach this to each per-tuple ``Package`` entry so an installer
|
|
114
|
+
on a matching environment picks the right pin.
|
|
115
|
+
"""
|
|
116
|
+
env = self.environment
|
|
117
|
+
return (
|
|
118
|
+
f'python_version == "{self.python_version}"'
|
|
119
|
+
f' and sys_platform == "{env["sys_platform"]}"'
|
|
120
|
+
f' and platform_machine == "{env["platform_machine"]}"'
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@dataclass
|
|
125
|
+
class Matrix:
|
|
126
|
+
"""User-declared universal resolution matrix.
|
|
127
|
+
|
|
128
|
+
``python_order``: ``"asc"`` (default, 3.9 first) or ``"desc"`` (3.13
|
|
129
|
+
first). Combined with cross-tuple alignment in the resolver this
|
|
130
|
+
selects between ``fork-strategy=fewest`` (asc: oldest-Python pin
|
|
131
|
+
propagates forward, the lowest common version wins) and
|
|
132
|
+
``fork-strategy=requires-python`` (desc: newest-Python pin
|
|
133
|
+
propagates, older Pythons diverge only when the new version is
|
|
134
|
+
incompatible).
|
|
135
|
+
|
|
136
|
+
``python_patches``: optional ``{minor: full_version}`` mapping that
|
|
137
|
+
sets the per-tuple ``python_full_version`` marker. Defaults to
|
|
138
|
+
``{minor}.0`` per tuple, which makes markers like
|
|
139
|
+
``python_full_version >= "3.11.4"`` evaluate to False on a 3.11
|
|
140
|
+
tuple. Users with deployments on later patch releases should
|
|
141
|
+
declare them here so marker evaluation matches reality. Example:
|
|
142
|
+
``python_patches={"3.11": "3.11.4", "3.12": "3.12.1"}``.
|
|
143
|
+
See ``universal_open_questions.md`` section 1.1 for the design
|
|
144
|
+
discussion.
|
|
145
|
+
"""
|
|
146
|
+
|
|
147
|
+
python: str
|
|
148
|
+
platforms: tuple[str | PlatformSpec, ...]
|
|
149
|
+
python_order: str = "asc"
|
|
150
|
+
python_patches: dict[str, str] | None = None
|
|
151
|
+
|
|
152
|
+
def expand(self) -> list[MatrixTuple]:
|
|
153
|
+
"""Expand the matrix into concrete tuples.
|
|
154
|
+
|
|
155
|
+
Validates inputs eagerly: unknown platform ids, an empty
|
|
156
|
+
python range, or an invalid ``python_order`` each raise a
|
|
157
|
+
``ValueError`` before any work happens.
|
|
158
|
+
|
|
159
|
+
``platforms`` accepts either bare platform-id strings (use
|
|
160
|
+
default tag floors) or :class:`PlatformSpec` instances for
|
|
161
|
+
per-platform glibc/musl/macOS overrides.
|
|
162
|
+
"""
|
|
163
|
+
if self.python_order not in {"asc", "desc"}:
|
|
164
|
+
msg = f"python_order must be 'asc' or 'desc'; got {self.python_order!r}"
|
|
165
|
+
raise ValueError(msg)
|
|
166
|
+
specs = [
|
|
167
|
+
p if isinstance(p, PlatformSpec) else PlatformSpec(p)
|
|
168
|
+
for p in self.platforms
|
|
169
|
+
]
|
|
170
|
+
unknown = [
|
|
171
|
+
s.platform_id for s in specs if s.platform_id not in _PLATFORM_DEFAULTS
|
|
172
|
+
]
|
|
173
|
+
if unknown:
|
|
174
|
+
msg = f"Unknown platform ids: {unknown!r}"
|
|
175
|
+
raise ValueError(msg)
|
|
176
|
+
py_versions = list(_pythons_in_range(self.python))
|
|
177
|
+
if not py_versions:
|
|
178
|
+
msg = f"No known Python versions match {self.python!r}"
|
|
179
|
+
raise ValueError(msg)
|
|
180
|
+
if self.python_order == "desc":
|
|
181
|
+
py_versions.reverse()
|
|
182
|
+
patches = self.python_patches or {}
|
|
183
|
+
return [
|
|
184
|
+
MatrixTuple(
|
|
185
|
+
python_version=py,
|
|
186
|
+
platform_id=spec.platform_id,
|
|
187
|
+
environment=_build_environment(py, spec, patches.get(py)),
|
|
188
|
+
platform_spec=spec,
|
|
189
|
+
)
|
|
190
|
+
for py in py_versions
|
|
191
|
+
for spec in specs
|
|
192
|
+
]
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def _build_environment(
|
|
196
|
+
python_version: str,
|
|
197
|
+
spec: PlatformSpec,
|
|
198
|
+
python_full_version: str | None = None,
|
|
199
|
+
) -> dict[str, str]:
|
|
200
|
+
"""Build a complete PEP 508 marker environment for one tuple.
|
|
201
|
+
|
|
202
|
+
Combines the platform's OS/arch defaults with python-axis values
|
|
203
|
+
derived from ``python_version``. ``platform_release`` and
|
|
204
|
+
``platform_version`` come from the :class:`PlatformSpec`; both
|
|
205
|
+
default to ``""`` so kernel-conditioned markers evaluate False
|
|
206
|
+
unless the user declares a target kernel/OS version.
|
|
207
|
+
|
|
208
|
+
``python_full_version`` overrides the default ``{minor}.0`` value.
|
|
209
|
+
Used when the matrix declares ``python_patches`` to make
|
|
210
|
+
patch-bound markers (``python_full_version >= "3.11.4"``)
|
|
211
|
+
evaluate against the user's actual deployment patch release.
|
|
212
|
+
"""
|
|
213
|
+
full = python_full_version or f"{python_version}.0"
|
|
214
|
+
return {
|
|
215
|
+
**_PLATFORM_DEFAULTS[spec.platform_id],
|
|
216
|
+
"python_version": python_version,
|
|
217
|
+
"python_full_version": full,
|
|
218
|
+
"implementation_version": full,
|
|
219
|
+
"platform_release": spec.platform_release,
|
|
220
|
+
"platform_version": spec.platform_version,
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def _pythons_in_range(spec: str) -> Iterable[str]:
|
|
225
|
+
"""Yield known Python minors that satisfy ``spec``.
|
|
226
|
+
|
|
227
|
+
``spec`` is a PEP 440 specifier set, e.g. ``">=3.11, <3.14"``.
|
|
228
|
+
"""
|
|
229
|
+
parsed = SpecifierSet(spec)
|
|
230
|
+
for minor in _KNOWN_PYTHON_MINORS:
|
|
231
|
+
# Use the .0 patch for membership testing so that a >=3.11
|
|
232
|
+
# specifier admits "3.11" via "3.11.0".
|
|
233
|
+
candidate = Version(f"{minor}.0")
|
|
234
|
+
if candidate in parsed:
|
|
235
|
+
yield minor
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"""Provider subclass that accepts a full PEP 508 marker environment.
|
|
2
|
+
|
|
3
|
+
The stock :class:`nab_python.provider.Provider` only accepts a
|
|
4
|
+
``python_version`` override and inherits everything else from
|
|
5
|
+
``default_environment()`` (the host environment). Universal resolution
|
|
6
|
+
needs to swap the entire environment per tuple, so this subclass
|
|
7
|
+
overlays a user-supplied dict on top.
|
|
8
|
+
|
|
9
|
+
Also adds a ``preferences`` knob: a ``{package_name: Version}`` dict
|
|
10
|
+
tried first when choosing a version. Used for cross-tuple alignment
|
|
11
|
+
("if tuple A picked numpy 2.2.6, ask tuple B to try 2.2.6 first").
|
|
12
|
+
|
|
13
|
+
Resolution strategy (``highest``/``lowest``/``lowest-direct``) is
|
|
14
|
+
inherited from :class:`Provider` and threaded through via the
|
|
15
|
+
parent's ``resolution_strategy`` and ``direct_packages`` kwargs.
|
|
16
|
+
|
|
17
|
+
When ``platform_spec`` is supplied, the provider also filters wheel
|
|
18
|
+
candidates by tag compatibility at resolve time (hole 2 in
|
|
19
|
+
``universal_open_questions.md``). Versions whose only wheels are
|
|
20
|
+
above the spec's manylinux/musllinux/macOS floor become unavailable
|
|
21
|
+
unless an sdist is present and ``build_policy`` allows building.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
from typing import TYPE_CHECKING
|
|
27
|
+
|
|
28
|
+
from nab_index.client import SdistFile, WheelFile
|
|
29
|
+
|
|
30
|
+
from .._vendor.packaging.markers import default_environment
|
|
31
|
+
from .._vendor.packaging.ranges import VersionRange
|
|
32
|
+
from .._vendor.packaging.utils import canonicalize_name
|
|
33
|
+
from ..provider import (
|
|
34
|
+
BuildPolicy,
|
|
35
|
+
DistPolicy,
|
|
36
|
+
ExtrasMode,
|
|
37
|
+
LocalSource,
|
|
38
|
+
Provider,
|
|
39
|
+
ResolutionStrategy,
|
|
40
|
+
VcsConfig,
|
|
41
|
+
VcsSource,
|
|
42
|
+
)
|
|
43
|
+
from .wheel_selection import compatible_tags_for_tuple, wheel_tag_set
|
|
44
|
+
|
|
45
|
+
__all__ = [
|
|
46
|
+
"DistFile",
|
|
47
|
+
"UniversalProvider",
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
DistFile = WheelFile | SdistFile
|
|
52
|
+
|
|
53
|
+
if TYPE_CHECKING:
|
|
54
|
+
from collections.abc import Mapping, Sequence
|
|
55
|
+
from datetime import datetime
|
|
56
|
+
from pathlib import Path
|
|
57
|
+
|
|
58
|
+
from nab_resolver.types import RangeProtocol
|
|
59
|
+
|
|
60
|
+
from .._vendor.packaging.version import Version
|
|
61
|
+
from ..config import NabProjectConfig
|
|
62
|
+
from ..fetch import FetchCoordinator
|
|
63
|
+
from .wheel_selection import PlatformSpec
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class UniversalProvider(Provider):
|
|
67
|
+
"""Provider with a user-supplied marker environment + preferences."""
|
|
68
|
+
|
|
69
|
+
def __init__( # noqa: PLR0913 - matches Provider signature
|
|
70
|
+
self,
|
|
71
|
+
coordinator: FetchCoordinator,
|
|
72
|
+
marker_environment: dict[str, str],
|
|
73
|
+
*,
|
|
74
|
+
root_requirements: dict[str, VersionRange] | None = None,
|
|
75
|
+
uploaded_prior_to: datetime | None = None,
|
|
76
|
+
uploaded_prior_to_overrides: Mapping[str, datetime | None] | None = None,
|
|
77
|
+
extras_mode: ExtrasMode = ExtrasMode.ERROR_USER,
|
|
78
|
+
root_extras: set[tuple[str, str]] | None = None,
|
|
79
|
+
dist_policy: DistPolicy = DistPolicy.WHEEL_OR_SDIST,
|
|
80
|
+
dist_policy_overrides: Mapping[str, DistPolicy] | None = None,
|
|
81
|
+
build_policy: BuildPolicy = BuildPolicy.BUILD_LOCAL,
|
|
82
|
+
build_policy_overrides: Mapping[str, BuildPolicy] | None = None,
|
|
83
|
+
vcs_config: VcsConfig | None = None,
|
|
84
|
+
vcs_cache_dir: Path | None = None,
|
|
85
|
+
local_sources: list[LocalSource] | None = None,
|
|
86
|
+
vcs_sources: list[VcsSource] | None = None,
|
|
87
|
+
build_config: NabProjectConfig | None = None,
|
|
88
|
+
preferences: dict[str, Version] | None = None,
|
|
89
|
+
resolution_strategy: ResolutionStrategy | str = ResolutionStrategy.HIGHEST,
|
|
90
|
+
direct_packages: frozenset[str] | None = None,
|
|
91
|
+
platform_spec: PlatformSpec | None = None,
|
|
92
|
+
) -> None:
|
|
93
|
+
"""Create a provider with overlay environment + uv-style preferences."""
|
|
94
|
+
if isinstance(resolution_strategy, str):
|
|
95
|
+
try:
|
|
96
|
+
resolution_strategy = ResolutionStrategy(resolution_strategy)
|
|
97
|
+
except ValueError as exc:
|
|
98
|
+
valid = sorted(s.value for s in ResolutionStrategy)
|
|
99
|
+
msg = (
|
|
100
|
+
f"resolution_strategy must be one of {valid!r};"
|
|
101
|
+
f" got {resolution_strategy!r}"
|
|
102
|
+
)
|
|
103
|
+
raise ValueError(msg) from exc
|
|
104
|
+
super().__init__(
|
|
105
|
+
coordinator,
|
|
106
|
+
python_version=marker_environment.get("python_version"),
|
|
107
|
+
root_requirements=root_requirements,
|
|
108
|
+
uploaded_prior_to=uploaded_prior_to,
|
|
109
|
+
uploaded_prior_to_overrides=uploaded_prior_to_overrides,
|
|
110
|
+
extras_mode=extras_mode,
|
|
111
|
+
root_extras=root_extras,
|
|
112
|
+
dist_policy=dist_policy,
|
|
113
|
+
dist_policy_overrides=dist_policy_overrides,
|
|
114
|
+
build_policy=build_policy,
|
|
115
|
+
build_policy_overrides=build_policy_overrides,
|
|
116
|
+
vcs_config=vcs_config,
|
|
117
|
+
local_sources=local_sources,
|
|
118
|
+
vcs_sources=vcs_sources,
|
|
119
|
+
vcs_cache_dir=vcs_cache_dir,
|
|
120
|
+
build_config=build_config,
|
|
121
|
+
resolution_strategy=resolution_strategy,
|
|
122
|
+
direct_packages=direct_packages,
|
|
123
|
+
)
|
|
124
|
+
merged: dict[str, str] = {
|
|
125
|
+
key: value
|
|
126
|
+
for key, value in default_environment().items()
|
|
127
|
+
if isinstance(value, str)
|
|
128
|
+
}
|
|
129
|
+
merged.update(marker_environment)
|
|
130
|
+
self.environment = merged
|
|
131
|
+
self.env_with_extra = dict(merged)
|
|
132
|
+
# Normalize preferences keys so lookup matches the provider's
|
|
133
|
+
# canonical naming scheme.
|
|
134
|
+
self._preferences: dict[str, Version] = {
|
|
135
|
+
canonicalize_name(k): v for k, v in (preferences or {}).items()
|
|
136
|
+
}
|
|
137
|
+
self._platform_spec = platform_spec
|
|
138
|
+
self._py_minor = marker_environment.get("python_version")
|
|
139
|
+
self.excluded_by_wheel_tags = 0
|
|
140
|
+
self.excluded_versions_no_compatible_wheel = 0
|
|
141
|
+
|
|
142
|
+
def choose_version(
|
|
143
|
+
self, package: str, version_range: RangeProtocol[Version]
|
|
144
|
+
) -> Version | None:
|
|
145
|
+
"""Pick a version under the configured strategy, honoring preferences."""
|
|
146
|
+
assert isinstance(version_range, VersionRange)
|
|
147
|
+
_, _, normalized = self.split_and_normalize(package)
|
|
148
|
+
|
|
149
|
+
# Preferences win when the version is still in range AND its metadata
|
|
150
|
+
# is extractable in this tuple; the look-ahead gate matters for
|
|
151
|
+
# cross-tuple alignment (a candidate that worked elsewhere may be
|
|
152
|
+
# tag-incompatible or unbuildable here).
|
|
153
|
+
preferred = self._preferences.get(normalized)
|
|
154
|
+
if preferred is not None:
|
|
155
|
+
all_versions = self.versions_only(normalized, self.fetch_versions(package))
|
|
156
|
+
if preferred in set(version_range.filter(all_versions)) and (
|
|
157
|
+
self.split_and_normalize(package)[1] is not None
|
|
158
|
+
or self._look_ahead_ok(normalized, preferred, check_decisions=True)
|
|
159
|
+
):
|
|
160
|
+
self._flush_pending_blocks()
|
|
161
|
+
return preferred
|
|
162
|
+
|
|
163
|
+
return super().choose_version(package, version_range)
|
|
164
|
+
|
|
165
|
+
def filter_distributions(
|
|
166
|
+
self, normalized: str, files: Sequence[WheelFile | SdistFile]
|
|
167
|
+
) -> list[tuple[Version, DistFile]]:
|
|
168
|
+
"""Filter parent's result by wheel-tag compatibility.
|
|
169
|
+
|
|
170
|
+
Hole 2 plug: a version is unavailable to the resolver if its
|
|
171
|
+
only wheels are tag-incompatible with this tuple's
|
|
172
|
+
``platform_spec``. Sdists keep the version alive at every
|
|
173
|
+
:class:`BuildPolicy` level because static PKG-INFO and the
|
|
174
|
+
bundled ``pyproject.toml`` fallback are read unconditionally;
|
|
175
|
+
``BUILD_LOCAL`` adds backend invocation on local checkouts and
|
|
176
|
+
``BUILD_REMOTE`` adds it on VCS clones and remote sdists. The
|
|
177
|
+
resolver's ``look_ahead_ok`` rejects per-version if metadata
|
|
178
|
+
extraction actually fails (e.g. dynamic deps with no static
|
|
179
|
+
fallback under :attr:`BuildPolicy.NEVER`).
|
|
180
|
+
|
|
181
|
+
When ``platform_spec`` is unset (legacy callers) the override
|
|
182
|
+
is a no-op.
|
|
183
|
+
"""
|
|
184
|
+
base = super().filter_distributions(normalized, files)
|
|
185
|
+
if self._platform_spec is None or self._py_minor is None:
|
|
186
|
+
return base
|
|
187
|
+
|
|
188
|
+
spec = self._platform_spec
|
|
189
|
+
py_minor = self._py_minor
|
|
190
|
+
# Look up the per-tuple compat tag set once outside the per-wheel
|
|
191
|
+
# loop and inline the membership check; this loop runs for every
|
|
192
|
+
# wheel of every package on every tuple, so the hoist matters on
|
|
193
|
+
# large workloads.
|
|
194
|
+
compat = compatible_tags_for_tuple(python_version=py_minor, spec=spec)
|
|
195
|
+
kept: list[tuple[Version, DistFile]] = []
|
|
196
|
+
versions_with_wheel: set[Version] = set()
|
|
197
|
+
versions_with_sdist: set[Version] = set()
|
|
198
|
+
for version, dist in base:
|
|
199
|
+
if isinstance(dist, WheelFile):
|
|
200
|
+
wheel_tags = wheel_tag_set(dist.filename)
|
|
201
|
+
if wheel_tags is not None and not wheel_tags.isdisjoint(compat):
|
|
202
|
+
kept.append((version, dist))
|
|
203
|
+
versions_with_wheel.add(version)
|
|
204
|
+
else:
|
|
205
|
+
self.excluded_by_wheel_tags += 1
|
|
206
|
+
else:
|
|
207
|
+
kept.append((version, dist))
|
|
208
|
+
versions_with_sdist.add(version)
|
|
209
|
+
|
|
210
|
+
build_allowed = self.build_policy != BuildPolicy.NEVER
|
|
211
|
+
usable = versions_with_wheel | (versions_with_sdist if build_allowed else set())
|
|
212
|
+
all_versions = {v for v, _ in base}
|
|
213
|
+
self.excluded_versions_no_compatible_wheel += len(all_versions) - len(usable)
|
|
214
|
+
return [pair for pair in kept if pair[0] in usable]
|