pex 2.61.0__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 (46) hide show
  1. pex/cache/dirs.py +3 -8
  2. pex/cli/commands/pip/wheel.py +1 -1
  3. pex/docs/html/_pagefind/fragment/en_19d749c.pf_fragment +0 -0
  4. pex/docs/html/_pagefind/fragment/en_358f3c1.pf_fragment +0 -0
  5. pex/docs/html/_pagefind/fragment/{en_7f63cbe.pf_fragment → en_4a62b9a.pf_fragment} +0 -0
  6. pex/docs/html/_pagefind/fragment/{en_9ce19f0.pf_fragment → en_5c1928c.pf_fragment} +0 -0
  7. pex/docs/html/_pagefind/fragment/en_a6b0f05.pf_fragment +0 -0
  8. pex/docs/html/_pagefind/fragment/en_a77f515.pf_fragment +0 -0
  9. pex/docs/html/_pagefind/fragment/en_ed65506.pf_fragment +0 -0
  10. pex/docs/html/_pagefind/fragment/{en_cd3f4ce.pf_fragment → en_f5aab8f.pf_fragment} +0 -0
  11. pex/docs/html/_pagefind/index/en_856ae3c.pf_index +0 -0
  12. pex/docs/html/_pagefind/pagefind-entry.json +1 -1
  13. pex/docs/html/_pagefind/pagefind.en_92d55346bf.pf_meta +0 -0
  14. pex/docs/html/_static/documentation_options.js +1 -1
  15. pex/docs/html/api/vars.html +5 -5
  16. pex/docs/html/buildingpex.html +5 -5
  17. pex/docs/html/genindex.html +5 -5
  18. pex/docs/html/index.html +5 -5
  19. pex/docs/html/recipes.html +5 -5
  20. pex/docs/html/scie.html +5 -5
  21. pex/docs/html/search.html +5 -5
  22. pex/docs/html/whatispex.html +5 -5
  23. pex/resolve/locker.py +6 -51
  24. pex/resolve/locker_patches.py +123 -209
  25. pex/resolve/lockfile/create.py +26 -12
  26. pex/resolve/lockfile/targets.py +292 -26
  27. pex/resolve/pre_resolved_resolver.py +21 -12
  28. pex/resolve/requirement_configuration.py +15 -8
  29. pex/resolve/target_system.py +512 -119
  30. pex/resolver.py +269 -118
  31. pex/venv/venv_pex.py +1 -1
  32. pex/version.py +1 -1
  33. {pex-2.61.0.dist-info → pex-2.62.0.dist-info}/METADATA +4 -4
  34. {pex-2.61.0.dist-info → pex-2.62.0.dist-info}/RECORD +39 -39
  35. pex/docs/html/_pagefind/fragment/en_5c93d32.pf_fragment +0 -0
  36. pex/docs/html/_pagefind/fragment/en_6262b88.pf_fragment +0 -0
  37. pex/docs/html/_pagefind/fragment/en_d1987a6.pf_fragment +0 -0
  38. pex/docs/html/_pagefind/fragment/en_d7a44fe.pf_fragment +0 -0
  39. pex/docs/html/_pagefind/fragment/en_f6e4117.pf_fragment +0 -0
  40. pex/docs/html/_pagefind/index/en_3ad4a26.pf_index +0 -0
  41. pex/docs/html/_pagefind/pagefind.en_50edc69c3c.pf_meta +0 -0
  42. {pex-2.61.0.dist-info → pex-2.62.0.dist-info}/WHEEL +0 -0
  43. {pex-2.61.0.dist-info → pex-2.62.0.dist-info}/entry_points.txt +0 -0
  44. {pex-2.61.0.dist-info → pex-2.62.0.dist-info}/licenses/LICENSE +0 -0
  45. {pex-2.61.0.dist-info → pex-2.62.0.dist-info}/pylock/pylock.toml +0 -0
  46. {pex-2.61.0.dist-info → pex-2.62.0.dist-info}/top_level.txt +0 -0
pex/resolver.py CHANGED
@@ -46,6 +46,7 @@ from pex.pep_427 import InstallableType, WheelError, install_wheel_chroot
46
46
  from pex.pep_503 import ProjectName
47
47
  from pex.pip.download_observer import DownloadObserver
48
48
  from pex.pip.installation import get_pip
49
+ from pex.pip.local_project import digest_local_project
49
50
  from pex.pip.tool import PackageIndexConfiguration
50
51
  from pex.pip.version import PipVersionValue
51
52
  from pex.requirements import LocalProjectRequirement, URLRequirement
@@ -149,9 +150,9 @@ class DownloadTarget(object):
149
150
  return self.target.id
150
151
 
151
152
 
152
- def _uniqued_targets(targets=None):
153
- # type: (Optional[Iterable[DownloadTarget]]) -> Tuple[DownloadTarget, ...]
154
- return tuple(OrderedSet(targets)) if targets is not None else ()
153
+ def _uniqued_download_requests(requests=None):
154
+ # type: (Optional[Iterable[DownloadRequest]]) -> Tuple[DownloadRequest, ...]
155
+ return tuple(OrderedSet(requests)) if requests is not None else ()
155
156
 
156
157
 
157
158
  @attr.s(frozen=True)
@@ -238,12 +239,9 @@ class PipLogManager(object):
238
239
 
239
240
 
240
241
  @attr.s(frozen=True)
241
- class DownloadRequest(object):
242
- download_targets = attr.ib(converter=_uniqued_targets) # type: Tuple[DownloadTarget, ...]
242
+ class _DownloadSession(object):
243
+ requests = attr.ib(converter=_uniqued_download_requests) # type: Tuple[DownloadRequest, ...]
243
244
  direct_requirements = attr.ib() # type: Iterable[ParsedRequirement]
244
- requirements = attr.ib(default=None) # type: Optional[Iterable[str]]
245
- requirement_files = attr.ib(default=None) # type: Optional[Iterable[str]]
246
- constraint_files = attr.ib(default=None) # type: Optional[Iterable[str]]
247
245
  allow_prereleases = attr.ib(default=False) # type: bool
248
246
  transitive = attr.ib(default=True) # type: bool
249
247
  package_index_configuration = attr.ib(default=None) # type: Optional[PackageIndexConfiguration]
@@ -260,14 +258,17 @@ class DownloadRequest(object):
260
258
  # type: () -> Iterator[BuildRequest]
261
259
  for requirement in self.direct_requirements:
262
260
  if isinstance(requirement, LocalProjectRequirement):
263
- for download_target in self.download_targets:
264
- yield BuildRequest.create(
265
- target=download_target.target, source_path=requirement.path
261
+ for request in self.requests:
262
+ yield BuildRequest.for_directory(
263
+ target=request.target,
264
+ source_path=requirement.path,
265
+ resolver=self.resolver,
266
+ pip_version=self.pip_version,
266
267
  )
267
268
 
268
269
  def download_distributions(self, dest=None, max_parallel_jobs=None):
269
270
  # type: (...) -> List[DownloadResult]
270
- if not self.requirements and not self.requirement_files:
271
+ if not self.requests or not any(request.has_requirements for request in self.requests):
271
272
  # Nothing to resolve.
272
273
  return []
273
274
 
@@ -275,48 +276,32 @@ class DownloadRequest(object):
275
276
  prefix="resolver_download.", dir=safe_mkdir(CacheDir.DOWNLOADS.path(".tmp"))
276
277
  )
277
278
 
278
- log_manager = PipLogManager.create(self.pip_log, self.download_targets)
279
+ log_manager = PipLogManager.create(
280
+ self.pip_log,
281
+ download_targets=tuple(request.download_target for request in self.requests),
282
+ )
279
283
  if self.pip_log and not self.pip_log.user_specified:
280
284
  TRACER.log(
281
285
  "Preserving `pip download` log at {log_path}".format(log_path=self.pip_log.path),
282
286
  V=ENV.PEX_VERBOSE,
283
287
  )
284
288
 
285
- requirement_config = RequirementConfiguration(
286
- requirements=self.requirements,
287
- requirement_files=self.requirement_files,
288
- constraint_files=self.constraint_files,
289
- )
290
- network_configuration = (
291
- self.package_index_configuration.network_configuration
292
- if self.package_index_configuration
293
- else None
294
- )
295
- subdirectory_by_filename = {} # type: Dict[str, str]
296
- for parsed_requirement in requirement_config.parse_requirements(network_configuration):
297
- if not isinstance(parsed_requirement, URLRequirement):
298
- continue
299
- subdirectory = parsed_requirement.subdirectory
300
- if subdirectory:
301
- subdirectory_by_filename[parsed_requirement.filename] = subdirectory
302
-
303
289
  spawn_download = functools.partial(
304
290
  self._spawn_download,
305
291
  resolved_dists_dir=dest,
306
292
  log_manager=log_manager,
307
- subdirectory_by_filename=subdirectory_by_filename,
308
293
  )
309
294
  with TRACER.timed(
310
295
  "Resolving for:\n {}".format(
311
- "\n ".join(target.render_description() for target in self.download_targets)
296
+ "\n ".join(request.render_description() for request in self.requests)
312
297
  )
313
298
  ):
314
299
  try:
315
300
  return list(
316
301
  execute_parallel(
317
- inputs=self.download_targets,
302
+ inputs=self.requests,
318
303
  spawn_func=spawn_download,
319
- error_handler=Raise[DownloadTarget, DownloadResult](Unsatisfiable),
304
+ error_handler=Raise[DownloadRequest, DownloadResult](Unsatisfiable),
320
305
  max_jobs=max_parallel_jobs,
321
306
  )
322
307
  )
@@ -325,13 +310,31 @@ class DownloadRequest(object):
325
310
 
326
311
  def _spawn_download(
327
312
  self,
328
- download_target, # type: DownloadTarget
313
+ request, # type: DownloadRequest
329
314
  resolved_dists_dir, # type: str
330
315
  log_manager, # type: PipLogManager
331
- subdirectory_by_filename, # type: Mapping[str, str]
332
316
  ):
333
317
  # type: (...) -> SpawnedJob[DownloadResult]
334
318
 
319
+ requirement_config = RequirementConfiguration(
320
+ requirements=request.requirements,
321
+ requirement_files=request.requirement_files,
322
+ constraint_files=request.constraint_files,
323
+ )
324
+ network_configuration = (
325
+ self.package_index_configuration.network_configuration
326
+ if self.package_index_configuration
327
+ else None
328
+ )
329
+ subdirectory_by_filename = {} # type: Dict[str, str]
330
+ for parsed_requirement in requirement_config.parse_requirements(network_configuration):
331
+ if not isinstance(parsed_requirement, URLRequirement):
332
+ continue
333
+ subdirectory = parsed_requirement.subdirectory
334
+ if subdirectory:
335
+ subdirectory_by_filename[parsed_requirement.filename] = subdirectory
336
+
337
+ download_target = request.download_target
335
338
  download_dir = os.path.join(resolved_dists_dir, download_target.id(complete=True))
336
339
  observer = (
337
340
  self.observer.observe_download(
@@ -354,9 +357,9 @@ class DownloadRequest(object):
354
357
  ),
355
358
  ).spawn_download_distributions(
356
359
  download_dir=download_dir,
357
- requirements=self.requirements,
358
- requirement_files=self.requirement_files,
359
- constraint_files=self.constraint_files,
360
+ requirements=request.requirements,
361
+ requirement_files=request.requirement_files,
362
+ constraint_files=request.constraint_files,
360
363
  allow_prereleases=self.allow_prereleases,
361
364
  transitive=self.transitive,
362
365
  target=target,
@@ -396,7 +399,14 @@ class DownloadResult(object):
396
399
  subdirectory = self.subdirectory_by_filename.get(
397
400
  os.path.basename(distribution_path)
398
401
  )
399
- yield BuildRequest.create(
402
+ production_assert(
403
+ os.path.isfile(distribution_path),
404
+ (
405
+ "Pip download results should always be files and never local project "
406
+ "directories."
407
+ ),
408
+ )
409
+ yield BuildRequest.for_file(
400
410
  target=self.download_target,
401
411
  source_path=distribution_path,
402
412
  subdirectory=subdirectory,
@@ -415,26 +425,46 @@ class IntegrityError(Exception):
415
425
  pass
416
426
 
417
427
 
418
- def fingerprint_path(path):
428
+ if TYPE_CHECKING:
429
+ from pex.hashing import Hasher
430
+
431
+
432
+ def _hasher():
433
+ # type: () -> Hasher
434
+
435
+ return hashlib.sha256()
436
+
437
+
438
+ def _fingerprint_file(path):
419
439
  # type: (str) -> str
440
+ return CacheHelper.hash(path, digest=_hasher())
420
441
 
421
- # We switched from sha1 to sha256 at the transition from using `pip install --target` to
422
- # `pip install --prefix` to serve two purposes:
423
- # 1. Insulate the new installation scheme from the old.
424
- # 2. Move past sha1 which was shown to have practical collision attacks in 2019.
425
- #
426
- # The installation scheme switch was the primary purpose and switching hashes proved a pragmatic
427
- # insulation. If the `pip install --prefix` re-arrangement scheme evolves, then some other
428
- # option than switching hashing algorithms will be needed, like post-fixing a running version
429
- # integer or just mixing one into the hashed content.
430
- #
431
- # See: https://github.com/pex-tool/pex/issues/1655 for a general overview of these cache
432
- # structure concerns.
433
- hasher = hashlib.sha256
434
442
 
435
- if os.path.isdir(path):
436
- return CacheHelper.dir_hash(path, hasher=hasher)
437
- return CacheHelper.hash(path, hasher=hasher)
443
+ def _fingerprint_directory(path):
444
+ # type: (str) -> str
445
+ return CacheHelper.dir_hash(path, digest=_hasher())
446
+
447
+
448
+ def _fingerprint_local_project(
449
+ path, # type: str
450
+ target, # type: Target
451
+ resolver=None, # type: Optional[Resolver]
452
+ pip_version=None, # type: Optional[PipVersionValue]
453
+ ):
454
+ if resolver:
455
+ build_system_resolver = resolver
456
+ else:
457
+ from pex.resolve.configured_resolver import ConfiguredResolver
458
+
459
+ build_system_resolver = ConfiguredResolver.default()
460
+
461
+ return digest_local_project(
462
+ directory=path,
463
+ digest=_hasher(),
464
+ target=target,
465
+ resolver=build_system_resolver,
466
+ pip_version=pip_version,
467
+ )
438
468
 
439
469
 
440
470
  class BuildError(Exception):
@@ -449,14 +479,14 @@ def _as_download_target(target):
449
479
  @attr.s(frozen=True)
450
480
  class BuildRequest(object):
451
481
  @classmethod
452
- def create(
482
+ def for_file(
453
483
  cls,
454
484
  target, # type: Union[DownloadTarget, Target]
455
485
  source_path, # type: str
456
486
  subdirectory=None, # type: Optional[str]
457
487
  ):
458
488
  # type: (...) -> BuildRequest
459
- fingerprint = fingerprint_path(source_path)
489
+ fingerprint = _fingerprint_file(source_path)
460
490
  return cls(
461
491
  download_target=_as_download_target(target),
462
492
  source_path=source_path,
@@ -464,6 +494,30 @@ class BuildRequest(object):
464
494
  subdirectory=subdirectory,
465
495
  )
466
496
 
497
+ @classmethod
498
+ def for_directory(
499
+ cls,
500
+ target, # type: Union[DownloadTarget, Target]
501
+ source_path, # type: str
502
+ subdirectory=None, # type: Optional[str]
503
+ resolver=None, # type: Optional[Resolver]
504
+ pip_version=None, # type: Optional[PipVersionValue]
505
+ ):
506
+ # type: (...) -> BuildRequest
507
+ download_target = _as_download_target(target)
508
+ fingerprint = _fingerprint_local_project(
509
+ path=source_path,
510
+ target=download_target.target,
511
+ resolver=resolver,
512
+ pip_version=pip_version,
513
+ )
514
+ return cls(
515
+ download_target=download_target,
516
+ source_path=source_path,
517
+ fingerprint=fingerprint,
518
+ subdirectory=subdirectory,
519
+ )
520
+
467
521
  download_target = attr.ib(converter=_as_download_target) # type: DownloadTarget
468
522
  source_path = attr.ib() # type: str
469
523
  fingerprint = attr.ib() # type: str
@@ -635,7 +689,7 @@ class InstallRequest(object):
635
689
  was_built_locally=False, # type: bool
636
690
  ):
637
691
  # type: (...) -> InstallRequest
638
- fingerprint = fingerprint_path(wheel_path)
692
+ fingerprint = _fingerprint_file(wheel_path)
639
693
  return cls(
640
694
  download_target=_as_download_target(target),
641
695
  wheel_path=wheel_path,
@@ -768,7 +822,7 @@ class InstallResult(object):
768
822
  else:
769
823
  cached_fingerprint = installed_wheel.fingerprint
770
824
 
771
- wheel_dir_hash = cached_fingerprint or fingerprint_path(self.install_chroot)
825
+ wheel_dir_hash = cached_fingerprint or _fingerprint_directory(self.install_chroot)
772
826
  runtime_key_dir = os.path.join(self._installation_root, wheel_dir_hash)
773
827
  with atomic_directory(runtime_key_dir) as atomic_dir:
774
828
  if not atomic_dir.is_finalized():
@@ -1086,8 +1140,17 @@ class BuildAndInstallRequest(object):
1086
1140
  )
1087
1141
  if is_wheel(dist_path):
1088
1142
  to_install.add(InstallRequest.create(install_request.target, dist_path))
1143
+ elif os.path.isdir(dist_path):
1144
+ to_build.add(
1145
+ BuildRequest.for_directory(
1146
+ install_request.target,
1147
+ dist_path,
1148
+ resolver=self._resolver,
1149
+ pip_version=self._pip_version,
1150
+ )
1151
+ )
1089
1152
  else:
1090
- to_build.add(BuildRequest.create(install_request.target, dist_path))
1153
+ to_build.add(BuildRequest.for_file(install_request.target, dist_path))
1091
1154
  already_analyzed.add(metadata.project_name)
1092
1155
 
1093
1156
  all_install_requests = OrderedSet(install_requests)
@@ -1251,7 +1314,7 @@ def _parse_reqs(
1251
1314
  requirement_files=None, # type: Optional[Iterable[str]]
1252
1315
  network_configuration=None, # type: Optional[NetworkConfiguration]
1253
1316
  ):
1254
- # type: (...) -> Iterable[ParsedRequirement]
1317
+ # type: (...) -> Tuple[ParsedRequirement, ...]
1255
1318
  requirement_configuration = RequirementConfiguration(
1256
1319
  requirements=requirements, requirement_files=requirement_files
1257
1320
  )
@@ -1318,6 +1381,23 @@ def resolve(
1318
1381
  :raises ValueError: If `build=False` and `use_wheel=False`.
1319
1382
  """
1320
1383
 
1384
+ if not build_configuration.allow_wheels:
1385
+ foreign_targets = [
1386
+ target
1387
+ for target in targets.unique_targets()
1388
+ if not isinstance(target, LocalInterpreter)
1389
+ ]
1390
+ if foreign_targets:
1391
+ raise ValueError(
1392
+ "Cannot ignore wheels (use_wheel=False) when resolving for foreign {platforms}: "
1393
+ "{foreign_platforms}".format(
1394
+ platforms=pluralize(foreign_targets, "platform"),
1395
+ foreign_platforms=", ".join(
1396
+ target.render_description() for target in foreign_targets
1397
+ ),
1398
+ )
1399
+ )
1400
+
1321
1401
  # A resolve happens in four stages broken into two phases:
1322
1402
  # 1. Download phase: resolves sdists and wheels in a single operation per distribution target.
1323
1403
  # 2. Install phase:
@@ -1359,29 +1439,19 @@ def resolve(
1359
1439
  keyring_provider=keyring_provider,
1360
1440
  )
1361
1441
 
1362
- if not build_configuration.allow_wheels:
1363
- foreign_targets = [
1364
- target
1365
- for target in targets.unique_targets()
1366
- if not isinstance(target, LocalInterpreter)
1367
- ]
1368
- if foreign_targets:
1369
- raise ValueError(
1370
- "Cannot ignore wheels (use_wheel=False) when resolving for foreign {platforms}: "
1371
- "{foreign_platforms}".format(
1372
- platforms=pluralize(foreign_targets, "platform"),
1373
- foreign_platforms=", ".join(
1374
- target.render_description() for target in foreign_targets
1375
- ),
1376
- )
1377
- )
1442
+ requests = tuple(
1443
+ DownloadRequest(
1444
+ download_target=DownloadTarget(target=target),
1445
+ requirements=requirements,
1446
+ requirement_files=requirement_files,
1447
+ constraint_files=constraint_files,
1448
+ )
1449
+ for target in targets.unique_targets()
1450
+ )
1378
1451
 
1379
1452
  build_requests, download_results = _download_internal(
1380
- targets=targets,
1453
+ requests=requests,
1381
1454
  direct_requirements=direct_requirements,
1382
- requirements=requirements,
1383
- requirement_files=requirement_files,
1384
- constraint_files=constraint_files,
1385
1455
  allow_prereleases=allow_prereleases,
1386
1456
  transitive=transitive,
1387
1457
  package_index_configuration=package_index_configuration,
@@ -1429,11 +1499,8 @@ def resolve(
1429
1499
 
1430
1500
 
1431
1501
  def _download_internal(
1432
- targets, # type: Targets
1502
+ requests, # type: Tuple[DownloadRequest, ...]
1433
1503
  direct_requirements, # type: Iterable[ParsedRequirement]
1434
- requirements=None, # type: Optional[Iterable[str]]
1435
- requirement_files=None, # type: Optional[Iterable[str]]
1436
- constraint_files=None, # type: Optional[Iterable[str]]
1437
1504
  allow_prereleases=False, # type: bool
1438
1505
  transitive=True, # type: bool
1439
1506
  package_index_configuration=None, # type: Optional[PackageIndexConfiguration]
@@ -1445,27 +1512,12 @@ def _download_internal(
1445
1512
  pip_version=None, # type: Optional[PipVersionValue]
1446
1513
  resolver=None, # type: Optional[Resolver]
1447
1514
  dependency_configuration=DependencyConfiguration(), # type: DependencyConfiguration
1448
- universal_targets=(), # type: Iterable[UniversalTarget]
1449
1515
  ):
1450
1516
  # type: (...) -> Tuple[List[BuildRequest], List[DownloadResult]]
1451
1517
 
1452
- unique_targets = targets.unique_targets()
1453
- if universal_targets:
1454
- production_assert(len(unique_targets) == 1)
1455
- target = unique_targets.pop()
1456
- download_targets = tuple(
1457
- DownloadTarget(target, universal_target=universal_target)
1458
- for universal_target in universal_targets
1459
- )
1460
- else:
1461
- download_targets = tuple(DownloadTarget(target) for target in unique_targets)
1462
-
1463
- download_request = DownloadRequest(
1464
- download_targets=download_targets,
1518
+ download_session = _DownloadSession(
1519
+ requests=requests,
1465
1520
  direct_requirements=direct_requirements,
1466
- requirements=requirements,
1467
- requirement_files=requirement_files,
1468
- constraint_files=constraint_files,
1469
1521
  allow_prereleases=allow_prereleases,
1470
1522
  transitive=transitive,
1471
1523
  package_index_configuration=package_index_configuration,
@@ -1477,8 +1529,8 @@ def _download_internal(
1477
1529
  dependency_configuration=dependency_configuration,
1478
1530
  )
1479
1531
 
1480
- local_projects = list(download_request.iter_local_projects())
1481
- download_results = download_request.download_distributions(
1532
+ local_projects = list(download_session.iter_local_projects())
1533
+ download_results = download_session.download_distributions(
1482
1534
  dest=dest, max_parallel_jobs=max_parallel_jobs
1483
1535
  )
1484
1536
  return local_projects, download_results
@@ -1496,10 +1548,6 @@ class LocalDistribution(object):
1496
1548
  # type: () -> Target
1497
1549
  return self.download_target.target
1498
1550
 
1499
- @fingerprint.default
1500
- def _calculate_fingerprint(self):
1501
- return fingerprint_path(self.path)
1502
-
1503
1551
  @property
1504
1552
  def is_wheel(self):
1505
1553
  return is_wheel(self.path) and zipfile.is_zipfile(self.path)
@@ -1542,7 +1590,6 @@ def download(
1542
1590
  extra_pip_requirements=(), # type: Tuple[Requirement, ...]
1543
1591
  keyring_provider=None, # type: Optional[str]
1544
1592
  dependency_configuration=DependencyConfiguration(), # type: DependencyConfiguration
1545
- universal_targets=(), # type: Iterable[UniversalTarget]
1546
1593
  ):
1547
1594
  # type: (...) -> Downloaded
1548
1595
  """Downloads all distributions needed to meet requirements for multiple distribution targets.
@@ -1572,7 +1619,114 @@ def download(
1572
1619
  :raises ValueError: If a foreign platform was provided in `platforms`, and `use_wheel=False`.
1573
1620
  :raises ValueError: If `build=False` and `use_wheel=False`.
1574
1621
  """
1575
- direct_requirements = _parse_reqs(requirements, requirement_files, network_configuration)
1622
+ return download_requests(
1623
+ requests=tuple(
1624
+ DownloadRequest(
1625
+ download_target=DownloadTarget(target),
1626
+ requirements=requirements,
1627
+ requirement_files=requirement_files,
1628
+ constraint_files=constraint_files,
1629
+ )
1630
+ for target in targets.unique_targets()
1631
+ ),
1632
+ direct_requirements=_parse_reqs(requirements, requirement_files, network_configuration),
1633
+ allow_prereleases=allow_prereleases,
1634
+ transitive=transitive,
1635
+ repos_configuration=repos_configuration,
1636
+ resolver_version=resolver_version,
1637
+ network_configuration=network_configuration,
1638
+ build_configuration=build_configuration,
1639
+ dest=dest,
1640
+ max_parallel_jobs=max_parallel_jobs,
1641
+ observer=observer,
1642
+ pip_log=pip_log,
1643
+ pip_version=pip_version,
1644
+ resolver=resolver,
1645
+ use_pip_config=use_pip_config,
1646
+ extra_pip_requirements=extra_pip_requirements,
1647
+ keyring_provider=keyring_provider,
1648
+ dependency_configuration=dependency_configuration,
1649
+ )
1650
+
1651
+
1652
+ def _as_str_tuple(items):
1653
+ # type: (Optional[Iterable[str]]) -> Tuple[str, ...]
1654
+ if not items:
1655
+ return ()
1656
+ return items if isinstance(items, tuple) else tuple(items)
1657
+
1658
+
1659
+ @attr.s(frozen=True)
1660
+ class DownloadRequest(object):
1661
+ @classmethod
1662
+ def create(
1663
+ cls,
1664
+ target, # type: Target
1665
+ universal_target=None, # type: Optional[UniversalTarget]
1666
+ requirement_configuration=RequirementConfiguration(), # type: RequirementConfiguration
1667
+ provenance=None, # type: Optional[str]
1668
+ ):
1669
+ # type: (...) -> DownloadRequest
1670
+ return cls(
1671
+ download_target=DownloadTarget(target=target, universal_target=universal_target),
1672
+ requirements=requirement_configuration.requirements,
1673
+ requirement_files=requirement_configuration.requirement_files,
1674
+ constraint_files=requirement_configuration.constraint_files,
1675
+ provenance=provenance,
1676
+ )
1677
+
1678
+ download_target = attr.ib() # type: DownloadTarget
1679
+ requirements = attr.ib(default=(), converter=_as_str_tuple) # type: Tuple[str, ...]
1680
+ requirement_files = attr.ib(default=(), converter=_as_str_tuple) # type: Tuple[str, ...]
1681
+ constraint_files = attr.ib(default=(), converter=_as_str_tuple) # type: Tuple[str, ...]
1682
+ provenance = attr.ib(default=None) # type: Optional[str]
1683
+
1684
+ def render_description(self):
1685
+ # type: () -> str
1686
+ description = self.download_target.render_description()
1687
+ if not self.provenance:
1688
+ return description
1689
+ return "{description} from {provenance}".format(
1690
+ description=description, provenance=self.provenance
1691
+ )
1692
+
1693
+ @property
1694
+ def target(self):
1695
+ # type: () -> Target
1696
+ return self.download_target.target
1697
+
1698
+ @property
1699
+ def universal_target(self):
1700
+ # type: () -> Optional[UniversalTarget]
1701
+ return self.download_target.universal_target
1702
+
1703
+ @property
1704
+ def has_requirements(self):
1705
+ return bool(self.requirements) or bool(self.requirement_files)
1706
+
1707
+
1708
+ def download_requests(
1709
+ requests, # type: Tuple[DownloadRequest, ...]
1710
+ direct_requirements, # type: Tuple[ParsedRequirement, ...]
1711
+ allow_prereleases=False, # type: bool
1712
+ transitive=True, # type: bool
1713
+ repos_configuration=ReposConfiguration(), # type: ReposConfiguration
1714
+ resolver_version=None, # type: Optional[ResolverVersion.Value]
1715
+ network_configuration=None, # type: Optional[NetworkConfiguration]
1716
+ build_configuration=BuildConfiguration(), # type: BuildConfiguration
1717
+ dest=None, # type: Optional[str]
1718
+ max_parallel_jobs=None, # type: Optional[int]
1719
+ observer=None, # type: Optional[ResolveObserver]
1720
+ pip_log=None, # type: Optional[PipLog]
1721
+ pip_version=None, # type: Optional[PipVersionValue]
1722
+ resolver=None, # type: Optional[Resolver]
1723
+ use_pip_config=False, # type: bool
1724
+ extra_pip_requirements=(), # type: Tuple[Requirement, ...]
1725
+ keyring_provider=None, # type: Optional[str]
1726
+ dependency_configuration=DependencyConfiguration(), # type: DependencyConfiguration
1727
+ ):
1728
+ # type: (...) -> Downloaded
1729
+
1576
1730
  package_index_configuration = PackageIndexConfiguration.create(
1577
1731
  pip_version=pip_version,
1578
1732
  resolver_version=resolver_version,
@@ -1582,12 +1736,10 @@ def download(
1582
1736
  extra_pip_requirements=extra_pip_requirements,
1583
1737
  keyring_provider=keyring_provider,
1584
1738
  )
1739
+
1585
1740
  build_requests, download_results = _download_internal(
1586
- targets=targets,
1741
+ requests=requests,
1587
1742
  direct_requirements=direct_requirements,
1588
- requirements=requirements,
1589
- requirement_files=requirement_files,
1590
- constraint_files=constraint_files,
1591
1743
  allow_prereleases=allow_prereleases,
1592
1744
  transitive=transitive,
1593
1745
  package_index_configuration=package_index_configuration,
@@ -1599,7 +1751,6 @@ def download(
1599
1751
  pip_version=pip_version,
1600
1752
  resolver=resolver,
1601
1753
  dependency_configuration=dependency_configuration,
1602
- universal_targets=universal_targets,
1603
1754
  )
1604
1755
 
1605
1756
  local_distributions = []
pex/venv/venv_pex.py CHANGED
@@ -186,7 +186,7 @@ def boot(
186
186
  "_PEX_PATCHED_TAGS_FILE",
187
187
  # These are used by Pex's Pip venv to implement universal locks.
188
188
  "_PEX_PYTHON_VERSIONS_FILE",
189
- "_PEX_TARGET_SYSTEMS_FILE",
189
+ "_PEX_UNIVERSAL_TARGET_FILE",
190
190
  # This is used to implement Pex --exclude and --override support.
191
191
  "_PEX_DEP_CONFIG_FILE",
192
192
  # This is used to implement --source support.
pex/version.py CHANGED
@@ -1,4 +1,4 @@
1
1
  # Copyright 2015 Pex project contributors.
2
2
  # Licensed under the Apache License, Version 2.0 (see LICENSE).
3
3
 
4
- __version__ = "2.61.0"
4
+ __version__ = "2.62.0"
@@ -1,15 +1,15 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pex
3
- Version: 2.61.0
3
+ Version: 2.62.0
4
4
  Summary: The PEX packaging toolchain.
5
5
  Home-page: https://github.com/pex-tool/pex
6
- Download-URL: https://github.com/pex-tool/pex/releases/download/v2.61.0/pex
6
+ Download-URL: https://github.com/pex-tool/pex/releases/download/v2.62.0/pex
7
7
  Author: The PEX developers
8
8
  Author-email: developers@pex-tool.org
9
9
  License-Expression: Apache-2.0
10
- Project-URL: Changelog, https://github.com/pex-tool/pex/blob/v2.61.0/CHANGES.md
10
+ Project-URL: Changelog, https://github.com/pex-tool/pex/blob/v2.62.0/CHANGES.md
11
11
  Project-URL: Documentation, https://docs.pex-tool.org/
12
- Project-URL: Source, https://github.com/pex-tool/pex/tree/v2.61.0
12
+ Project-URL: Source, https://github.com/pex-tool/pex/tree/v2.62.0
13
13
  Keywords: package,executable,virtualenv,lock,freeze
14
14
  Classifier: Development Status :: 5 - Production/Stable
15
15
  Classifier: Intended Audience :: Developers