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,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]