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,327 @@
|
|
|
1
|
+
"""Predict which wheel a ``(python_version, platform_id)`` tuple would install.
|
|
2
|
+
|
|
3
|
+
Universal resolution needs the install-time wheel selection answer
|
|
4
|
+
without a live interpreter, so the tag set is computed from
|
|
5
|
+
:class:`PlatformSpec` directly. CPython tags come from
|
|
6
|
+
``packaging.tags.cpython_tags``, interpreter-agnostic tags from
|
|
7
|
+
``compatible_tags``, macOS from ``mac_platforms``, and manylinux /
|
|
8
|
+
musllinux are expanded from a declared glibc / musl floor. A wheel
|
|
9
|
+
matches the tuple iff its parsed tags share a member with the
|
|
10
|
+
tuple's compatible-tag set.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
from functools import cache, lru_cache
|
|
17
|
+
from typing import TYPE_CHECKING
|
|
18
|
+
|
|
19
|
+
from .._vendor.packaging import tags as ptags
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
from collections.abc import Iterable
|
|
23
|
+
|
|
24
|
+
from nab_index.client import WheelFile
|
|
25
|
+
|
|
26
|
+
from .._vendor.packaging.tags import Tag
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# PEP 427: a wheel filename has at least 5 dash-separated segments
|
|
30
|
+
# (name-version-pythontag-abitag-platformtag.whl), or 6 with a build tag.
|
|
31
|
+
__all__ = [
|
|
32
|
+
"PlatformSpec",
|
|
33
|
+
"compatible_tags_for_tuple",
|
|
34
|
+
"select_wheel_for_tuple",
|
|
35
|
+
"wheel_compatible_with_tuple",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
_MIN_WHEEL_FILENAME_PARTS = 5
|
|
40
|
+
|
|
41
|
+
# Default manylinux floor: glibc 2.17 (the manylinux2014 generation,
|
|
42
|
+
# adopted by every mainstream distro since CentOS 7 / Ubuntu 14.04).
|
|
43
|
+
# A tighter floor reduces accepted wheels; a looser floor accepts
|
|
44
|
+
# wheels that may not run on older glibc.
|
|
45
|
+
_DEFAULT_MANYLINUX_FLOOR = (2, 17)
|
|
46
|
+
# Default musllinux floor: musl 1.2 (adopted by Alpine 3.13+, 2021+).
|
|
47
|
+
_DEFAULT_MUSLLINUX_FLOOR = (1, 2)
|
|
48
|
+
# Default macOS minimum: 11 (Big Sur, 2020+). arm64 was introduced
|
|
49
|
+
# at 11.0; using 10.x for arm64 has no compatible wheels.
|
|
50
|
+
_DEFAULT_MACOS_MIN = (11, 0)
|
|
51
|
+
# Default macOS minimum for x86_64 builds. 10.13 was the last with
|
|
52
|
+
# wide wheel coverage; newer macOS x86_64 builds rarely declare
|
|
53
|
+
# below 10.13.
|
|
54
|
+
_DEFAULT_MACOS_X86_64_MIN = (10, 13)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass(frozen=True)
|
|
58
|
+
class PlatformSpec:
|
|
59
|
+
"""Concrete tag floors for one matrix platform_id.
|
|
60
|
+
|
|
61
|
+
Users can override the per-platform floors when their
|
|
62
|
+
deployment target requires it. The defaults are deliberately
|
|
63
|
+
permissive (manylinux 2.17, musl 1.2, macOS 11) so most real
|
|
64
|
+
deployments work out of the box.
|
|
65
|
+
|
|
66
|
+
``platform_release`` and ``platform_version`` set the
|
|
67
|
+
corresponding PEP 508 marker values on this platform's tuples
|
|
68
|
+
(hole 1.3 plug). When unset, both default to the empty string,
|
|
69
|
+
which makes any kernel-version-conditioned marker
|
|
70
|
+
(``platform_release >= "5.10"``) evaluate False (the safe
|
|
71
|
+
direction: drop the gated dep) but a silent failure if the
|
|
72
|
+
target machine actually has that kernel. Users who declare a
|
|
73
|
+
minimum target kernel get the gated deps included.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
platform_id: str
|
|
77
|
+
manylinux_floor: tuple[int, int] = _DEFAULT_MANYLINUX_FLOOR
|
|
78
|
+
musllinux_floor: tuple[int, int] = _DEFAULT_MUSLLINUX_FLOOR
|
|
79
|
+
macos_min: tuple[int, int] | None = None # arch-dependent default
|
|
80
|
+
platform_release: str = ""
|
|
81
|
+
platform_version: str = ""
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def arch(self) -> str:
|
|
85
|
+
"""The architecture suffix used in platform tags."""
|
|
86
|
+
return _PLATFORM_ARCH[self.platform_id]
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# Map our matrix platform_ids to (kind, arch). Kind is one of
|
|
90
|
+
# "linux", "macos", "windows". Used for tag generation.
|
|
91
|
+
_PLATFORM_ARCH: dict[str, str] = {
|
|
92
|
+
"linux_x86_64": "x86_64",
|
|
93
|
+
"linux_aarch64": "aarch64",
|
|
94
|
+
"macos_arm64": "arm64",
|
|
95
|
+
"macos_x86_64": "x86_64",
|
|
96
|
+
"windows_amd64": "amd64",
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
_PLATFORM_KIND: dict[str, str] = {
|
|
100
|
+
"linux_x86_64": "linux",
|
|
101
|
+
"linux_aarch64": "linux",
|
|
102
|
+
"macos_arm64": "macos",
|
|
103
|
+
"macos_x86_64": "macos",
|
|
104
|
+
"windows_amd64": "windows",
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _linux_platform_tags(
|
|
109
|
+
arch: str,
|
|
110
|
+
*,
|
|
111
|
+
manylinux_floor: tuple[int, int],
|
|
112
|
+
musllinux_floor: tuple[int, int],
|
|
113
|
+
) -> list[str]:
|
|
114
|
+
"""Generate manylinux + musllinux + plain linux tags for an arch.
|
|
115
|
+
|
|
116
|
+
Returns the tag list in install-preference order: most-specific
|
|
117
|
+
(highest glibc/musl version) first. Accepts every minor version
|
|
118
|
+
at or below the declared floor; this is the spec-compliant
|
|
119
|
+
interpretation of "manylinux_X_Y means glibc X.Y or older".
|
|
120
|
+
|
|
121
|
+
Note: PEP 600 says installers prefer wheels with the *highest*
|
|
122
|
+
glibc among compatible ones. Our use case is "decide which
|
|
123
|
+
wheel a tuple would install"; the tag list ordering matches
|
|
124
|
+
that preference.
|
|
125
|
+
"""
|
|
126
|
+
# manylinux_X_Y: PEP 600 form. We accept any minor at or below
|
|
127
|
+
# the floor (a wheel built for glibc 2.5 runs on a system with
|
|
128
|
+
# glibc 2.17; a wheel built for glibc 2.34 does not). Iterate
|
|
129
|
+
# high-to-low for preference order.
|
|
130
|
+
major, minor = manylinux_floor
|
|
131
|
+
out = [f"manylinux_{major}_{m}_{arch}" for m in range(minor, -1, -1)]
|
|
132
|
+
# Legacy aliases (PEPs 513/571/599). These map to specific
|
|
133
|
+
# glibc versions: manylinux1=2.5, manylinux2010=2.12,
|
|
134
|
+
# manylinux2014=2.17. We include them when they're <= floor.
|
|
135
|
+
legacy_aliases = [
|
|
136
|
+
("manylinux1", (2, 5)),
|
|
137
|
+
("manylinux2010", (2, 12)),
|
|
138
|
+
("manylinux2014", (2, 17)),
|
|
139
|
+
]
|
|
140
|
+
out.extend(
|
|
141
|
+
f"{name}_{arch}" for name, lver in legacy_aliases if lver <= manylinux_floor
|
|
142
|
+
)
|
|
143
|
+
# musllinux_X_Y: PEP 656 form. Same accept-at-or-below rule.
|
|
144
|
+
mu_major, mu_minor = musllinux_floor
|
|
145
|
+
out.extend(f"musllinux_{mu_major}_{m}_{arch}" for m in range(mu_minor, -1, -1))
|
|
146
|
+
# Plain linux_<arch>: the most generic Linux tag. Most installers
|
|
147
|
+
# accept this only when no manylinux/musllinux wheel is present.
|
|
148
|
+
out.append(f"linux_{arch}")
|
|
149
|
+
return out
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _platform_tags_for_spec(spec: PlatformSpec) -> list[str]:
|
|
153
|
+
"""Build the platform-tag list for ``spec`` in preference order."""
|
|
154
|
+
kind = _PLATFORM_KIND[spec.platform_id]
|
|
155
|
+
arch = spec.arch
|
|
156
|
+
|
|
157
|
+
if kind == "linux":
|
|
158
|
+
return _linux_platform_tags(
|
|
159
|
+
arch,
|
|
160
|
+
manylinux_floor=spec.manylinux_floor,
|
|
161
|
+
musllinux_floor=spec.musllinux_floor,
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
if kind == "macos":
|
|
165
|
+
macos_min = spec.macos_min
|
|
166
|
+
if macos_min is None:
|
|
167
|
+
macos_min = (
|
|
168
|
+
_DEFAULT_MACOS_MIN if arch == "arm64" else _DEFAULT_MACOS_X86_64_MIN
|
|
169
|
+
)
|
|
170
|
+
# mac_platforms treats the declared OS as a max and yields older too.
|
|
171
|
+
return list(ptags.mac_platforms(version=macos_min, arch=arch))
|
|
172
|
+
|
|
173
|
+
if kind == "windows":
|
|
174
|
+
return [f"win_{arch}"]
|
|
175
|
+
|
|
176
|
+
# Unreachable; PlatformSpec construction validates.
|
|
177
|
+
msg = f"Unknown platform kind: {kind}" # pragma: no cover
|
|
178
|
+
raise ValueError(msg) # pragma: no cover
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
@cache
|
|
182
|
+
def compatible_tags_for_tuple(
|
|
183
|
+
*,
|
|
184
|
+
python_version: str,
|
|
185
|
+
spec: PlatformSpec,
|
|
186
|
+
) -> frozenset[Tag]:
|
|
187
|
+
"""Return the full set of tags ``(python_version, spec)`` accepts.
|
|
188
|
+
|
|
189
|
+
Combines:
|
|
190
|
+
|
|
191
|
+
1. CPython-specific tags via ``packaging.tags.cpython_tags``
|
|
192
|
+
(cpXY-cpXY, cpXY-abi3 forward-compat, cpXY-none).
|
|
193
|
+
2. Interpreter-agnostic tags via ``packaging.tags.compatible_tags``
|
|
194
|
+
(pyXY-none-any, py3-none-any, etc.).
|
|
195
|
+
|
|
196
|
+
The platform list is computed by :func:`_platform_tags_for_spec`.
|
|
197
|
+
Cached on ``(python_version, spec)``: both inputs are immutable
|
|
198
|
+
(str, frozen dataclass) and the resulting set is identical across
|
|
199
|
+
every wheel-compatibility check for the same tuple, so the cache
|
|
200
|
+
skips rebuilding the same :class:`Tag` set per call.
|
|
201
|
+
"""
|
|
202
|
+
major, minor = (int(p) for p in python_version.split("."))
|
|
203
|
+
py_version = (major, minor)
|
|
204
|
+
abi = f"cp{major}{minor}"
|
|
205
|
+
platforms = _platform_tags_for_spec(spec)
|
|
206
|
+
out: set[Tag] = set()
|
|
207
|
+
out.update(
|
|
208
|
+
ptags.cpython_tags(python_version=py_version, abis=[abi], platforms=platforms)
|
|
209
|
+
)
|
|
210
|
+
out.update(
|
|
211
|
+
ptags.compatible_tags(
|
|
212
|
+
python_version=py_version, interpreter=abi, platforms=platforms
|
|
213
|
+
)
|
|
214
|
+
)
|
|
215
|
+
return frozenset(out)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
@lru_cache(maxsize=4096)
|
|
219
|
+
def _intern_tag(tag: Tag) -> Tag:
|
|
220
|
+
"""Return a shared :class:`Tag` for ``tag``.
|
|
221
|
+
|
|
222
|
+
``packaging.tags.parse_tag`` constructs fresh :class:`Tag` instances
|
|
223
|
+
on every call. The set of distinct (interpreter, abi, platform)
|
|
224
|
+
triples in a single PyPI scan is small compared with the wheels
|
|
225
|
+
visited, so sharing the canonical instance collapses the duplicates.
|
|
226
|
+
``Tag`` is immutable (``__slots__``) so the shared instance is safe.
|
|
227
|
+
"""
|
|
228
|
+
return tag
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
@lru_cache(maxsize=8192)
|
|
232
|
+
def _parse_tag_str(tag_str: str) -> frozenset[Tag] | None:
|
|
233
|
+
"""Cache ``parse_tag`` keyed on the wheel's ``python-abi-platform``.
|
|
234
|
+
|
|
235
|
+
Many distinct wheel filenames share the same tag suffix
|
|
236
|
+
(e.g. ``cp310-cp310-manylinux2014_x86_64``), so caching by tag
|
|
237
|
+
string deduplicates more aggressively than caching by filename.
|
|
238
|
+
Returns ``None`` for unparseable input.
|
|
239
|
+
"""
|
|
240
|
+
try:
|
|
241
|
+
raw = ptags.parse_tag(tag_str)
|
|
242
|
+
except Exception: # noqa: BLE001 - never trust upstream parser
|
|
243
|
+
return None
|
|
244
|
+
return frozenset(_intern_tag(t) for t in raw)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def wheel_tag_set(filename: str) -> frozenset[Tag] | None:
|
|
248
|
+
"""Parse a wheel filename into the set of tags it advertises.
|
|
249
|
+
|
|
250
|
+
Per PEP 427 the filename's last three dash-separated segments
|
|
251
|
+
are ``python-abi-platform``; per PEP 425 each can be a
|
|
252
|
+
dot-separated compressed set. Returns ``None`` for a non-wheel
|
|
253
|
+
filename or one with too few segments. The expensive work
|
|
254
|
+
(``parse_tag`` + Tag interning) lives in :func:`_parse_tag_str`,
|
|
255
|
+
which is cached on the suffix so wheels that share it
|
|
256
|
+
short-circuit.
|
|
257
|
+
"""
|
|
258
|
+
if not filename.endswith(".whl"):
|
|
259
|
+
return None
|
|
260
|
+
stem = filename[:-4]
|
|
261
|
+
parts = stem.split("-")
|
|
262
|
+
# PEP 427: filename has 5 segments (no build tag) or 6 (with build).
|
|
263
|
+
if len(parts) < _MIN_WHEEL_FILENAME_PARTS:
|
|
264
|
+
return None
|
|
265
|
+
# The last three dash-separated segments are python-abi-platform.
|
|
266
|
+
return _parse_tag_str("-".join(parts[-3:]))
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def wheel_compatible_with_tuple(
|
|
270
|
+
wheel: WheelFile,
|
|
271
|
+
*,
|
|
272
|
+
python_version: str,
|
|
273
|
+
spec: PlatformSpec,
|
|
274
|
+
) -> bool:
|
|
275
|
+
"""Return True iff ``wheel`` is a candidate for the given tuple."""
|
|
276
|
+
wheel_tags = wheel_tag_set(wheel.filename)
|
|
277
|
+
if wheel_tags is None:
|
|
278
|
+
return False
|
|
279
|
+
compat = compatible_tags_for_tuple(python_version=python_version, spec=spec)
|
|
280
|
+
# ``frozenset.isdisjoint`` is a C-level builtin that beats the
|
|
281
|
+
# Python ``any(t in compat for t in wheel_tags)`` generator on
|
|
282
|
+
# the per-wheel hot loop.
|
|
283
|
+
return not wheel_tags.isdisjoint(compat)
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def select_wheel_for_tuple(
|
|
287
|
+
wheels: Iterable[WheelFile],
|
|
288
|
+
*,
|
|
289
|
+
python_version: str,
|
|
290
|
+
spec: PlatformSpec,
|
|
291
|
+
) -> WheelFile | None:
|
|
292
|
+
"""Pick the most-specific compatible wheel for the tuple, or None.
|
|
293
|
+
|
|
294
|
+
Implements PEP 425 preference: wheels matching earlier
|
|
295
|
+
(more-specific) tags in ``compatible_tags_for_tuple`` win over
|
|
296
|
+
those matching later (more-generic) tags. Within the same tag
|
|
297
|
+
rank, the first wheel in input order wins.
|
|
298
|
+
"""
|
|
299
|
+
compat_list = list(_compatible_tags_in_order(python_version, spec))
|
|
300
|
+
rank: dict[Tag, int] = {tag: i for i, tag in enumerate(compat_list)}
|
|
301
|
+
|
|
302
|
+
best: tuple[int, WheelFile] | None = None
|
|
303
|
+
for wheel in wheels:
|
|
304
|
+
wheel_tags = wheel_tag_set(wheel.filename)
|
|
305
|
+
if not wheel_tags:
|
|
306
|
+
continue
|
|
307
|
+
# Lowest rank index wins (most-specific tag).
|
|
308
|
+
wheel_rank = min((rank[t] for t in wheel_tags if t in rank), default=None)
|
|
309
|
+
if wheel_rank is None:
|
|
310
|
+
continue
|
|
311
|
+
if best is None or wheel_rank < best[0]:
|
|
312
|
+
best = (wheel_rank, wheel)
|
|
313
|
+
return best[1] if best is not None else None
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def _compatible_tags_in_order(python_version: str, spec: PlatformSpec) -> Iterable[Tag]:
|
|
317
|
+
"""Yield compatible tags in install preference order."""
|
|
318
|
+
major, minor = (int(p) for p in python_version.split("."))
|
|
319
|
+
py_version = (major, minor)
|
|
320
|
+
abi = f"cp{major}{minor}"
|
|
321
|
+
platforms = _platform_tags_for_spec(spec)
|
|
322
|
+
yield from ptags.cpython_tags(
|
|
323
|
+
python_version=py_version, abis=[abi], platforms=platforms
|
|
324
|
+
)
|
|
325
|
+
yield from ptags.compatible_tags(
|
|
326
|
+
python_version=py_version, interpreter=abi, platforms=platforms
|
|
327
|
+
)
|
nab_python/workspace.py
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"""Workspace discovery for member ``pyproject.toml`` files.
|
|
2
|
+
|
|
3
|
+
A "workspace" here is the ``[tool.nab.workspace]`` table on a parent
|
|
4
|
+
``pyproject.toml``. When ``nab lock`` is invoked against a member, this
|
|
5
|
+
module walks up to the root, reads the members, and synthesises a
|
|
6
|
+
:class:`~nab_python.provider.LocalSource` per member. The provider then
|
|
7
|
+
prefers those local sources over PyPI by canonical name, so a member
|
|
8
|
+
package resolves against its in-tree source instead of being fetched
|
|
9
|
+
from the index.
|
|
10
|
+
|
|
11
|
+
Members are listed literally; globs are refused with an error. Users
|
|
12
|
+
coming from other tools that allow globs get a clear migration
|
|
13
|
+
message.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import logging
|
|
19
|
+
from dataclasses import dataclass
|
|
20
|
+
from typing import TYPE_CHECKING
|
|
21
|
+
|
|
22
|
+
import tomli
|
|
23
|
+
|
|
24
|
+
from ._vendor.packaging.utils import canonicalize_name
|
|
25
|
+
from .provider import BuildPolicy, LocalSource
|
|
26
|
+
|
|
27
|
+
if TYPE_CHECKING:
|
|
28
|
+
from collections.abc import Iterable
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
__all__ = [
|
|
33
|
+
"WorkspaceConfig",
|
|
34
|
+
"WorkspaceDiscoveryError",
|
|
35
|
+
"auto_promote_build_policy_for_workspace",
|
|
36
|
+
"discover_workspace_root",
|
|
37
|
+
"merge_workspace_local_sources",
|
|
38
|
+
"read_workspace_members",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
logger = logging.getLogger(__name__)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
_PERMISSIVENESS = {
|
|
46
|
+
BuildPolicy.NEVER: 0,
|
|
47
|
+
BuildPolicy.BUILD_LOCAL: 1,
|
|
48
|
+
BuildPolicy.BUILD_REMOTE: 2,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class WorkspaceDiscoveryError(ValueError):
|
|
53
|
+
"""Raised when a workspace member or root is structurally invalid."""
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass(frozen=True, slots=True)
|
|
57
|
+
class WorkspaceConfig:
|
|
58
|
+
"""Parsed ``[tool.nab.workspace]`` table.
|
|
59
|
+
|
|
60
|
+
``members`` is the literal list of paths declared at the workspace
|
|
61
|
+
root. No globs, no path resolution; those happen later in
|
|
62
|
+
:func:`read_workspace_members`.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
members: tuple[str, ...]
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def discover_workspace_root(member_pyproject: Path) -> Path | None:
|
|
69
|
+
"""Return the workspace root pyproject for ``member_pyproject``.
|
|
70
|
+
|
|
71
|
+
Walks from ``member_pyproject``'s directory upwards looking for the
|
|
72
|
+
first ``pyproject.toml`` whose ``[tool.nab.workspace]`` table is
|
|
73
|
+
present. Returns that pyproject's path, or ``None`` when no such
|
|
74
|
+
ancestor (or self) exists.
|
|
75
|
+
|
|
76
|
+
The input ``member_pyproject`` is itself considered: a user invoking
|
|
77
|
+
``nab lock`` on a workspace root sees discovery activate.
|
|
78
|
+
Filesystem-level errors and TOML parse errors during the walk are
|
|
79
|
+
swallowed: a malformed sibling pyproject should not prevent
|
|
80
|
+
discovery from finding a valid root above it.
|
|
81
|
+
"""
|
|
82
|
+
start_dir = member_pyproject.resolve().parent
|
|
83
|
+
for parent in (start_dir, *start_dir.parents):
|
|
84
|
+
candidate = parent / "pyproject.toml"
|
|
85
|
+
if not candidate.is_file():
|
|
86
|
+
continue
|
|
87
|
+
try:
|
|
88
|
+
with candidate.open("rb") as f:
|
|
89
|
+
data = tomli.load(f)
|
|
90
|
+
except (OSError, tomli.TOMLDecodeError):
|
|
91
|
+
continue
|
|
92
|
+
if "workspace" in data.get("tool", {}).get("nab", {}):
|
|
93
|
+
return candidate
|
|
94
|
+
return None
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def read_workspace_members(root_pyproject: Path) -> tuple[LocalSource, ...]:
|
|
98
|
+
"""Synthesise :class:`LocalSource` entries from a workspace root.
|
|
99
|
+
|
|
100
|
+
Reads ``[tool.nab.workspace].members`` from ``root_pyproject``. Each
|
|
101
|
+
entry must be a literal path; any entry containing ``*``, ``?`` or
|
|
102
|
+
``[`` raises :class:`WorkspaceDiscoveryError` with a message naming
|
|
103
|
+
the offending entry. For every member directory the function opens
|
|
104
|
+
``<member>/pyproject.toml`` and requires ``[project].name``;
|
|
105
|
+
missing pyproject or missing name is a hard error.
|
|
106
|
+
|
|
107
|
+
Two members declaring the same canonical name raises
|
|
108
|
+
:class:`WorkspaceDiscoveryError`. The returned tuple preserves
|
|
109
|
+
declaration order, which makes ``nab lock`` output stable when the
|
|
110
|
+
list of members is itself stable.
|
|
111
|
+
"""
|
|
112
|
+
with root_pyproject.open("rb") as f:
|
|
113
|
+
root_data = tomli.load(f)
|
|
114
|
+
raw_workspace = root_data.get("tool", {}).get("nab", {}).get("workspace")
|
|
115
|
+
if not isinstance(raw_workspace, dict):
|
|
116
|
+
msg = (
|
|
117
|
+
f"{root_pyproject}: [tool.nab.workspace] must be a table,"
|
|
118
|
+
f" got {type(raw_workspace).__name__}"
|
|
119
|
+
)
|
|
120
|
+
raise WorkspaceDiscoveryError(msg)
|
|
121
|
+
raw_members = raw_workspace.get("members")
|
|
122
|
+
if not isinstance(raw_members, list):
|
|
123
|
+
msg = (
|
|
124
|
+
f"{root_pyproject}: [tool.nab.workspace].members must be a list of"
|
|
125
|
+
f" strings, got {type(raw_members).__name__}"
|
|
126
|
+
)
|
|
127
|
+
raise WorkspaceDiscoveryError(msg)
|
|
128
|
+
|
|
129
|
+
sources: list[LocalSource] = []
|
|
130
|
+
seen: dict[str, str] = {}
|
|
131
|
+
for entry in raw_members:
|
|
132
|
+
if not isinstance(entry, str):
|
|
133
|
+
msg = (
|
|
134
|
+
f"{root_pyproject}: [tool.nab.workspace].members entries must be"
|
|
135
|
+
f" strings, got {type(entry).__name__}: {entry!r}"
|
|
136
|
+
)
|
|
137
|
+
raise WorkspaceDiscoveryError(msg)
|
|
138
|
+
if any(ch in entry for ch in "*?["):
|
|
139
|
+
msg = (
|
|
140
|
+
f"{root_pyproject}: globs in [tool.nab.workspace].members are not"
|
|
141
|
+
f" supported in nab; list members literally."
|
|
142
|
+
f" Offending entry: {entry!r}"
|
|
143
|
+
)
|
|
144
|
+
raise WorkspaceDiscoveryError(msg)
|
|
145
|
+
member_dir = (root_pyproject.parent / entry).resolve()
|
|
146
|
+
member_pyproject = member_dir / "pyproject.toml"
|
|
147
|
+
if not member_pyproject.is_file():
|
|
148
|
+
msg = (
|
|
149
|
+
f"{root_pyproject}: workspace member {entry!r} has no"
|
|
150
|
+
f" pyproject.toml at {member_pyproject}"
|
|
151
|
+
)
|
|
152
|
+
raise WorkspaceDiscoveryError(msg)
|
|
153
|
+
with member_pyproject.open("rb") as f:
|
|
154
|
+
member_data = tomli.load(f)
|
|
155
|
+
name = member_data.get("project", {}).get("name")
|
|
156
|
+
if not isinstance(name, str) or not name:
|
|
157
|
+
msg = (
|
|
158
|
+
f"{member_pyproject}: workspace member must declare"
|
|
159
|
+
f" [project].name (got {name!r})"
|
|
160
|
+
)
|
|
161
|
+
raise WorkspaceDiscoveryError(msg)
|
|
162
|
+
canonical = canonicalize_name(name)
|
|
163
|
+
if canonical in seen:
|
|
164
|
+
msg = (
|
|
165
|
+
f"{root_pyproject}: workspace members declare duplicate"
|
|
166
|
+
f" canonical name {canonical!r} via entries {seen[canonical]!r}"
|
|
167
|
+
f" and {entry!r}"
|
|
168
|
+
)
|
|
169
|
+
raise WorkspaceDiscoveryError(msg)
|
|
170
|
+
seen[canonical] = entry
|
|
171
|
+
sources.append(LocalSource(name=name, path=str(member_dir)))
|
|
172
|
+
return tuple(sources)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def merge_workspace_local_sources(
|
|
176
|
+
explicit: Iterable[LocalSource],
|
|
177
|
+
discovered: Iterable[LocalSource],
|
|
178
|
+
) -> tuple[LocalSource, ...]:
|
|
179
|
+
"""Combine explicit and workspace-discovered local sources.
|
|
180
|
+
|
|
181
|
+
Explicit ``[[tool.nab.local-sources]]`` entries always win. When a
|
|
182
|
+
discovered member shares a canonical name with an explicit entry,
|
|
183
|
+
the discovered entry is dropped and one INFO log line records the
|
|
184
|
+
shadow so the user can audit what was overridden. The order is
|
|
185
|
+
explicit entries first, then unshadowed discovered entries in the
|
|
186
|
+
order they were declared.
|
|
187
|
+
"""
|
|
188
|
+
explicit_tuple = tuple(explicit)
|
|
189
|
+
explicit_names = {canonicalize_name(s.name) for s in explicit_tuple}
|
|
190
|
+
out = list(explicit_tuple)
|
|
191
|
+
for src in discovered:
|
|
192
|
+
if canonicalize_name(src.name) in explicit_names:
|
|
193
|
+
logger.info(
|
|
194
|
+
"workspace member %r at %s shadowed by explicit"
|
|
195
|
+
" [[tool.nab.local-sources]]",
|
|
196
|
+
src.name,
|
|
197
|
+
src.path,
|
|
198
|
+
)
|
|
199
|
+
continue
|
|
200
|
+
out.append(src)
|
|
201
|
+
return tuple(out)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def auto_promote_build_policy_for_workspace(current: BuildPolicy) -> BuildPolicy:
|
|
205
|
+
"""Floor ``current`` at :attr:`BuildPolicy.BUILD_LOCAL`.
|
|
206
|
+
|
|
207
|
+
Workspace members frequently use ``dynamic = ["version"]`` (hatch's
|
|
208
|
+
pattern) and other dynamic fields, which require the local backend
|
|
209
|
+
path. The user's setting wins when it is already at least as
|
|
210
|
+
permissive as :attr:`BuildPolicy.BUILD_LOCAL`.
|
|
211
|
+
"""
|
|
212
|
+
if _PERMISSIVENESS[current] < _PERMISSIVENESS[BuildPolicy.BUILD_LOCAL]:
|
|
213
|
+
return BuildPolicy.BUILD_LOCAL
|
|
214
|
+
return current
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nab-python
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Index-backed provider, lockfile emitter, and downloader for nab
|
|
5
|
+
Project-URL: Homepage, https://github.com/notatallshaw/nab
|
|
6
|
+
Project-URL: Documentation, https://nab.readthedocs.io/
|
|
7
|
+
Project-URL: Issues, https://github.com/notatallshaw/nab/issues
|
|
8
|
+
Project-URL: Source, https://github.com/notatallshaw/nab
|
|
9
|
+
Project-URL: Changelog, https://github.com/notatallshaw/nab/blob/main/CHANGELOG.md
|
|
10
|
+
Author-email: Damian Shaw <damian.peter.shaw@gmail.com>
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Typing :: Typed
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Requires-Dist: build>=1.2
|
|
22
|
+
Requires-Dist: installer>=0.7
|
|
23
|
+
Requires-Dist: nab-index==0.0.1
|
|
24
|
+
Requires-Dist: nab-resolver==0.0.1
|
|
25
|
+
Requires-Dist: pyproject-hooks>=1.2
|
|
26
|
+
Requires-Dist: tomli-w>=1.2
|
|
27
|
+
Requires-Dist: tomli>=2.0
|
|
28
|
+
Requires-Dist: typing-extensions>=4.6
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# nab-python
|
|
32
|
+
|
|
33
|
+
The Python package index provider that drives [`nab-resolver`](https://pypi.org/project/nab-resolver/)
|
|
34
|
+
for Python packages.
|
|
35
|
+
|
|
36
|
+
It owns all the mechanics of how the resolver needs to interact with standards
|
|
37
|
+
based Python package indexes and packages.
|
|
38
|
+
|
|
39
|
+
It implements build and distribution policies to allow the user to control
|
|
40
|
+
resolver and install behavior.
|
|
41
|
+
|
|
42
|
+
## When to use it
|
|
43
|
+
|
|
44
|
+
Use `nab-python` if you need to embed Python package resolution in
|
|
45
|
+
another tool and want the resolver, provider, and lockfile emitter
|
|
46
|
+
without the CLI.
|
|
47
|
+
|
|
48
|
+
The API is currently under rapid experimentation, use exact version
|
|
49
|
+
pinning.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
nab_python/__init__.py,sha256=tKRFGIR13xvNEftDBxxdczeKtcrvaqcOPiYbmdyFOOs,62
|
|
2
|
+
nab_python/_packaging_provider.py,sha256=-dxbaed8UoPtqxBnrOqxw2N_jPzHaQEMT1RJOoMHdRI,3435
|
|
3
|
+
nab_python/_vcs_admission.py,sha256=RSFnuZZW_-amCcfh3ERrCx_bBsLLuBcFX-Gt9dVy3fY,7021
|
|
4
|
+
nab_python/build_backend.py,sha256=T-KBp-VulIQATUjYvTt_hJronFceDSjvB-wZ3YHWPpw,6519
|
|
5
|
+
nab_python/config.py,sha256=UuLxZ3pn4pEQBKBRkMiWNV53xu-iswG81lQ6-mpdmLc,28988
|
|
6
|
+
nab_python/download.py,sha256=-wJGvpWqHSuQ2nK5cX-imC4pe6ypQDMYr8DLZy9ZauM,5398
|
|
7
|
+
nab_python/fetch.py,sha256=jB5G-7glsn_DycdrjFz0fek-iCWiBJYsN5KRvq6-3XQ,31760
|
|
8
|
+
nab_python/lockfile.py,sha256=OmO-owxmyWPUzwAgXhMgGphIv0hj1TeWW-hdF1sGqCs,7271
|
|
9
|
+
nab_python/metadata.py,sha256=hRVP7xt6CWl2h-Sxq1F1BSilOWj4yg0AxEnPyuRWPlw,4868
|
|
10
|
+
nab_python/provider.py,sha256=j9dutTZnuMMG8EFEYfUmXTiKb1Ge4FV7_sY-ET1gJKw,51256
|
|
11
|
+
nab_python/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
+
nab_python/requirements_file.py,sha256=t_7q1D-MUS_fH5-BLrXs4hB2txO5Iv-_b8vzmtINMsI,6327
|
|
13
|
+
nab_python/resolve.py,sha256=X_8nBI14ECNfCh3bkQyFMNEfSpv7Jfdfz3ITyoA5QdY,18275
|
|
14
|
+
nab_python/workspace.py,sha256=fKe4gyq09WkE1lSEXzkgYXeUKXcoW18SXOlYbVzY2hE,8006
|
|
15
|
+
nab_python/_build/__init__.py,sha256=7qW6Gk8Tr8fc3xon-yZ1zW5Z1oUU-LPRpgyucso1GNc,61
|
|
16
|
+
nab_python/_build/env.py,sha256=WxXMitoU2m526WFEi5NpNRgLOZkrrXuinan-uKY2uqQ,13790
|
|
17
|
+
nab_python/_build/errors.py,sha256=1--kmqmqsOYg7JOgYWysAiLhGJci8Fi929NvPT1ssXw,501
|
|
18
|
+
nab_python/_build/runner.py,sha256=VzCYGi6pIgd3d_06r5KXa0Ra5KXz-IPOrNkzCBynTcA,9356
|
|
19
|
+
nab_python/_lockfile/__init__.py,sha256=gfbgTLr4C66nEZeaI5G5DGGo6_s_qrB2uWAo3DjXC7Q,59
|
|
20
|
+
nab_python/_lockfile/builder.py,sha256=1YDp8yGXB1b-BrQ9NdoZYgjkEqs-qkQl_dp49BjhA8o,11531
|
|
21
|
+
nab_python/_lockfile/disjointness.py,sha256=fOg6Fdyk_ZuIlmqqzh3XjrRvLYPnJ2IhzJBboRln6b4,8495
|
|
22
|
+
nab_python/_lockfile/pylock.py,sha256=QKkbSz-Q5gJx-gl8hazhQetppPL-LRTaifFM9DtV5VU,10918
|
|
23
|
+
nab_python/_lockfile/requirements.py,sha256=etDNIM-2CIq3eWF5BMY1Uafb9V5DHEKoSSfmEZrBff4,4410
|
|
24
|
+
nab_python/_provider/__init__.py,sha256=FJwoSdUfrfhhXfaeJAIXQEWzY6KV4hKFNnvRkQVccfA,59
|
|
25
|
+
nab_python/_provider/build_remote.py,sha256=nqQ4rzXNkWxNouAyhhDC-rPZmtSsbPOaREoAPYm4Oz4,3411
|
|
26
|
+
nab_python/_provider/extras.py,sha256=SjJY33eXdF8MJ1KfIc2Hio3jxbpwaAwh7Rn4WZCEjMk,8293
|
|
27
|
+
nab_python/_provider/listing.py,sha256=SWYyPyjhIOTLbKi0B2jYxNC3KVtq5-3XFct_DHtp0gY,16512
|
|
28
|
+
nab_python/_provider/lookahead.py,sha256=xgX0f4h9dt2rdXZ_HmQmzXKyac2H4pDoaRMpVUqFotQ,5895
|
|
29
|
+
nab_python/_provider/metadata_resolver.py,sha256=rBWyDlq61DasSMkKp0ilh_S8VBNOJ63rUO63eG6T_Qc,16727
|
|
30
|
+
nab_python/_provider/priority.py,sha256=mYJHoMsoi71qTycyloWMmQ8ey00uNgBdIIkfXuar9yM,5975
|
|
31
|
+
nab_python/_provider/sources.py,sha256=qWnS13DKWtXLqCh9dJkN453fjhmO1o9IUurPIB0pu6w,7382
|
|
32
|
+
nab_python/_testing/__init__.py,sha256=WiMwVVwq6kg2mHFeM7JNA4-2tiXWVOPXq6YJd49vJQ0,44
|
|
33
|
+
nab_python/_testing/coordinator_fake.py,sha256=C2A70zT311tF4Lkj2UO8YYhHeiAYLMtOgm7HUMToWxM,8759
|
|
34
|
+
nab_python/_vendor/__init__.py,sha256=lSo_j79lhUNAEUBUs6RK5ZGyKM7NmjUhAXRmdGpRKmw,198
|
|
35
|
+
nab_python/_vendor/packaging/LICENSE,sha256=ytHvW9NA1z4HS6YU0m996spceUDD2MNIUuZcSQlobEg,197
|
|
36
|
+
nab_python/_vendor/packaging/LICENSE.APACHE,sha256=DVQuDIgE45qn836wDaWnYhSdxoLXgpRRKH4RuTjpRZQ,10174
|
|
37
|
+
nab_python/_vendor/packaging/LICENSE.BSD,sha256=tw5-m3QvHMb5SLNMFqo5_-zpQZY2S8iP8NIYDwAo-sU,1344
|
|
38
|
+
nab_python/_vendor/packaging/PROVENANCE.md,sha256=HZFDXXqPVjFCIxAK2QtT16jS-STyEOBulxGbhLokR10,2725
|
|
39
|
+
nab_python/_vendor/packaging/__init__.py,sha256=vwGIetD0qfqeO5zUYB3ECnspcLXICM3lnmTEqh55fJE,499
|
|
40
|
+
nab_python/_vendor/packaging/_elffile.py,sha256=-sKkptYqzYw2-x3QByJa5mB4rfPWu1pxkZHRx1WAFCY,3211
|
|
41
|
+
nab_python/_vendor/packaging/_manylinux.py,sha256=iF4g3wPHrxB7L7801xDd7Y9t8WvUnFPlhQXNT-GtV1I,9624
|
|
42
|
+
nab_python/_vendor/packaging/_musllinux.py,sha256=f12WeQCVwYA_yEcVgaYrrnXzp3meJTHD5LS_aUDn4us,2772
|
|
43
|
+
nab_python/_vendor/packaging/_parser.py,sha256=FSWddS3aQIyT4aMCTt5Wvs-O-iqVNvRFZSw7Q1FetLI,11712
|
|
44
|
+
nab_python/_vendor/packaging/_structures.py,sha256=60jRbF78p8z5MKnNd6cAprgOadCJHV0DlmUmRBqFZcs,1109
|
|
45
|
+
nab_python/_vendor/packaging/_tokenizer.py,sha256=VE_Lkm74DdF4as52JUfMRwKN81Lu_zSit6THmN2Ffq8,5456
|
|
46
|
+
nab_python/_vendor/packaging/dependency_groups.py,sha256=XZIAVFK9uHG4RCGprmJn3VInUWMesxha_kytJuMO9eY,10218
|
|
47
|
+
nab_python/_vendor/packaging/direct_url.py,sha256=eKmbDiPP1sLV4Mj_kCSZqqknrIyVO9Sr7JpF8KCjp4U,10917
|
|
48
|
+
nab_python/_vendor/packaging/errors.py,sha256=6hfEYXAf8v_IF65-lFadJOMIieBP2xIKtyEXjG1nGIs,2680
|
|
49
|
+
nab_python/_vendor/packaging/markers.py,sha256=bve3TML_xTL-QNpNWHrLiuTrDWmLROFUYjiC3cQKtoU,17470
|
|
50
|
+
nab_python/_vendor/packaging/metadata.py,sha256=crAh0E3GVGVqPlu6EdRFsaG-Y6UYznTUqjuGKRGPv6c,38770
|
|
51
|
+
nab_python/_vendor/packaging/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
52
|
+
nab_python/_vendor/packaging/pylock.py,sha256=o3eyzUZ6DkKU2S-H2IZKZKTD4yCd0Gtw2uq_6hVjNBQ,33967
|
|
53
|
+
nab_python/_vendor/packaging/ranges.py,sha256=wgoGlYIBARnpGhYXJBxyPCrUDVk3Nv0Uuz4qhaeSwWU,67300
|
|
54
|
+
nab_python/_vendor/packaging/requirements.py,sha256=ijGq_2NxRpeOIto1z_RRMR8ZIvJoKZicFKxN8H4krlw,4448
|
|
55
|
+
nab_python/_vendor/packaging/specifiers.py,sha256=yDSDr4prgiIkMzWkHgSGFWjBeNrMpZaTIecCXQk7HA4,41156
|
|
56
|
+
nab_python/_vendor/packaging/tags.py,sha256=xegoSm6VqawLUlgIAB0WQYtKHAySYwxABTbOcBiVw_0,34234
|
|
57
|
+
nab_python/_vendor/packaging/utils.py,sha256=2J6uAgU8vJDIY5Lvqj5lO54Qc2yxhkpgIdSqjTgtYho,9841
|
|
58
|
+
nab_python/_vendor/packaging/version.py,sha256=daDMk4YRlKNgSiQkkIlxfmpsj_qGiWhVghgMiGUyc2M,38358
|
|
59
|
+
nab_python/_vendor/packaging/licenses/__init__.py,sha256=_Jx0XRiD_58palsWnyLrLuh59ZpGCPIPXLKdZo9OJvQ,7293
|
|
60
|
+
nab_python/_vendor/packaging/licenses/_spdx.py,sha256=WW7DXiyg68up_YND_wpRYlr1SHhiV4FfJLQffghhMxQ,51122
|
|
61
|
+
nab_python/universal/__init__.py,sha256=eNFMbsK-jBNy1J7wAE702vI-LVXF_aaQQvX7G02ImUw,48
|
|
62
|
+
nab_python/universal/matrix.py,sha256=QOrU5oM3XhrWKlYRuwjdYau9o6DOhsngmr6VN8anJ3s,8215
|
|
63
|
+
nab_python/universal/provider.py,sha256=IrlMhoaCtDhjLyX7eu55Mq_lmsAEQhjzs3QLFUbC-lI,9238
|
|
64
|
+
nab_python/universal/reresolve.py,sha256=GFpkpR897XD6XLBISMNlNIqSHPeUZ7SjpL0USWUDKag,11548
|
|
65
|
+
nab_python/universal/resolve.py,sha256=zGhS3CPGINr1pZ3DBo6cZ4fj8SFmHOOOqzo-YVyuUug,18526
|
|
66
|
+
nab_python/universal/validate.py,sha256=t0lPQieaITnQdFYMqXB4o7LDrnL-K2EhuMSso1tlzo0,16269
|
|
67
|
+
nab_python/universal/wheel_selection.py,sha256=0qkwb3XgZdqOwVneO7Rfg7Rg02sNo6sUp9L1KszVHuc,12001
|
|
68
|
+
nab_python-0.0.1.dist-info/METADATA,sha256=ywrCnKV7Vcjv-X-SIxmnavN5pBLwaHSocrChbTst5po,1804
|
|
69
|
+
nab_python-0.0.1.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
70
|
+
nab_python-0.0.1.dist-info/licenses/LICENSE,sha256=oec5WE-g9eYDBVwbDbyKHR7_zK67vUXRoikkrsZRuJ8,1068
|
|
71
|
+
nab_python-0.0.1.dist-info/RECORD,,
|