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,310 @@
1
+ """Re-resolve a tuple using its specific wheel's metadata.
2
+
3
+ When :mod:`validate` reports a ``divergent`` finding, the lock's pins
4
+ were chosen against the resolver's baseline metadata, not the
5
+ metadata of the wheel the tuple would actually install. This module
6
+ provides an opt-in second pass that re-resolves the affected tuple
7
+ with the wheel-specific metadata so the lock surfaces any new or
8
+ removed pins driven by the divergence.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from collections import defaultdict
14
+ from contextlib import contextmanager
15
+ from dataclasses import dataclass, field
16
+ from typing import TYPE_CHECKING
17
+
18
+ from .._vendor.packaging.utils import canonicalize_name
19
+ from ..provider import (
20
+ BuildPolicy,
21
+ DistPolicy,
22
+ LocalSource,
23
+ VcsConfig,
24
+ VcsSource,
25
+ )
26
+ from .matrix import Matrix as _Matrix
27
+ from .resolve import resolve_with_coordinator
28
+
29
+ if TYPE_CHECKING:
30
+ from collections.abc import Iterator, Mapping
31
+ from datetime import datetime
32
+ from pathlib import Path
33
+
34
+ from ..config import NabProjectConfig
35
+ from ..fetch import FetchCoordinator
36
+ from .matrix import MatrixTuple
37
+ from .resolve import UniversalResult
38
+ from .validate import PinValidation, ValidationReport
39
+
40
+
41
+ __all__ = [
42
+ "ReresolveDiff",
43
+ "reresolve_divergent_tuples",
44
+ ]
45
+
46
+
47
+ @dataclass
48
+ class ReresolveDiff:
49
+ """Difference between the original and the wheel-aware re-resolve."""
50
+
51
+ tuple_label: str
52
+ added: dict[str, str] = field(default_factory=dict)
53
+ removed: dict[str, str] = field(default_factory=dict)
54
+ version_changed: dict[str, tuple[str, str]] = field(default_factory=dict)
55
+
56
+
57
+ def reresolve_divergent_tuples( # noqa: PLR0913 - mirrors the original resolve
58
+ coordinator: FetchCoordinator,
59
+ requirements: list[str],
60
+ original: UniversalResult,
61
+ report: ValidationReport,
62
+ *,
63
+ constraints: list[str] | None = None,
64
+ uploaded_prior_to: datetime | None = None,
65
+ uploaded_prior_to_overrides: Mapping[str, datetime | None] | None = None,
66
+ dist_policy: DistPolicy = DistPolicy.WHEEL_OR_SDIST,
67
+ dist_policy_overrides: Mapping[str, DistPolicy] | None = None,
68
+ build_policy: BuildPolicy = BuildPolicy.BUILD_LOCAL,
69
+ build_policy_overrides: Mapping[str, BuildPolicy] | None = None,
70
+ vcs_config: VcsConfig | None = None,
71
+ local_sources: list[LocalSource] | None = None,
72
+ vcs_sources: list[VcsSource] | None = None,
73
+ vcs_cache_dir: Path | None = None,
74
+ build_config: NabProjectConfig | None = None,
75
+ resolution_strategy: str = "highest",
76
+ ) -> dict[str, ReresolveDiff]:
77
+ """Re-resolve every tuple with at least one divergent pin.
78
+
79
+ Returns a mapping from tuple_label to its diff against the
80
+ original pins. Tuples with no divergent findings are skipped.
81
+
82
+ The resolve is run with the *same* configuration the original
83
+ matrix used (``uploaded_prior_to``, sdist/build policies,
84
+ resolution strategy). Mismatched options would compare apples
85
+ to oranges; passing them through keeps the diff meaningful.
86
+ """
87
+ new_pins_per_tuple = _reresolve_one_step(
88
+ coordinator,
89
+ requirements,
90
+ original,
91
+ report,
92
+ constraints=constraints,
93
+ uploaded_prior_to=uploaded_prior_to,
94
+ uploaded_prior_to_overrides=uploaded_prior_to_overrides,
95
+ dist_policy=dist_policy,
96
+ dist_policy_overrides=dist_policy_overrides,
97
+ build_policy=build_policy,
98
+ build_policy_overrides=build_policy_overrides,
99
+ vcs_config=vcs_config,
100
+ local_sources=local_sources,
101
+ vcs_sources=vcs_sources,
102
+ vcs_cache_dir=vcs_cache_dir,
103
+ build_config=build_config,
104
+ resolution_strategy=resolution_strategy,
105
+ )
106
+ diffs: dict[str, ReresolveDiff] = {}
107
+ for tr in original.tuple_results:
108
+ if not tr.success:
109
+ continue
110
+ if tr.tuple_.label not in new_pins_per_tuple:
111
+ continue
112
+ original_pins = {k: str(v) for k, v in tr.pins.items()}
113
+ diffs[tr.tuple_.label] = _diff_pins(
114
+ tr.tuple_.label,
115
+ new_pins=new_pins_per_tuple[tr.tuple_.label],
116
+ original_pins=original_pins,
117
+ )
118
+ return diffs
119
+
120
+
121
+ def _reresolve_one_step( # noqa: PLR0913
122
+ coordinator: FetchCoordinator,
123
+ requirements: list[str],
124
+ current: UniversalResult,
125
+ report: ValidationReport,
126
+ *,
127
+ constraints: list[str] | None,
128
+ uploaded_prior_to: datetime | None,
129
+ uploaded_prior_to_overrides: Mapping[str, datetime | None] | None = None,
130
+ dist_policy: DistPolicy,
131
+ dist_policy_overrides: Mapping[str, DistPolicy] | None = None,
132
+ build_policy: BuildPolicy,
133
+ build_policy_overrides: Mapping[str, BuildPolicy] | None = None,
134
+ vcs_config: VcsConfig | None = None,
135
+ local_sources: list[LocalSource] | None = None,
136
+ vcs_sources: list[VcsSource] | None = None,
137
+ vcs_cache_dir: Path | None = None,
138
+ build_config: NabProjectConfig | None = None,
139
+ resolution_strategy: str,
140
+ ) -> dict[str, dict[str, str]]:
141
+ """Run one re-resolve sweep; return ``{tuple_label: {package: version}}``.
142
+
143
+ Tuples without a divergent finding are absent from the output. A
144
+ tuple whose re-resolve fails (returns no pins) IS present with an
145
+ empty value; callers represent that as "all pins removed", not
146
+ "no work attempted".
147
+ """
148
+ by_tuple = _findings_by_tuple_label(report)
149
+ new_pins_per_tuple: dict[str, dict[str, str]] = {}
150
+ for tr in current.tuple_results:
151
+ if not tr.success:
152
+ continue
153
+ findings = by_tuple.get(tr.tuple_.label, [])
154
+ divergent = [f for f in findings if f.status == "divergent"]
155
+ if not divergent:
156
+ continue
157
+ wheel_metadata = _collect_wheel_metadata_overrides(coordinator, divergent)
158
+ if not wheel_metadata: # pragma: no cover - divergent always has metadata
159
+ continue
160
+ new_pins = _resolve_one_tuple_with_overrides(
161
+ coordinator,
162
+ tr.tuple_,
163
+ requirements,
164
+ wheel_metadata,
165
+ constraints=constraints,
166
+ uploaded_prior_to=uploaded_prior_to,
167
+ uploaded_prior_to_overrides=uploaded_prior_to_overrides,
168
+ dist_policy=dist_policy,
169
+ dist_policy_overrides=dist_policy_overrides,
170
+ build_policy=build_policy,
171
+ build_policy_overrides=build_policy_overrides,
172
+ vcs_config=vcs_config,
173
+ local_sources=local_sources,
174
+ vcs_sources=vcs_sources,
175
+ vcs_cache_dir=vcs_cache_dir,
176
+ build_config=build_config,
177
+ resolution_strategy=resolution_strategy,
178
+ )
179
+ new_pins_per_tuple[tr.tuple_.label] = new_pins
180
+ return new_pins_per_tuple
181
+
182
+
183
+ def _resolve_one_tuple_with_overrides( # noqa: PLR0913
184
+ coordinator: FetchCoordinator,
185
+ tup: MatrixTuple,
186
+ requirements: list[str],
187
+ wheel_metadata: dict[tuple[str, str], str],
188
+ *,
189
+ constraints: list[str] | None,
190
+ uploaded_prior_to: datetime | None,
191
+ uploaded_prior_to_overrides: Mapping[str, datetime | None] | None = None,
192
+ dist_policy: DistPolicy,
193
+ dist_policy_overrides: Mapping[str, DistPolicy] | None = None,
194
+ build_policy: BuildPolicy,
195
+ build_policy_overrides: Mapping[str, BuildPolicy] | None = None,
196
+ vcs_config: VcsConfig | None = None,
197
+ local_sources: list[LocalSource] | None = None,
198
+ vcs_sources: list[VcsSource] | None = None,
199
+ vcs_cache_dir: Path | None = None,
200
+ build_config: NabProjectConfig | None = None,
201
+ resolution_strategy: str,
202
+ ) -> dict[str, str]:
203
+ """Re-resolve ``tup`` with the given metadata overrides; return pins."""
204
+ one_tuple_matrix = _Matrix(
205
+ python=f"=={tup.python_version}",
206
+ platforms=(tup.platform_spec,),
207
+ )
208
+ with _override_metadata(coordinator, wheel_metadata):
209
+ result = resolve_with_coordinator(
210
+ coordinator,
211
+ one_tuple_matrix,
212
+ requirements,
213
+ constraints=constraints,
214
+ uploaded_prior_to=uploaded_prior_to,
215
+ uploaded_prior_to_overrides=uploaded_prior_to_overrides,
216
+ dist_policy=dist_policy,
217
+ dist_policy_overrides=dist_policy_overrides,
218
+ build_policy=build_policy,
219
+ build_policy_overrides=build_policy_overrides,
220
+ vcs_config=vcs_config,
221
+ local_sources=local_sources,
222
+ vcs_sources=vcs_sources,
223
+ vcs_cache_dir=vcs_cache_dir,
224
+ build_config=build_config,
225
+ resolution_strategy=resolution_strategy,
226
+ )
227
+ if not result.success:
228
+ return {}
229
+ return {k: str(v) for k, v in result.tuple_results[0].pins.items()}
230
+
231
+
232
+ def _findings_by_tuple_label(
233
+ report: ValidationReport,
234
+ ) -> dict[str, list[PinValidation]]:
235
+ """Group validation findings by tuple label."""
236
+ out: defaultdict[str, list[PinValidation]] = defaultdict(list)
237
+ for f in report.findings:
238
+ out[f.tuple_label].append(f)
239
+ return out
240
+
241
+
242
+ def _collect_wheel_metadata_overrides(
243
+ coordinator: FetchCoordinator,
244
+ findings: list[PinValidation],
245
+ ) -> dict[tuple[str, str], str]:
246
+ """For each divergent finding, fetch the chosen wheel's metadata text."""
247
+ out: dict[tuple[str, str], str] = {}
248
+ for f in findings:
249
+ if f.chosen_wheel is None: # pragma: no cover
250
+ continue
251
+ normalized = canonicalize_name(f.package)
252
+ text = coordinator.index.get_metadata(
253
+ normalized, f"{f.version}#{f.chosen_wheel}"
254
+ )
255
+ if text is not None:
256
+ out[(normalized, f.version)] = text
257
+ return out
258
+
259
+
260
+ def _diff_pins(
261
+ tuple_label: str,
262
+ *,
263
+ new_pins: dict[str, str],
264
+ original_pins: dict[str, str] | None = None,
265
+ ) -> ReresolveDiff:
266
+ """Compute (added, removed, version_changed) between two pin sets."""
267
+ diff = ReresolveDiff(tuple_label=tuple_label)
268
+ if original_pins is None:
269
+ diff.added = dict(new_pins)
270
+ return diff
271
+ for name, new_ver in new_pins.items():
272
+ if name not in original_pins:
273
+ diff.added[name] = new_ver
274
+ elif original_pins[name] != new_ver:
275
+ diff.version_changed[name] = (original_pins[name], new_ver)
276
+ for name, old_ver in original_pins.items():
277
+ if name not in new_pins:
278
+ diff.removed[name] = old_ver
279
+ return diff
280
+
281
+
282
+ @contextmanager
283
+ def _override_metadata(
284
+ coordinator: FetchCoordinator,
285
+ overrides: dict[tuple[str, str], str],
286
+ ) -> Iterator[None]:
287
+ """Snapshot, override, and restore baseline metadata for one re-resolve.
288
+
289
+ Stores ``overrides`` at the ``(name, version)`` key the provider
290
+ reads, then restores the previous values on exit. Also evicts
291
+ the matching entries from the coordinator's parsed-metadata
292
+ cache so a subsequent ``get_parsed_metadata`` re-parses the
293
+ overridden raw text rather than serving the prior tuple's view.
294
+ """
295
+ text_snapshot: dict[tuple[str, str], str | None] = {}
296
+ parsed_snapshot: dict[tuple[str, str], object | None] = {}
297
+ for key in overrides:
298
+ text_snapshot[key] = coordinator.index.get_metadata(*key)
299
+ parsed_snapshot[key] = coordinator.index.pop_parsed_metadata(*key)
300
+ try:
301
+ for key, text in overrides.items():
302
+ coordinator.index.store_metadata(*key, text)
303
+ yield
304
+ finally:
305
+ for key, prior in text_snapshot.items():
306
+ coordinator.index.store_metadata(*key, prior)
307
+ for key, parsed in parsed_snapshot.items():
308
+ coordinator.index.pop_parsed_metadata(*key)
309
+ if parsed is not None:
310
+ coordinator.index.store_parsed_metadata(*key, parsed)