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