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,506 @@
1
+ # This file is dual licensed under the terms of the Apache License, Version
2
+ # 2.0, and the BSD License. See the LICENSE file in the root of this repository
3
+ # for complete details.
4
+
5
+ from __future__ import annotations
6
+
7
+ import operator
8
+ import os
9
+ import platform
10
+ import sys
11
+ from collections.abc import Set as AbstractSet
12
+ from typing import TYPE_CHECKING, Callable, Literal, TypedDict, Union, cast
13
+
14
+ from ._parser import MarkerAtom, MarkerList, Op, Value, Variable
15
+ from ._parser import parse_marker as _parse_marker
16
+ from ._tokenizer import ParserSyntaxError
17
+ from .specifiers import InvalidSpecifier, Specifier
18
+ from .utils import canonicalize_name
19
+
20
+ if TYPE_CHECKING:
21
+ from collections.abc import Mapping
22
+
23
+ __all__ = [
24
+ "Environment",
25
+ "EvaluateContext",
26
+ "InvalidMarker",
27
+ "Marker",
28
+ "UndefinedComparison",
29
+ "UndefinedEnvironmentName",
30
+ "default_environment",
31
+ ]
32
+
33
+
34
+ def __dir__() -> list[str]:
35
+ return __all__
36
+
37
+
38
+ Operator = Callable[[str, Union[str, AbstractSet[str]]], bool]
39
+ EvaluateContext = Literal["metadata", "lock_file", "requirement"]
40
+ """A ``typing.Literal`` enumerating valid marker evaluation contexts.
41
+
42
+ Valid values for the ``context`` passed to :meth:`Marker.evaluate` are:
43
+
44
+ * ``"metadata"`` (for core metadata; default)
45
+ * ``"lock_file"`` (for lock files)
46
+ * ``"requirement"`` (i.e. all other situations)
47
+ """
48
+
49
+ MARKERS_ALLOWING_SET = {"extras", "dependency_groups"}
50
+ MARKERS_REQUIRING_VERSION = {
51
+ "implementation_version",
52
+ "platform_release",
53
+ "python_full_version",
54
+ "python_version",
55
+ }
56
+
57
+
58
+ class InvalidMarker(ValueError):
59
+ """Raised when attempting to create a :class:`Marker` from invalid input.
60
+
61
+ This error indicates that the given marker string does not conform to the
62
+ :ref:`specification of dependency specifiers <pypug:dependency-specifiers>`.
63
+ """
64
+
65
+
66
+ class UndefinedComparison(ValueError):
67
+ """Raised when evaluating an unsupported marker comparison.
68
+
69
+ This can happen when marker values are compared as versions but do not
70
+ conform to the :ref:`specification of version specifiers
71
+ <pypug:version-specifiers>`.
72
+ """
73
+
74
+
75
+ class UndefinedEnvironmentName(ValueError):
76
+ """Raised when evaluating a marker that references a missing environment key."""
77
+
78
+
79
+ class Environment(TypedDict):
80
+ """
81
+ A dictionary that represents a Python environment as captured by
82
+ :func:`default_environment`. All fields are required.
83
+ """
84
+
85
+ implementation_name: str
86
+ """The implementation's identifier, e.g. ``'cpython'``."""
87
+
88
+ implementation_version: str
89
+ """
90
+ The implementation's version, e.g. ``'3.13.0a2'`` for CPython 3.13.0a2, or
91
+ ``'7.3.13'`` for PyPy3.10 v7.3.13.
92
+ """
93
+
94
+ os_name: str
95
+ """
96
+ The value of :py:data:`os.name`. The name of the operating system dependent module
97
+ imported, e.g. ``'posix'``.
98
+ """
99
+
100
+ platform_machine: str
101
+ """
102
+ Returns the machine type, e.g. ``'i386'``.
103
+
104
+ An empty string if the value cannot be determined.
105
+ """
106
+
107
+ platform_release: str
108
+ """
109
+ The system's release, e.g. ``'2.2.0'`` or ``'NT'``.
110
+
111
+ An empty string if the value cannot be determined.
112
+ """
113
+
114
+ platform_system: str
115
+ """
116
+ The system/OS name, e.g. ``'Linux'``, ``'Windows'`` or ``'Java'``.
117
+
118
+ An empty string if the value cannot be determined.
119
+ """
120
+
121
+ platform_version: str
122
+ """
123
+ The system's release version, e.g. ``'#3 on degas'``.
124
+
125
+ An empty string if the value cannot be determined.
126
+ """
127
+
128
+ python_full_version: str
129
+ """
130
+ The Python version as string ``'major.minor.patchlevel'``.
131
+
132
+ Note that unlike the Python :py:data:`sys.version`, this value will always include
133
+ the patchlevel (it defaults to 0).
134
+ """
135
+
136
+ platform_python_implementation: str
137
+ """
138
+ A string identifying the Python implementation, e.g. ``'CPython'``.
139
+ """
140
+
141
+ python_version: str
142
+ """The Python version as string ``'major.minor'``."""
143
+
144
+ sys_platform: str
145
+ """
146
+ This string contains a platform identifier that can be used to append
147
+ platform-specific components to :py:data:`sys.path`, for instance.
148
+
149
+ For Unix systems, except on Linux and AIX, this is the lowercased OS name as
150
+ returned by ``uname -s`` with the first part of the version as returned by
151
+ ``uname -r`` appended, e.g. ``'sunos5'`` or ``'freebsd8'``, at the time when Python
152
+ was built.
153
+ """
154
+
155
+
156
+ def _normalize_extras(
157
+ result: MarkerList | MarkerAtom | str,
158
+ ) -> MarkerList | MarkerAtom | str:
159
+ if not isinstance(result, tuple):
160
+ return result
161
+
162
+ lhs, op, rhs = result
163
+ if isinstance(lhs, Variable) and lhs.value == "extra":
164
+ normalized_extra = canonicalize_name(rhs.value)
165
+ rhs = Value(normalized_extra)
166
+ elif isinstance(rhs, Variable) and rhs.value == "extra":
167
+ normalized_extra = canonicalize_name(lhs.value)
168
+ lhs = Value(normalized_extra)
169
+ return lhs, op, rhs
170
+
171
+
172
+ def _normalize_extra_values(results: MarkerList) -> MarkerList:
173
+ """
174
+ Normalize extra values.
175
+ """
176
+
177
+ return [_normalize_extras(r) for r in results]
178
+
179
+
180
+ def _format_marker(
181
+ marker: list[str] | MarkerAtom | str, first: bool | None = True
182
+ ) -> str:
183
+ assert isinstance(marker, (list, tuple, str))
184
+
185
+ # Sometimes we have a structure like [[...]] which is a single item list
186
+ # where the single item is itself it's own list. In that case we want skip
187
+ # the rest of this function so that we don't get extraneous () on the
188
+ # outside.
189
+ if (
190
+ isinstance(marker, list)
191
+ and len(marker) == 1
192
+ and isinstance(marker[0], (list, tuple))
193
+ ):
194
+ return _format_marker(marker[0])
195
+
196
+ if isinstance(marker, list):
197
+ inner = (_format_marker(m, first=False) for m in marker)
198
+ if first:
199
+ return " ".join(inner)
200
+ else:
201
+ return "(" + " ".join(inner) + ")"
202
+ elif isinstance(marker, tuple):
203
+ return " ".join([m.serialize() for m in marker])
204
+ else:
205
+ return marker
206
+
207
+
208
+ _operators: dict[str, Operator] = {
209
+ "in": lambda lhs, rhs: lhs in rhs,
210
+ "not in": lambda lhs, rhs: lhs not in rhs,
211
+ "<": lambda _lhs, _rhs: False,
212
+ "<=": operator.eq,
213
+ "==": operator.eq,
214
+ "!=": operator.ne,
215
+ ">=": operator.eq,
216
+ ">": lambda _lhs, _rhs: False,
217
+ }
218
+
219
+
220
+ def _eval_op(lhs: str, op: Op, rhs: str | AbstractSet[str], *, key: str) -> bool:
221
+ op_str = op.serialize()
222
+ if key in MARKERS_REQUIRING_VERSION:
223
+ try:
224
+ spec = Specifier(f"{op_str}{rhs}")
225
+ except InvalidSpecifier:
226
+ pass
227
+ else:
228
+ return spec.contains(lhs, prereleases=True)
229
+
230
+ oper: Operator | None = _operators.get(op_str)
231
+ if oper is None:
232
+ raise UndefinedComparison(f"Undefined {op!r} on {lhs!r} and {rhs!r}.")
233
+
234
+ return oper(lhs, rhs)
235
+
236
+
237
+ def _normalize(
238
+ lhs: str, rhs: str | AbstractSet[str], key: str
239
+ ) -> tuple[str, str | AbstractSet[str]]:
240
+ # PEP 685 - Comparison of extra names for optional distribution dependencies
241
+ # https://peps.python.org/pep-0685/
242
+ # > When comparing extra names, tools MUST normalize the names being
243
+ # > compared using the semantics outlined in PEP 503 for names
244
+ if key == "extra":
245
+ assert isinstance(rhs, str), "extra value must be a string"
246
+ # Both sides are normalized at this point already
247
+ return (lhs, rhs)
248
+ if key in MARKERS_ALLOWING_SET:
249
+ if isinstance(rhs, str): # pragma: no cover
250
+ return (canonicalize_name(lhs), canonicalize_name(rhs))
251
+ else:
252
+ return (canonicalize_name(lhs), {canonicalize_name(v) for v in rhs})
253
+
254
+ # other environment markers don't have such standards
255
+ return lhs, rhs
256
+
257
+
258
+ def _evaluate_markers(
259
+ markers: MarkerList, environment: dict[str, str | AbstractSet[str]]
260
+ ) -> bool:
261
+ groups: list[list[bool]] = [[]]
262
+
263
+ for marker in markers:
264
+ if isinstance(marker, list):
265
+ groups[-1].append(_evaluate_markers(marker, environment))
266
+ elif isinstance(marker, tuple):
267
+ lhs, op, rhs = marker
268
+
269
+ if isinstance(lhs, Variable):
270
+ environment_key = lhs.value
271
+ lhs_value = environment[environment_key]
272
+ rhs_value = rhs.value
273
+ else:
274
+ lhs_value = lhs.value
275
+ environment_key = rhs.value
276
+ rhs_value = environment[environment_key]
277
+
278
+ assert isinstance(lhs_value, str), "lhs must be a string"
279
+ lhs_value, rhs_value = _normalize(lhs_value, rhs_value, key=environment_key)
280
+ groups[-1].append(_eval_op(lhs_value, op, rhs_value, key=environment_key))
281
+ elif marker == "or":
282
+ groups.append([])
283
+ elif marker == "and":
284
+ pass
285
+ else: # pragma: nocover
286
+ raise TypeError(f"Unexpected marker {marker!r}")
287
+
288
+ return any(all(item) for item in groups)
289
+
290
+
291
+ def _format_full_version(info: sys._version_info) -> str:
292
+ version = f"{info.major}.{info.minor}.{info.micro}"
293
+ kind = info.releaselevel
294
+ if kind != "final":
295
+ version += kind[0] + str(info.serial)
296
+ return version
297
+
298
+
299
+ def default_environment() -> Environment:
300
+ """Return the default marker environment for the current Python process.
301
+
302
+ This is the base environment used by :meth:`Marker.evaluate`.
303
+ """
304
+ iver = _format_full_version(sys.implementation.version)
305
+ implementation_name = sys.implementation.name
306
+ return {
307
+ "implementation_name": implementation_name,
308
+ "implementation_version": iver,
309
+ "os_name": os.name,
310
+ "platform_machine": platform.machine(),
311
+ "platform_release": platform.release(),
312
+ "platform_system": platform.system(),
313
+ "platform_version": platform.version(),
314
+ "python_full_version": platform.python_version(),
315
+ "platform_python_implementation": platform.python_implementation(),
316
+ "python_version": ".".join(platform.python_version_tuple()[:2]),
317
+ "sys_platform": sys.platform,
318
+ }
319
+
320
+
321
+ class Marker:
322
+ """Represents a parsed dependency marker expression.
323
+
324
+ Marker expressions are parsed according to the
325
+ :ref:`specification of dependency specifiers <pypug:dependency-specifiers>`.
326
+
327
+ :param marker: The string representation of a marker expression.
328
+ :raises InvalidMarker: If ``marker`` cannot be parsed.
329
+
330
+ Instances are safe to serialize with :mod:`pickle`. They use a stable
331
+ format so the same pickle can be loaded in future packaging releases.
332
+
333
+ .. versionchanged:: 26.2
334
+
335
+ Added a stable pickle format. Pickles created with packaging 26.2+ can
336
+ be unpickled with future releases. Backward compatibility with pickles
337
+ from packaging < 26.2 is supported but may be removed in a future
338
+ release.
339
+ """
340
+
341
+ __slots__ = ("_markers",)
342
+
343
+ def __init__(self, marker: str) -> None:
344
+ # Note: We create a Marker object without calling this constructor in
345
+ # packaging.requirements.Requirement. If any additional logic is
346
+ # added here, make sure to mirror/adapt Requirement.
347
+
348
+ # If this fails and throws an error, the repr still expects _markers to
349
+ # be defined.
350
+ self._markers: MarkerList = []
351
+
352
+ try:
353
+ self._markers = _normalize_extra_values(_parse_marker(marker))
354
+ # The attribute `_markers` can be described in terms of a recursive type:
355
+ # MarkerList = List[Union[Tuple[Node, ...], str, MarkerList]]
356
+ #
357
+ # For example, the following expression:
358
+ # python_version > "3.6" or (python_version == "3.6" and os_name == "unix")
359
+ #
360
+ # is parsed into:
361
+ # [
362
+ # (<Variable('python_version')>, <Op('>')>, <Value('3.6')>),
363
+ # 'and',
364
+ # [
365
+ # (<Variable('python_version')>, <Op('==')>, <Value('3.6')>),
366
+ # 'or',
367
+ # (<Variable('os_name')>, <Op('==')>, <Value('unix')>)
368
+ # ]
369
+ # ]
370
+ except ParserSyntaxError as e:
371
+ raise InvalidMarker(str(e)) from e
372
+
373
+ @classmethod
374
+ def _from_markers(cls, markers: MarkerList) -> Marker:
375
+ """Create a Marker instance from a pre-parsed marker tree.
376
+
377
+ This avoids re-parsing serialised marker strings when combining markers.
378
+ """
379
+ new = cls.__new__(cls)
380
+ new._markers = markers
381
+ return new
382
+
383
+ def __str__(self) -> str:
384
+ return _format_marker(self._markers)
385
+
386
+ def __repr__(self) -> str:
387
+ return f"<{self.__class__.__name__}({str(self)!r})>"
388
+
389
+ def __hash__(self) -> int:
390
+ return hash(str(self))
391
+
392
+ def __eq__(self, other: object) -> bool:
393
+ if not isinstance(other, Marker):
394
+ return NotImplemented
395
+
396
+ return str(self) == str(other)
397
+
398
+ def __getstate__(self) -> str:
399
+ # Return the marker expression string for compactness and stability.
400
+ # Internal Node objects are excluded; the string is re-parsed on load.
401
+ return str(self)
402
+
403
+ def __setstate__(self, state: object) -> None:
404
+ if isinstance(state, str):
405
+ # New format (26.2+): just the marker expression string.
406
+ try:
407
+ self._markers = _normalize_extra_values(_parse_marker(state))
408
+ except ParserSyntaxError as exc:
409
+ raise TypeError(f"Cannot restore Marker from {state!r}") from exc
410
+ return
411
+ if isinstance(state, dict) and "_markers" in state:
412
+ # Old format (packaging <= 26.1, no __slots__): plain __dict__.
413
+ markers = state["_markers"]
414
+ if isinstance(markers, list):
415
+ self._markers = markers
416
+ return
417
+ if isinstance(state, tuple) and len(state) == 2:
418
+ # Old format (packaging <= 26.1, __slots__): (None, {slot: value}).
419
+ _, slot_dict = state
420
+ if isinstance(slot_dict, dict) and "_markers" in slot_dict:
421
+ markers = slot_dict["_markers"]
422
+ if isinstance(markers, list):
423
+ self._markers = markers
424
+ return
425
+ raise TypeError(f"Cannot restore Marker from {state!r}")
426
+
427
+ def __and__(self, other: Marker) -> Marker:
428
+ if not isinstance(other, Marker):
429
+ return NotImplemented
430
+ return self._from_markers([self._markers, "and", other._markers])
431
+
432
+ def __or__(self, other: Marker) -> Marker:
433
+ if not isinstance(other, Marker):
434
+ return NotImplemented
435
+ return self._from_markers([self._markers, "or", other._markers])
436
+
437
+ def evaluate(
438
+ self,
439
+ environment: Mapping[str, str | AbstractSet[str]] | None = None,
440
+ context: EvaluateContext = "metadata",
441
+ ) -> bool:
442
+ """Evaluate a marker.
443
+
444
+ Return the boolean from evaluating this marker against the environment.
445
+ The environment is determined from the current Python process unless
446
+ passed in explicitly.
447
+
448
+ :param environment: Mapping containing keys and values to override the
449
+ detected environment.
450
+ :param EvaluateContext context: The context in which the marker is
451
+ evaluated, which influences what marker names are considered valid.
452
+ Accepted values are ``"metadata"`` (for core metadata; default),
453
+ ``"lock_file"``, and ``"requirement"`` (i.e. all other situations).
454
+ :raises UndefinedComparison: If the marker uses a comparison on values
455
+ that are not valid versions per the :ref:`specification of version
456
+ specifiers <pypug:version-specifiers>`.
457
+ :raises UndefinedEnvironmentName: If the marker references a value that
458
+ is missing from the evaluation environment.
459
+ :returns: ``True`` if the marker matches, otherwise ``False``.
460
+
461
+ """
462
+ current_environment = cast(
463
+ "dict[str, str | AbstractSet[str]]", default_environment()
464
+ )
465
+ if context == "lock_file":
466
+ current_environment |= {
467
+ "extras": frozenset(),
468
+ "dependency_groups": frozenset(),
469
+ }
470
+ elif context == "metadata":
471
+ current_environment["extra"] = ""
472
+
473
+ if environment is not None:
474
+ current_environment |= environment
475
+ if "extra" in current_environment:
476
+ # The API used to allow setting extra to None. We need to handle
477
+ # this case for backwards compatibility. Also skip running
478
+ # normalize name if extra is empty.
479
+ extra = cast("str | None", current_environment["extra"])
480
+ current_environment["extra"] = canonicalize_name(extra) if extra else ""
481
+
482
+ return _evaluate_markers(
483
+ self._markers, _repair_python_full_version(current_environment)
484
+ )
485
+
486
+
487
+ def _pep440_python_full_version(python_full_version: str) -> str:
488
+ """
489
+ Work around platform.python_version() returning something that is not PEP 440
490
+ compliant for non-tagged Python builds.
491
+ """
492
+ if python_full_version.endswith("+"):
493
+ return f"{python_full_version}local"
494
+ return python_full_version
495
+
496
+
497
+ def _repair_python_full_version(
498
+ env: dict[str, str | AbstractSet[str]],
499
+ ) -> dict[str, str | AbstractSet[str]]:
500
+ """
501
+ Work around platform.python_version() returning something that is not PEP 440
502
+ compliant for non-tagged Python builds.
503
+ """
504
+ python_full_version = cast("str", env["python_full_version"])
505
+ env["python_full_version"] = _pep440_python_full_version(python_full_version)
506
+ return env