pex 2.61.1__py2.py3-none-any.whl → 2.62.0__py2.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.

Potentially problematic release.


This version of pex might be problematic. Click here for more details.

Files changed (44) hide show
  1. pex/docs/html/_pagefind/fragment/en_19d749c.pf_fragment +0 -0
  2. pex/docs/html/_pagefind/fragment/en_358f3c1.pf_fragment +0 -0
  3. pex/docs/html/_pagefind/fragment/{en_bf32fcd.pf_fragment → en_4a62b9a.pf_fragment} +0 -0
  4. pex/docs/html/_pagefind/fragment/{en_c9714ee.pf_fragment → en_5c1928c.pf_fragment} +0 -0
  5. pex/docs/html/_pagefind/fragment/en_a6b0f05.pf_fragment +0 -0
  6. pex/docs/html/_pagefind/fragment/en_a77f515.pf_fragment +0 -0
  7. pex/docs/html/_pagefind/fragment/en_ed65506.pf_fragment +0 -0
  8. pex/docs/html/_pagefind/fragment/en_f5aab8f.pf_fragment +0 -0
  9. pex/docs/html/_pagefind/index/en_856ae3c.pf_index +0 -0
  10. pex/docs/html/_pagefind/pagefind-entry.json +1 -1
  11. pex/docs/html/_pagefind/pagefind.en_92d55346bf.pf_meta +0 -0
  12. pex/docs/html/_static/documentation_options.js +1 -1
  13. pex/docs/html/api/vars.html +5 -5
  14. pex/docs/html/buildingpex.html +5 -5
  15. pex/docs/html/genindex.html +5 -5
  16. pex/docs/html/index.html +5 -5
  17. pex/docs/html/recipes.html +5 -5
  18. pex/docs/html/scie.html +5 -5
  19. pex/docs/html/search.html +5 -5
  20. pex/docs/html/whatispex.html +5 -5
  21. pex/resolve/locker.py +6 -51
  22. pex/resolve/locker_patches.py +123 -209
  23. pex/resolve/lockfile/create.py +9 -10
  24. pex/resolve/lockfile/targets.py +292 -26
  25. pex/resolve/requirement_configuration.py +15 -8
  26. pex/resolve/target_system.py +512 -119
  27. pex/resolver.py +181 -90
  28. pex/venv/venv_pex.py +1 -1
  29. pex/version.py +1 -1
  30. {pex-2.61.1.dist-info → pex-2.62.0.dist-info}/METADATA +4 -4
  31. {pex-2.61.1.dist-info → pex-2.62.0.dist-info}/RECORD +36 -36
  32. pex/docs/html/_pagefind/fragment/en_245699a.pf_fragment +0 -0
  33. pex/docs/html/_pagefind/fragment/en_4695b51.pf_fragment +0 -0
  34. pex/docs/html/_pagefind/fragment/en_57f2ab1.pf_fragment +0 -0
  35. pex/docs/html/_pagefind/fragment/en_aefc110.pf_fragment +0 -0
  36. pex/docs/html/_pagefind/fragment/en_b7fad62.pf_fragment +0 -0
  37. pex/docs/html/_pagefind/fragment/en_e6c0aae.pf_fragment +0 -0
  38. pex/docs/html/_pagefind/index/en_7e57d09.pf_index +0 -0
  39. pex/docs/html/_pagefind/pagefind.en_c578c4b677.pf_meta +0 -0
  40. {pex-2.61.1.dist-info → pex-2.62.0.dist-info}/WHEEL +0 -0
  41. {pex-2.61.1.dist-info → pex-2.62.0.dist-info}/entry_points.txt +0 -0
  42. {pex-2.61.1.dist-info → pex-2.62.0.dist-info}/licenses/LICENSE +0 -0
  43. {pex-2.61.1.dist-info → pex-2.62.0.dist-info}/pylock/pylock.toml +0 -0
  44. {pex-2.61.1.dist-info → pex-2.62.0.dist-info}/top_level.txt +0 -0
@@ -6,6 +6,9 @@ from __future__ import absolute_import
6
6
  import operator
7
7
  import sys
8
8
 
9
+ from pex import pex_warnings
10
+ from pex.common import pluralize
11
+ from pex.compatibility import string
9
12
  from pex.enum import Enum
10
13
  from pex.exceptions import production_assert, reportable_unexpected_error_msg
11
14
  from pex.interpreter_constraints import InterpreterConstraint, iter_compatible_versions
@@ -16,7 +19,7 @@ from pex.pep_503 import ProjectName
16
19
  from pex.sorted_tuple import SortedTuple
17
20
  from pex.third_party.packaging.markers import Marker, Variable
18
21
  from pex.third_party.packaging.specifiers import Specifier, SpecifierSet
19
- from pex.typing import TYPE_CHECKING, cast
22
+ from pex.typing import TYPE_CHECKING, Generic, cast
20
23
 
21
24
  if TYPE_CHECKING:
22
25
  from typing import (
@@ -30,6 +33,7 @@ if TYPE_CHECKING:
30
33
  Optional,
31
34
  Sequence,
32
35
  Tuple,
36
+ TypeVar,
33
37
  Union,
34
38
  )
35
39
 
@@ -84,19 +88,132 @@ def _marker_items(marker):
84
88
  return cast("Iterable[Any]", marker._markers)
85
89
 
86
90
 
91
+ class MarkerVisitor(Generic["_C"]):
92
+ def visit_and(
93
+ self,
94
+ marker, # type: Marker
95
+ context, # type: _C
96
+ ):
97
+ # type: (...) -> None
98
+ pass
99
+
100
+ def visit_or(
101
+ self,
102
+ marker, # type: Marker
103
+ context, # type: _C
104
+ ):
105
+ # type: (...) -> None
106
+ pass
107
+
108
+ def begin_visit_group(
109
+ self,
110
+ group, # type: List[Any]
111
+ marker, # type: Marker
112
+ context, # type: _C
113
+ ):
114
+ # type: (...) -> Optional[_C]
115
+ return None
116
+
117
+ def end_visit_group(
118
+ self,
119
+ group, # type: List[Any]
120
+ marker, # type: Marker
121
+ context, # type: _C
122
+ group_context, # type: Optional[_C]
123
+ ):
124
+ # type: (...) -> None
125
+ pass
126
+
127
+ def visit_op(
128
+ self,
129
+ lhs, # type: Any
130
+ op, # type: Any
131
+ rhs, # type: Any
132
+ marker, # type: Marker
133
+ context, # type: _C
134
+ ):
135
+ # type: (...) -> None
136
+ pass
137
+
138
+
139
+ if TYPE_CHECKING:
140
+ _C = TypeVar("_C")
141
+
142
+
143
+ class MarkerParser(Generic["_C"]):
144
+ def __init__(self, visitor):
145
+ # type: (MarkerVisitor["_C"]) -> None
146
+ self._visitor = visitor
147
+
148
+ def _parse_marker_item(
149
+ self,
150
+ item, # type: Union[str, List, Tuple]
151
+ marker, # type: Marker
152
+ context, # type: _C
153
+ ):
154
+ # type: (...) -> None
155
+
156
+ if item == "and":
157
+ self._visitor.visit_and(marker, context)
158
+ elif item == "or":
159
+ self._visitor.visit_or(marker, context)
160
+ elif isinstance(item, list):
161
+ group_context = self._visitor.begin_visit_group(item, marker, context)
162
+ element_context = group_context if group_context is not None else context
163
+ for element in item:
164
+ self._parse_marker_item(element, marker, element_context)
165
+ self._visitor.end_visit_group(item, marker, context, group_context)
166
+ elif isinstance(item, tuple):
167
+ lhs, op, rhs = item
168
+ self._visitor.visit_op(lhs, op, rhs, marker, context)
169
+ else:
170
+ raise ValueError("Marker is invalid: {marker}".format(marker=marker))
171
+
172
+ def parse(
173
+ self,
174
+ marker, # type: Marker
175
+ context, # type: _C
176
+ ):
177
+ # type: (...) -> _C
178
+
179
+ for item in _marker_items(marker):
180
+ self._parse_marker_item(item, marker, context)
181
+ return context
182
+
183
+
184
+ class HasMarkerVisitor(MarkerVisitor[None]):
185
+ def __init__(self, name):
186
+ # type: (str) -> None
187
+ self._name = name
188
+ self.has_marker = False
189
+
190
+ def visit_op(
191
+ self,
192
+ lhs, # type: Any
193
+ op, # type: Any
194
+ rhs, # type: Any
195
+ marker, # type: Marker
196
+ context, # type: None
197
+ ):
198
+ # type: (...) -> None
199
+ if self.has_marker:
200
+ return
201
+
202
+ for term in lhs, rhs:
203
+ if is_variable(term) and self._name == str(term):
204
+ self.has_marker = True
205
+ break
206
+
207
+
87
208
  def has_marker(
88
209
  marker, # type: Marker
89
210
  name, # type: str
90
211
  ):
91
212
  # type: (...) -> bool
92
213
 
93
- for item in _marker_items(marker):
94
- if isinstance(item, tuple):
95
- lhs, _, rhs = item
96
- for term in lhs, rhs:
97
- if isinstance(term, Variable) and name == str(term):
98
- return True
99
- return False
214
+ visitor = HasMarkerVisitor(name)
215
+ MarkerParser(visitor).parse(marker, None)
216
+ return visitor.has_marker
100
217
 
101
218
 
102
219
  if TYPE_CHECKING:
@@ -122,6 +239,17 @@ _VERSION_MARKER_OP_FLIPPED = {
122
239
  ">": "<",
123
240
  }
124
241
 
242
+ _VERSION_CMP_OPS = {
243
+ "<",
244
+ "<=",
245
+ "==",
246
+ "!=",
247
+ ">=",
248
+ ">",
249
+ "~=",
250
+ "===",
251
+ }
252
+
125
253
 
126
254
  class _Op(object):
127
255
  def __init__(self, lhs):
@@ -143,81 +271,159 @@ class _Or(_Op):
143
271
  return self.lhs(marker_env) or cast("EvalMarker", self.rhs)(marker_env)
144
272
 
145
273
 
146
- def _get_values_func(marker):
147
- # type: (str) -> Optional[Callable[[MarkerEnv], Iterable[str]]]
148
-
149
- if marker == "extra":
150
- return lambda marker_env: marker_env.extras
151
- elif marker == "os_name":
152
- return lambda marker_env: marker_env.os_names
153
- elif marker == "platform_system":
154
- return lambda marker_env: marker_env.platform_systems
155
- elif marker == "sys_platform":
156
- return lambda marker_env: marker_env.sys_platforms
157
- elif marker == "platform_python_implementation":
158
- return lambda marker_env: marker_env.platform_python_implementations
159
- elif marker == "python_version":
160
- return lambda marker_env: marker_env.python_versions
161
- elif marker == "python_full_version":
162
- return lambda marker_env: marker_env.python_full_versions
163
- return None
164
-
165
-
166
- def _parse_marker_item(
167
- stack, # type: List[EvalMarker]
168
- item, # type: Union[str, List, Tuple]
169
- marker, # type: Marker
170
- ):
171
- # type: (...) -> None
172
-
173
- if item == "and":
174
- stack.append(_And(stack.pop()))
175
- elif item == "or":
176
- stack.append(_Or(stack.pop()))
177
- elif isinstance(item, list):
178
- group = [] # type: List[EvalMarker]
179
- for element in item:
180
- _parse_marker_item(group, element, marker)
181
- production_assert(len(group) == 1)
182
- if stack:
183
- production_assert(isinstance(stack[-1], _Op))
184
- cast(_Op, stack[-1]).rhs = group[0]
185
- else:
186
- stack.extend(group)
187
- elif isinstance(item, tuple):
188
- lhs, op, rhs = item
189
- check = EvalMarkerFunc.create(lhs, op, rhs)
190
- if stack:
191
- production_assert(isinstance(stack[-1], _Op))
192
- cast(_Op, stack[-1]).rhs = check
274
+ @attr.s(frozen=True)
275
+ class Values(object):
276
+ @classmethod
277
+ def from_dict(cls, data):
278
+ # type: (Dict[str, Any]) -> Values
279
+ return cls(
280
+ marker_name=data.pop("marker_name"),
281
+ values=tuple(data.pop("values", ())),
282
+ inclusive=data.pop("inclusive", True),
283
+ )
284
+
285
+ marker_name = attr.ib() # type: str
286
+ values = attr.ib(default=()) # type: Tuple[str, ...]
287
+ inclusive = attr.ib(default=True) # type: bool
288
+
289
+ def to_dict(self):
290
+ # type: () -> Dict[str, Any]
291
+ return {"marker_name": self.marker_name, "values": self.values, "inclusive": self.inclusive}
292
+
293
+ def apply(self, func):
294
+ # type: (Callable[[str], bool]) -> bool
295
+ if len(self.values) == 0:
296
+ return self.marker_name != "extra"
297
+ if self.inclusive:
298
+ return any(map(func, self.values))
299
+ return all((not result) for result in map(func, self.values))
300
+
301
+ def __len__(self):
302
+ # type: () -> int
303
+ return len(self.values)
304
+
305
+ def __iter__(self):
306
+ # type: () -> Iterator[str]
307
+ return iter(self.values)
308
+
309
+
310
+ def _get_values_func(marker_name):
311
+ # type: (str) -> Callable[[MarkerEnv], Values]
312
+
313
+ if marker_name == "extra":
314
+ return lambda marker_env: Values(marker_name, marker_env.extras)
315
+ elif marker_name == "os_name":
316
+ return lambda marker_env: Values(marker_name, marker_env.os_names)
317
+ elif marker_name == "platform_system":
318
+ return lambda marker_env: Values(marker_name, marker_env.platform_systems)
319
+ elif marker_name == "sys_platform":
320
+ return lambda marker_env: Values(marker_name, marker_env.sys_platforms)
321
+ elif marker_name == "platform_python_implementation":
322
+ return lambda marker_env: Values(marker_name, marker_env.platform_python_implementations)
323
+ elif marker_name == "python_version":
324
+ return lambda marker_env: Values(marker_name, marker_env.python_versions)
325
+ elif marker_name == "python_full_version":
326
+ return lambda marker_env: Values(marker_name, marker_env.python_full_versions)
327
+ return lambda marker_env: marker_env.extra_markers.get_values(marker_name)
328
+
329
+
330
+ class UniversalMarkerVisitor(MarkerVisitor["List[EvalMarker]"]):
331
+ @classmethod
332
+ def parse_marker(cls, marker):
333
+ # type: (Marker) -> EvalMarker
334
+
335
+ checks = MarkerParser(cls()).parse(marker, context=[])
336
+ production_assert(len(checks) == 1)
337
+ return checks[0]
338
+
339
+ def visit_and(
340
+ self,
341
+ marker, # type: Marker
342
+ context, # type: List[EvalMarker]
343
+ ):
344
+ # type: (...) -> None
345
+ context.append(_And(context.pop()))
346
+
347
+ def visit_or(
348
+ self,
349
+ marker, # type: Marker
350
+ context, # type: List[EvalMarker]
351
+ ):
352
+ # type: (...) -> None
353
+ context.append(_Or(context.pop()))
354
+
355
+ def begin_visit_group(
356
+ self,
357
+ group, # type: List[Any]
358
+ marker, # type: Marker
359
+ context, # type: List[EvalMarker]
360
+ ):
361
+ # type: (...) -> Optional[List[EvalMarker]]
362
+ return []
363
+
364
+ def end_visit_group(
365
+ self,
366
+ group, # type: List[Any]
367
+ marker, # type: Marker
368
+ context, # type: List[EvalMarker]
369
+ group_context, # type: Optional[List[EvalMarker]]
370
+ ):
371
+ # type: (...) -> None
372
+
373
+ if group_context is None or len(group_context) != 1:
374
+ raise AssertionError(reportable_unexpected_error_msg())
375
+
376
+ if context:
377
+ production_assert(isinstance(context[-1], _Op))
378
+ cast(_Op, context[-1]).rhs = group_context[0]
193
379
  else:
194
- stack.append(check)
195
- else:
196
- raise ValueError("Marker is invalid: {marker}".format(marker=marker))
380
+ context.extend(group_context)
197
381
 
382
+ def visit_op(
383
+ self,
384
+ lhs, # type: Any
385
+ op, # type: Any
386
+ rhs, # type: Any
387
+ marker, # type: Marker
388
+ context, # type: List[EvalMarker]
389
+ ):
390
+ # type: (...) -> None
198
391
 
199
- def _parse_marker_check(marker):
200
- # type: (Marker) -> EvalMarker
201
- checks = [] # type: List[EvalMarker]
202
- for item in _marker_items(marker):
203
- _parse_marker_item(checks, item, marker)
204
- production_assert(len(checks) == 1)
205
- return checks[0]
392
+ check = EvalMarkerFunc.create(lhs, op, rhs)
393
+ if context:
394
+ production_assert(isinstance(context[-1], _Op))
395
+ cast(_Op, context[-1]).rhs = check
396
+ else:
397
+ context.append(check)
206
398
 
207
399
 
208
- _MARKER_CHECKS = {} # type: Dict[str, EvalMarker]
400
+ _MARKER_CHECKS = {} # type: Dict[Union[Marker, str], EvalMarker]
209
401
 
210
402
 
211
403
  def _parse_marker(marker):
212
404
  # type: (Marker) -> EvalMarker
213
- maker_str = str(marker)
214
- eval_marker = _MARKER_CHECKS.get(maker_str)
405
+ eval_marker = _MARKER_CHECKS.get(marker)
215
406
  if not eval_marker:
216
- eval_marker = _parse_marker_check(marker)
217
- _MARKER_CHECKS[maker_str] = eval_marker
407
+ marker_str = str(marker)
408
+ eval_marker = _MARKER_CHECKS.get(marker_str)
409
+ if not eval_marker:
410
+ eval_marker = UniversalMarkerVisitor.parse_marker(marker)
411
+ _MARKER_CHECKS[marker] = eval_marker
412
+ _MARKER_CHECKS[marker_str] = eval_marker
218
413
  return eval_marker
219
414
 
220
415
 
416
+ def is_variable(value):
417
+ # type: (Any) -> bool
418
+
419
+ if isinstance(value, Variable):
420
+ return True
421
+
422
+ # N.B.: This allows interop with Pip vendored packaging which has the same types in a different
423
+ # namespace.
424
+ return type(value).__name__ == "Variable"
425
+
426
+
221
427
  class EvalMarkerFunc(object):
222
428
  @classmethod
223
429
  def create(
@@ -228,39 +434,32 @@ class EvalMarkerFunc(object):
228
434
  ):
229
435
  # type: (...) -> Callable[[MarkerEnv], bool]
230
436
 
231
- if isinstance(lhs, Variable):
232
- marker_name = str(lhs)
437
+ for var, operand, operand_side in ((lhs, rhs, "rhs"), (rhs, lhs, "lhs")):
438
+ if not is_variable(var):
439
+ continue
440
+ marker_name = str(var)
233
441
  get_values = _get_values_func(marker_name)
234
- if get_values:
235
- value = str(rhs)
236
- if marker_name == "extra":
237
- value = ProjectName(value).normalized
238
- return cls(
239
- get_values=get_values,
240
- op=str(op),
241
- rhs=value,
242
- is_version_comparison=marker_name in ("python_version", "python_full_version"),
243
- )
244
-
245
- if isinstance(rhs, Variable):
246
- marker_name = str(rhs)
247
- get_values = _get_values_func(marker_name)
248
- if get_values:
249
- value = str(lhs)
250
- if marker_name == "extra":
251
- value = ProjectName(value).normalized
252
- return cls(
253
- get_values=get_values,
254
- op=str(op),
255
- lhs=value,
256
- is_version_comparison=marker_name in ("python_version", "python_full_version"),
257
- )
442
+ value = str(operand)
443
+ if marker_name == "extra":
444
+ value = ProjectName(value).normalized
445
+ op_string = str(op)
446
+ operand_side_arg = {operand_side: value}
447
+ return cls(
448
+ get_values=get_values,
449
+ op=op_string,
450
+ is_version_comparison=(
451
+ marker_name
452
+ in ("python_version", "python_full_version", "implementation_version")
453
+ and op_string in _VERSION_CMP_OPS
454
+ ),
455
+ **operand_side_arg
456
+ )
258
457
 
259
458
  return lambda _: True
260
459
 
261
460
  def __init__(
262
461
  self,
263
- get_values, # type: Callable[[MarkerEnv], Iterable[str]]
462
+ get_values, # type: Callable[[MarkerEnv], Values]
264
463
  op, # type: str
265
464
  lhs=None, # type: Optional[str]
266
465
  rhs=None, # type: Optional[str]
@@ -275,7 +474,13 @@ class EvalMarkerFunc(object):
275
474
  "{flipped_op}{lhs}".format(lhs=lhs, flipped_op=flipped_op)
276
475
  )
277
476
  self._func = lambda value: cast(
278
- bool, version_specifier.contains(value, prereleases=True)
477
+ bool,
478
+ version_specifier.contains(
479
+ # N.B.: This handles `implementation_version` for Python dev releases in the
480
+ # same way `packaging` does.
481
+ (value + "local") if value.endswith("+") else value,
482
+ prereleases=True,
483
+ ),
279
484
  )
280
485
  else:
281
486
  oper = _OPERATORS[op]
@@ -284,7 +489,13 @@ class EvalMarkerFunc(object):
284
489
  if is_version_comparison:
285
490
  version_specifier = Specifier("{op}{rhs}".format(op=op, rhs=rhs))
286
491
  self._func = lambda value: cast(
287
- bool, version_specifier.contains(value, prereleases=True)
492
+ bool,
493
+ version_specifier.contains(
494
+ # N.B.: This handles `implementation_version` for Python dev releases in the
495
+ # same way `packaging` does.
496
+ (value + "local") if value.endswith("+") else value,
497
+ prereleases=True,
498
+ ),
288
499
  )
289
500
  else:
290
501
  oper = _OPERATORS[op]
@@ -299,8 +510,158 @@ class EvalMarkerFunc(object):
299
510
  def __call__(self, marker_env):
300
511
  # type: (MarkerEnv) -> bool
301
512
 
302
- values = self._get_values(marker_env)
303
- return any(map(self._func, values)) if values else True
513
+ return self._get_values(marker_env).apply(self._func)
514
+
515
+
516
+ class ExtraMarkersVisitor(MarkerVisitor[str]):
517
+ def __init__(self):
518
+ # type: () -> None
519
+ self.conflicts = OrderedSet() # type: OrderedSet[str]
520
+ self._marker_values = {} # type: Dict[str, Tuple[str, OrderedSet[str], bool]]
521
+
522
+ def visit_op(
523
+ self,
524
+ lhs, # type: Any
525
+ op, # type: Any
526
+ rhs, # type: Any
527
+ marker, # type: Marker
528
+ context, # type: str
529
+ ):
530
+ # type: (...) -> None
531
+
532
+ op_symbol = str(op)
533
+ if is_variable(lhs):
534
+ name = str(lhs)
535
+ value = str(rhs)
536
+ elif is_variable(rhs):
537
+ name = str(rhs)
538
+ value = str(lhs)
539
+ else:
540
+ return
541
+
542
+ if name in (
543
+ "extra",
544
+ "os_name",
545
+ "platform_system",
546
+ "sys_platform",
547
+ "platform_python_implementation",
548
+ "python_version",
549
+ "python_full_version",
550
+ ):
551
+ return
552
+
553
+ if op_symbol == "==":
554
+ requirement, values, inclusive = self._marker_values.setdefault(
555
+ name, (context, OrderedSet(), True)
556
+ )
557
+ if not inclusive:
558
+ self.conflicts.add(
559
+ "The requirement {context} includes {value} for {name} but the requirement "
560
+ "{requirement} established {name} as an exclusive set with values: "
561
+ "{values}.".format(
562
+ context=context,
563
+ value=value,
564
+ name=name,
565
+ requirement=requirement,
566
+ values=" ".join(values),
567
+ )
568
+ )
569
+ else:
570
+ values.add(value)
571
+ elif op_symbol == "!=":
572
+ requirement, values, inclusive = self._marker_values.setdefault(
573
+ name, (context, OrderedSet(), False)
574
+ )
575
+ if inclusive:
576
+ self.conflicts.add(
577
+ "The requirement {context} excludes {value} for {name} but the requirement "
578
+ "{requirement} established {name} as an inclusive set with values: "
579
+ "{values}.".format(
580
+ context=context,
581
+ value=value,
582
+ name=name,
583
+ requirement=requirement,
584
+ values=" ".join(values),
585
+ )
586
+ )
587
+ else:
588
+ values.add(value)
589
+ else:
590
+ pex_warnings.warn(
591
+ "Cannot split universal lock on all clauses of the marker in `{requirement}`.\n"
592
+ "The clause `{lhs} {op} {rhs}` uses comparison `{op}` but only `==` and `!=` are "
593
+ "supported for splitting on '{name}'.\n"
594
+ "Ignoring this clause in split calculations; lock results may be "
595
+ "unexpected.".format(requirement=context, lhs=lhs, op=op, rhs=rhs, name=name)
596
+ )
597
+
598
+ def marker_values(self):
599
+ # type: () -> Tuple[Values, ...]
600
+ return tuple(
601
+ Values(marker_name=name, values=tuple(values), inclusive=inclusive)
602
+ for name, (_, values, inclusive) in self._marker_values.items()
603
+ )
604
+
605
+
606
+ @attr.s(frozen=True)
607
+ class ExtraMarkers(object):
608
+ @classmethod
609
+ def extract(cls, requirements):
610
+ # type: (Iterable[Tuple[Marker, str]]) -> Optional[ExtraMarkers]
611
+
612
+ visitor = ExtraMarkersVisitor()
613
+ marker_parser = MarkerParser(visitor)
614
+
615
+ markers = [] # type: List[Marker]
616
+ for marker, provenance in requirements:
617
+ marker_parser.parse(marker, context=provenance)
618
+ markers.append(marker)
619
+
620
+ if visitor.conflicts:
621
+ raise ValueError(
622
+ "Encountered {count} {conflicts} when extracting universal lock splits from "
623
+ "top-level requirement markers:\n{items}".format(
624
+ count=len(visitor.conflicts),
625
+ conflicts=pluralize(visitor.conflicts, "conflict"),
626
+ items="\n".join(
627
+ "{index}. {conflict}".format(index=index, conflict=conflict)
628
+ for index, conflict in enumerate(visitor.conflicts, start=1)
629
+ ),
630
+ )
631
+ )
632
+
633
+ return cls(markers=tuple(markers), marker_values=visitor.marker_values())
634
+
635
+ @classmethod
636
+ def from_dict(cls, data):
637
+ # type: (Dict[str, Any]) -> ExtraMarkers
638
+ return cls(
639
+ markers=tuple(Marker(marker) for marker in data.pop("markers", ())),
640
+ marker_values=tuple(
641
+ Values.from_dict(marker_values) for marker_values in data.pop("marker_values", ())
642
+ ),
643
+ )
644
+
645
+ markers = attr.ib(default=()) # type: Tuple[Marker, ...]
646
+ marker_values = attr.ib(default=()) # type: Tuple[Values, ...]
647
+
648
+ def to_dict(self):
649
+ # type: () -> Dict[str, Any]
650
+ return {
651
+ "markers": [str(marker) for marker in self.markers],
652
+ "marker_values": [values.to_dict() for values in self.marker_values],
653
+ }
654
+
655
+ def get_values(self, marker_name):
656
+ # type: (str) -> Values
657
+ for values in self.marker_values:
658
+ if marker_name == values.marker_name:
659
+ return values
660
+ return Values(marker_name)
661
+
662
+ def __iter__(self):
663
+ # type: () -> Iterator[Marker]
664
+ return iter(self.markers)
304
665
 
305
666
 
306
667
  @attr.s(frozen=True)
@@ -353,7 +714,7 @@ class MarkerEnv(object):
353
714
  sys_platforms.append("win32")
354
715
 
355
716
  return cls(
356
- extras=SortedTuple(ProjectName(extra).normalized for extra in (extras or [""])),
717
+ extras=SortedTuple(ProjectName(extra).normalized for extra in extras),
357
718
  os_names=SortedTuple(os_names),
358
719
  platform_systems=SortedTuple(platform_systems),
359
720
  sys_platforms=SortedTuple(sys_platforms),
@@ -365,23 +726,17 @@ class MarkerEnv(object):
365
726
  ".".join(map(str, python_full_version))
366
727
  for python_full_version in python_full_versions
367
728
  ),
729
+ extra_markers=universal_target.extra_markers if universal_target else ExtraMarkers(),
368
730
  )
369
731
 
370
- extras = attr.ib(converter=SortedTuple, default=SortedTuple()) # type: SortedTuple[str]
371
- os_names = attr.ib(converter=SortedTuple, default=SortedTuple()) # type: SortedTuple[str]
372
- platform_systems = attr.ib(
373
- converter=SortedTuple, default=SortedTuple()
374
- ) # type: SortedTuple[str]
375
- sys_platforms = attr.ib(converter=SortedTuple, default=SortedTuple()) # type: SortedTuple[str]
376
- platform_python_implementations = attr.ib(
377
- converter=SortedTuple, default=SortedTuple()
378
- ) # type: SortedTuple[str]
379
- python_versions = attr.ib(
380
- converter=SortedTuple, default=SortedTuple()
381
- ) # type: SortedTuple[str]
382
- python_full_versions = attr.ib(
383
- converter=SortedTuple, default=SortedTuple()
384
- ) # type: SortedTuple[str]
732
+ extras = attr.ib() # type: SortedTuple[str]
733
+ os_names = attr.ib() # type: SortedTuple[str]
734
+ platform_systems = attr.ib() # type: SortedTuple[str]
735
+ sys_platforms = attr.ib() # type: SortedTuple[str]
736
+ platform_python_implementations = attr.ib() # type: SortedTuple[str]
737
+ python_versions = attr.ib() # type: SortedTuple[str]
738
+ python_full_versions = attr.ib() # type: SortedTuple[str]
739
+ extra_markers = attr.ib() # type: ExtraMarkers
385
740
 
386
741
  def as_dict(self):
387
742
  # type: () -> Dict[str, Any]
@@ -426,9 +781,46 @@ def _as_python_version_marker(specifier):
426
781
 
427
782
  @attr.s(frozen=True)
428
783
  class UniversalTarget(object):
784
+ @classmethod
785
+ def from_dict(cls, data):
786
+ # type: (Dict[str, Any]) -> UniversalTarget
787
+
788
+ raw_implementation = data.pop("implementation", None)
789
+ implementation = None # type: Optional[InterpreterImplementation.Value]
790
+ if raw_implementation:
791
+ if not isinstance(raw_implementation, string):
792
+ raise AssertionError(
793
+ reportable_unexpected_error_msg(
794
+ "Expected UniversalTarget `implementation` value to be a str, found "
795
+ "{value} of type {type}.",
796
+ value=raw_implementation,
797
+ type=type(raw_implementation),
798
+ )
799
+ )
800
+ implementation = InterpreterImplementation.for_value(raw_implementation)
801
+
802
+ return cls(
803
+ implementation=implementation,
804
+ requires_python=tuple(
805
+ SpecifierSet(specifier) for specifier in data.pop("requires_python", ())
806
+ ),
807
+ systems=tuple(TargetSystem.for_value(system) for system in data.pop("systems", ())),
808
+ extra_markers=ExtraMarkers.from_dict(data.pop("extra_markers", {})),
809
+ )
810
+
429
811
  implementation = attr.ib(default=None) # type: Optional[InterpreterImplementation.Value]
430
812
  requires_python = attr.ib(default=()) # type: Tuple[SpecifierSet, ...]
431
813
  systems = attr.ib(default=()) # type: Tuple[TargetSystem.Value, ...]
814
+ extra_markers = attr.ib(default=ExtraMarkers()) # type: ExtraMarkers
815
+
816
+ def to_dict(self):
817
+ # type: () -> Dict[str, Any]
818
+ return {
819
+ "implementation": str(self.implementation) if self.implementation else None,
820
+ "requires_python": [str(specifier) for specifier in self.requires_python],
821
+ "systems": [str(system) for system in self.systems],
822
+ "extra_markers": self.extra_markers.to_dict(),
823
+ }
432
824
 
433
825
  def iter_interpreter_constraints(self):
434
826
  # type: () -> Iterator[InterpreterConstraint]
@@ -456,8 +848,8 @@ class UniversalTarget(object):
456
848
  marker_envs = OrderedSet(
457
849
  MarkerEnv.create(
458
850
  extras=(),
459
- universal_target=UniversalTarget(
460
- implementation=self.implementation,
851
+ universal_target=attr.evolve(
852
+ self,
461
853
  requires_python=tuple(
462
854
  [SpecifierSet("=={version}".format(version=".".join(map(str, version))))]
463
855
  ),
@@ -512,4 +904,5 @@ class UniversalTarget(object):
512
904
  clauses.append(
513
905
  "({requires_pythons})".format(requires_pythons=" or ".join(python_version_markers))
514
906
  )
907
+ clauses.extend("({marker})".format(marker=marker) for marker in self.extra_markers)
515
908
  return Marker(" and ".join(clauses)) if clauses else None