pex 2.54.2__py2.py3-none-any.whl → 2.69.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 (180) hide show
  1. pex/auth.py +1 -1
  2. pex/bin/pex.py +15 -2
  3. pex/build_backend/configuration.py +5 -5
  4. pex/build_backend/wrap.py +27 -23
  5. pex/build_system/pep_517.py +4 -1
  6. pex/cache/dirs.py +17 -12
  7. pex/cli/commands/lock.py +302 -165
  8. pex/cli/commands/pip/core.py +4 -12
  9. pex/cli/commands/pip/wheel.py +1 -1
  10. pex/cli/commands/run.py +13 -20
  11. pex/cli/commands/venv.py +85 -16
  12. pex/cli/pex.py +11 -4
  13. pex/common.py +57 -7
  14. pex/compatibility.py +1 -1
  15. pex/dependency_configuration.py +87 -15
  16. pex/dist_metadata.py +143 -25
  17. pex/docs/html/_pagefind/fragment/en_4250138.pf_fragment +0 -0
  18. pex/docs/html/_pagefind/fragment/en_7125dad.pf_fragment +0 -0
  19. pex/docs/html/_pagefind/fragment/en_785d562.pf_fragment +0 -0
  20. pex/docs/html/_pagefind/fragment/en_8e94bb8.pf_fragment +0 -0
  21. pex/docs/html/_pagefind/fragment/en_a0396bb.pf_fragment +0 -0
  22. pex/docs/html/_pagefind/fragment/en_a8a3588.pf_fragment +0 -0
  23. pex/docs/html/_pagefind/fragment/en_c07d988.pf_fragment +0 -0
  24. pex/docs/html/_pagefind/fragment/en_d718411.pf_fragment +0 -0
  25. pex/docs/html/_pagefind/index/en_a2e3c5e.pf_index +0 -0
  26. pex/docs/html/_pagefind/pagefind-entry.json +1 -1
  27. pex/docs/html/_pagefind/pagefind.en_4ce1afa9e3.pf_meta +0 -0
  28. pex/docs/html/_static/documentation_options.js +1 -1
  29. pex/docs/html/_static/pygments.css +164 -146
  30. pex/docs/html/_static/styles/furo.css +1 -1
  31. pex/docs/html/_static/styles/furo.css.map +1 -1
  32. pex/docs/html/api/vars.html +25 -34
  33. pex/docs/html/buildingpex.html +25 -34
  34. pex/docs/html/genindex.html +24 -33
  35. pex/docs/html/index.html +25 -34
  36. pex/docs/html/recipes.html +25 -34
  37. pex/docs/html/scie.html +25 -34
  38. pex/docs/html/search.html +24 -33
  39. pex/docs/html/whatispex.html +25 -34
  40. pex/entry_points_txt.py +98 -0
  41. pex/environment.py +54 -33
  42. pex/finders.py +1 -1
  43. pex/hashing.py +71 -9
  44. pex/installed_wheel.py +141 -0
  45. pex/interpreter.py +41 -38
  46. pex/interpreter_constraints.py +25 -25
  47. pex/interpreter_implementation.py +40 -0
  48. pex/jobs.py +13 -6
  49. pex/pep_376.py +68 -384
  50. pex/pep_425.py +11 -2
  51. pex/pep_427.py +937 -205
  52. pex/pep_508.py +4 -5
  53. pex/pex_builder.py +5 -8
  54. pex/pex_info.py +14 -9
  55. pex/pip/dependencies/__init__.py +85 -13
  56. pex/pip/dependencies/requires.py +38 -3
  57. pex/pip/foreign_platform/__init__.py +4 -3
  58. pex/pip/installation.py +2 -2
  59. pex/pip/local_project.py +6 -14
  60. pex/pip/package_repositories/__init__.py +78 -0
  61. pex/pip/package_repositories/link_collector.py +96 -0
  62. pex/pip/tool.py +139 -33
  63. pex/pip/vcs.py +109 -43
  64. pex/pip/version.py +8 -1
  65. pex/requirements.py +121 -16
  66. pex/resolve/config.py +5 -1
  67. pex/resolve/configured_resolve.py +32 -10
  68. pex/resolve/configured_resolver.py +10 -39
  69. pex/resolve/downloads.py +4 -3
  70. pex/resolve/lock_downloader.py +16 -23
  71. pex/resolve/lock_resolver.py +41 -51
  72. pex/resolve/locked_resolve.py +89 -32
  73. pex/resolve/locker.py +145 -101
  74. pex/resolve/locker_patches.py +123 -197
  75. pex/resolve/lockfile/create.py +232 -87
  76. pex/resolve/lockfile/download_manager.py +5 -1
  77. pex/resolve/lockfile/json_codec.py +103 -28
  78. pex/resolve/lockfile/model.py +13 -35
  79. pex/resolve/lockfile/pep_751.py +117 -98
  80. pex/resolve/lockfile/requires_dist.py +17 -262
  81. pex/resolve/lockfile/subset.py +11 -0
  82. pex/resolve/lockfile/targets.py +445 -0
  83. pex/resolve/lockfile/updater.py +22 -10
  84. pex/resolve/package_repository.py +406 -0
  85. pex/resolve/pex_repository_resolver.py +1 -1
  86. pex/resolve/pre_resolved_resolver.py +19 -16
  87. pex/resolve/project.py +233 -47
  88. pex/resolve/requirement_configuration.py +28 -10
  89. pex/resolve/resolver_configuration.py +18 -32
  90. pex/resolve/resolver_options.py +234 -28
  91. pex/resolve/resolvers.py +3 -12
  92. pex/resolve/target_options.py +18 -2
  93. pex/resolve/target_system.py +908 -0
  94. pex/resolve/venv_resolver.py +670 -0
  95. pex/resolver.py +673 -209
  96. pex/scie/__init__.py +40 -1
  97. pex/scie/model.py +2 -0
  98. pex/scie/science.py +25 -3
  99. pex/sdist.py +219 -0
  100. pex/sh_boot.py +24 -21
  101. pex/sysconfig.py +5 -3
  102. pex/targets.py +31 -10
  103. pex/third_party/__init__.py +1 -1
  104. pex/tools/commands/repository.py +48 -25
  105. pex/vendor/__init__.py +4 -9
  106. pex/vendor/__main__.py +65 -41
  107. pex/vendor/_vendored/ansicolors/.layout.json +1 -1
  108. pex/vendor/_vendored/ansicolors/ansicolors-1.1.8.dist-info/RECORD +11 -0
  109. pex/vendor/_vendored/ansicolors/ansicolors-1.1.8.pex-info/original-whl-info.json +1 -0
  110. pex/vendor/_vendored/appdirs/.layout.json +1 -1
  111. pex/vendor/_vendored/appdirs/appdirs-1.4.4.dist-info/RECORD +7 -0
  112. pex/vendor/_vendored/appdirs/appdirs-1.4.4.pex-info/original-whl-info.json +1 -0
  113. pex/vendor/_vendored/attrs/.layout.json +1 -1
  114. pex/vendor/_vendored/attrs/attrs-21.5.0.dev0.dist-info/RECORD +37 -0
  115. pex/vendor/_vendored/attrs/attrs-21.5.0.dev0.pex-info/original-whl-info.json +1 -0
  116. pex/vendor/_vendored/packaging_20_9/.layout.json +1 -1
  117. pex/vendor/_vendored/packaging_20_9/packaging-20.9.dist-info/RECORD +20 -0
  118. pex/vendor/_vendored/packaging_20_9/packaging-20.9.pex-info/original-whl-info.json +1 -0
  119. pex/vendor/_vendored/packaging_20_9/pyparsing-2.4.7.dist-info/RECORD +7 -0
  120. pex/vendor/_vendored/packaging_20_9/pyparsing-2.4.7.pex-info/original-whl-info.json +1 -0
  121. pex/vendor/_vendored/packaging_21_3/.layout.json +1 -1
  122. pex/vendor/_vendored/packaging_21_3/packaging-21.3.dist-info/RECORD +20 -0
  123. pex/vendor/_vendored/packaging_21_3/packaging-21.3.pex-info/original-whl-info.json +1 -0
  124. pex/vendor/_vendored/packaging_21_3/pyparsing-3.0.7.dist-info/RECORD +18 -0
  125. pex/vendor/_vendored/packaging_21_3/pyparsing-3.0.7.pex-info/original-whl-info.json +1 -0
  126. pex/vendor/_vendored/packaging_24_0/.layout.json +1 -1
  127. pex/vendor/_vendored/packaging_24_0/packaging-24.0.dist-info/RECORD +22 -0
  128. pex/vendor/_vendored/packaging_24_0/packaging-24.0.pex-info/original-whl-info.json +1 -0
  129. pex/vendor/_vendored/packaging_25_0/.layout.json +1 -1
  130. pex/vendor/_vendored/packaging_25_0/packaging-25.0.dist-info/RECORD +24 -0
  131. pex/vendor/_vendored/packaging_25_0/packaging-25.0.pex-info/original-whl-info.json +1 -0
  132. pex/vendor/_vendored/pip/.layout.json +1 -1
  133. pex/vendor/_vendored/pip/pip/_vendor/certifi/cacert.pem +63 -1
  134. pex/vendor/_vendored/pip/pip-20.3.4.dist-info/RECORD +388 -0
  135. pex/vendor/_vendored/pip/pip-20.3.4.pex-info/original-whl-info.json +1 -0
  136. pex/vendor/_vendored/setuptools/.layout.json +1 -1
  137. pex/vendor/_vendored/setuptools/setuptools-44.0.0+3acb925dd708430aeaf197ea53ac8a752f7c1863.dist-info/RECORD +107 -0
  138. pex/vendor/_vendored/setuptools/setuptools-44.0.0+3acb925dd708430aeaf197ea53ac8a752f7c1863.pex-info/original-whl-info.json +1 -0
  139. pex/vendor/_vendored/toml/.layout.json +1 -1
  140. pex/vendor/_vendored/toml/toml-0.10.2.dist-info/RECORD +11 -0
  141. pex/vendor/_vendored/toml/toml-0.10.2.pex-info/original-whl-info.json +1 -0
  142. pex/vendor/_vendored/tomli/.layout.json +1 -1
  143. pex/vendor/_vendored/tomli/tomli-2.0.1.dist-info/RECORD +10 -0
  144. pex/vendor/_vendored/tomli/tomli-2.0.1.pex-info/original-whl-info.json +1 -0
  145. pex/venv/installer.py +46 -19
  146. pex/venv/venv_pex.py +6 -3
  147. pex/version.py +1 -1
  148. pex/wheel.py +188 -40
  149. pex/whl.py +67 -0
  150. pex/windows/__init__.py +14 -11
  151. {pex-2.54.2.dist-info → pex-2.69.0.dist-info}/METADATA +6 -5
  152. {pex-2.54.2.dist-info → pex-2.69.0.dist-info}/RECORD +157 -133
  153. {pex-2.54.2.dist-info → pex-2.69.0.dist-info}/entry_points.txt +1 -0
  154. {pex-2.54.2.dist-info → pex-2.69.0.dist-info}/pylock/pylock.toml +1 -1
  155. pex/docs/html/_pagefind/fragment/en_42c9d8c.pf_fragment +0 -0
  156. pex/docs/html/_pagefind/fragment/en_45dd5a2.pf_fragment +0 -0
  157. pex/docs/html/_pagefind/fragment/en_4ca74d2.pf_fragment +0 -0
  158. pex/docs/html/_pagefind/fragment/en_77273d5.pf_fragment +0 -0
  159. pex/docs/html/_pagefind/fragment/en_87a59c5.pf_fragment +0 -0
  160. pex/docs/html/_pagefind/fragment/en_8dc89b5.pf_fragment +0 -0
  161. pex/docs/html/_pagefind/fragment/en_9d1319b.pf_fragment +0 -0
  162. pex/docs/html/_pagefind/fragment/en_e55df9d.pf_fragment +0 -0
  163. pex/docs/html/_pagefind/index/en_1e98c6f.pf_index +0 -0
  164. pex/docs/html/_pagefind/pagefind.en_d1c488ecae.pf_meta +0 -0
  165. pex/vendor/_vendored/ansicolors/ansicolors-1.1.8.dist-info/INSTALLER +0 -1
  166. pex/vendor/_vendored/appdirs/appdirs-1.4.4.dist-info/INSTALLER +0 -1
  167. pex/vendor/_vendored/attrs/attrs-21.5.0.dev0.dist-info/INSTALLER +0 -1
  168. pex/vendor/_vendored/packaging_20_9/packaging-20.9.dist-info/INSTALLER +0 -1
  169. pex/vendor/_vendored/packaging_20_9/pyparsing-2.4.7.dist-info/INSTALLER +0 -1
  170. pex/vendor/_vendored/packaging_21_3/packaging-21.3.dist-info/INSTALLER +0 -1
  171. pex/vendor/_vendored/packaging_21_3/pyparsing-3.0.7.dist-info/INSTALLER +0 -1
  172. pex/vendor/_vendored/packaging_24_0/packaging-24.0.dist-info/INSTALLER +0 -1
  173. pex/vendor/_vendored/packaging_25_0/packaging-25.0.dist-info/INSTALLER +0 -1
  174. pex/vendor/_vendored/pip/pip-20.3.4.dist-info/INSTALLER +0 -1
  175. pex/vendor/_vendored/setuptools/setuptools-44.0.0+3acb925dd708430aeaf197ea53ac8a752f7c1863.dist-info/INSTALLER +0 -1
  176. pex/vendor/_vendored/toml/toml-0.10.2.dist-info/INSTALLER +0 -1
  177. pex/vendor/_vendored/tomli/tomli-2.0.1.dist-info/INSTALLER +0 -1
  178. {pex-2.54.2.dist-info → pex-2.69.0.dist-info}/WHEEL +0 -0
  179. {pex-2.54.2.dist-info → pex-2.69.0.dist-info}/licenses/LICENSE +0 -0
  180. {pex-2.54.2.dist-info → pex-2.69.0.dist-info}/top_level.txt +0 -0
pex/pep_508.py CHANGED
@@ -3,6 +3,7 @@
3
3
 
4
4
  from __future__ import absolute_import
5
5
 
6
+ from pex.interpreter_implementation import InterpreterImplementation
6
7
  from pex.platforms import Platform
7
8
  from pex.third_party.packaging import markers
8
9
  from pex.typing import TYPE_CHECKING
@@ -117,11 +118,9 @@ class MarkerEnvironment(object):
117
118
  sys_platform = "win32"
118
119
 
119
120
  platform_python_implementation = None
120
-
121
- if platform.impl == "cp":
122
- platform_python_implementation = "CPython"
123
- elif platform.impl == "pp":
124
- platform_python_implementation = "PyPy"
121
+ for implementation in InterpreterImplementation.values():
122
+ if implementation.abbr == platform.impl:
123
+ platform_python_implementation = implementation.value
125
124
 
126
125
  python_version = ".".join(map(str, platform.version_info[:2]))
127
126
 
pex/pex_builder.py CHANGED
@@ -33,11 +33,11 @@ from pex.enum import Enum
33
33
  from pex.executables import chmod_plus_x, create_sh_python_redirector_shebang
34
34
  from pex.finders import get_entry_point_from_console_script, get_script_from_distributions
35
35
  from pex.fs import safe_rename, safe_symlink
36
+ from pex.installed_wheel import InstalledWheel
36
37
  from pex.interpreter import PythonInterpreter
37
38
  from pex.layout import Layout
38
39
  from pex.orderedset import OrderedSet
39
40
  from pex.os import WINDOWS
40
- from pex.pep_376 import InstalledWheel
41
41
  from pex.pex import PEX
42
42
  from pex.pex_info import PexInfo
43
43
  from pex.sh_boot import create_sh_boot_script
@@ -470,10 +470,7 @@ class PEXBuilder(object):
470
470
  def _prepare_code(self):
471
471
  chroot_path = self._chroot.path()
472
472
  self._pex_info.code_hash = CacheHelper.pex_code_hash(
473
- chroot_path,
474
- exclude_dirs=tuple(
475
- os.path.join(chroot_path, d) for d in (layout.BOOTSTRAP_DIR, layout.DEPS_DIR)
476
- ),
473
+ chroot_path, exclude_dirs=(layout.BOOTSTRAP_DIR, layout.DEPS_DIR)
477
474
  )
478
475
  self._pex_info.pex_hash = hashlib.sha1(self._pex_info.dump().encode("utf-8")).hexdigest()
479
476
  self._chroot.write(self._pex_info.dump().encode("utf-8"), PexInfo.PATH, label="manifest")
@@ -790,9 +787,9 @@ class PEXBuilder(object):
790
787
  self._chroot.zip(
791
788
  os.path.join(atomic_zip_dir.work_dir, location),
792
789
  deterministic=deterministic,
793
- exclude_file=is_pyc_temporary_file
794
- if bytecode_compile
795
- else is_pyc_file,
790
+ exclude_file=(
791
+ is_pyc_temporary_file if bytecode_compile else is_pyc_file
792
+ ),
796
793
  strip_prefix=os.path.join(pex_info.internal_cache, location),
797
794
  labels=(location,),
798
795
  compress=compress,
pex/pex_info.py CHANGED
@@ -25,11 +25,11 @@ if TYPE_CHECKING:
25
25
  from typing import Collection # type: ignore[attr-defined]
26
26
  from typing import Any, Dict, Iterable, Mapping, Optional, Text, Tuple, Union
27
27
 
28
- from pex.cache.dirs import VenvDir
29
- from pex.dist_metadata import Requirement
30
-
31
28
  # N.B.: These are expensive imports and PexInfo is used during PEX bootstrapping which we want
32
29
  # to be as fast as possible.
30
+ from pex.cache.dirs import VenvDir
31
+ from pex.dependency_configuration import Override
32
+ from pex.dist_metadata import Requirement
33
33
  from pex.interpreter import PythonInterpreter
34
34
  from pex.interpreter_constraints import InterpreterConstraints
35
35
 
@@ -491,9 +491,9 @@ class PexInfo(object):
491
491
  # type: () -> Iterable[str]
492
492
  return self._excluded
493
493
 
494
- def add_override(self, requirement):
495
- # type: (Requirement) -> None
496
- self._overridden.add(str(requirement))
494
+ def add_override(self, override):
495
+ # type: (Override) -> None
496
+ self._overridden.add(str(override))
497
497
 
498
498
  @property
499
499
  def overridden(self):
@@ -604,9 +604,14 @@ class PexInfo(object):
604
604
  data["distributions"] = self._distributions.copy()
605
605
  return data
606
606
 
607
- def dump(self, **extra_json_dumps_kwargs):
608
- # type: (**Any) -> str
609
- return json.dumps(self.as_json_dict(), sort_keys=True, **extra_json_dumps_kwargs)
607
+ def dump(self, indent=None):
608
+ # type: (Optional[int]) -> str
609
+ return json.dumps(
610
+ self.as_json_dict(),
611
+ sort_keys=True,
612
+ indent=indent,
613
+ separators=None if indent else (",", ":"),
614
+ )
610
615
 
611
616
  def copy(self):
612
617
  # type: () -> PexInfo
@@ -8,30 +8,82 @@ import os
8
8
 
9
9
  from pex.common import safe_mkdtemp
10
10
  from pex.dependency_configuration import DependencyConfiguration
11
+ from pex.exceptions import reportable_unexpected_error_msg
12
+ from pex.interpreter_implementation import InterpreterImplementation
13
+ from pex.pep_508 import MarkerEnvironment
11
14
  from pex.pip.download_observer import DownloadObserver, Patch, PatchSet
12
- from pex.typing import TYPE_CHECKING
15
+ from pex.resolve.target_system import TargetSystem, UniversalTarget
16
+ from pex.third_party.packaging.specifiers import SpecifierSet
17
+ from pex.typing import TYPE_CHECKING, cast
13
18
 
14
19
  if TYPE_CHECKING:
15
- from typing import Mapping, Optional
20
+ from typing import Mapping, Optional, Union
16
21
 
22
+ import attr # vendor:skip
23
+ else:
24
+ from pex.third_party import attr
17
25
 
26
+
27
+ @attr.s(frozen=True)
18
28
  class PatchContext(object):
19
29
  _PEX_DEP_CONFIG_FILE_ENV_VAR_NAME = "_PEX_DEP_CONFIG_FILE"
20
30
 
21
31
  @classmethod
22
- def load_dependency_configuration(cls):
23
- # type: () -> DependencyConfiguration
32
+ def load(cls):
33
+ # type: () -> PatchContext
24
34
 
25
35
  dep_config_file = os.environ.pop(cls._PEX_DEP_CONFIG_FILE_ENV_VAR_NAME)
26
36
  with open(dep_config_file) as fp:
27
37
  data = json.load(fp)
28
- return DependencyConfiguration.create(
29
- excluded=data["excluded"], overridden=data["overridden"]
38
+
39
+ universal_target = None # type: Optional[UniversalTarget]
40
+ universal_target_data = data["universal_target"]
41
+ if universal_target_data:
42
+ implementation = universal_target_data["implementation"]
43
+ universal_target = UniversalTarget(
44
+ implementation=(
45
+ InterpreterImplementation.for_value(implementation) if implementation else None
46
+ ),
47
+ requires_python=tuple(
48
+ SpecifierSet(requires_python)
49
+ for requires_python in universal_target_data["requires_python"]
50
+ ),
51
+ systems=tuple(
52
+ TargetSystem.for_value(system) for system in universal_target_data["systems"]
53
+ ),
54
+ )
55
+
56
+ marker_environment = None # type: Optional[MarkerEnvironment]
57
+ marker_environment_data = data["marker_environment"]
58
+ if marker_environment_data:
59
+ marker_environment = MarkerEnvironment(**marker_environment_data)
60
+
61
+ if not (bool(universal_target) ^ bool(marker_environment)):
62
+ raise AssertionError(
63
+ reportable_unexpected_error_msg(
64
+ "Expected exactly one of lock_configuration or marker_environment to be "
65
+ "defined, found data: {data}",
66
+ data=data,
67
+ )
68
+ )
69
+
70
+ return cls(
71
+ dependency_configuration=DependencyConfiguration.create(
72
+ excluded=data["excluded"], overridden=data["overridden"]
73
+ ),
74
+ target=cast(
75
+ "Union[UniversalTarget, MarkerEnvironment]",
76
+ universal_target or marker_environment,
77
+ ),
30
78
  )
31
79
 
32
80
  @classmethod
33
- def dump_dependency_configuration(cls, dependency_configuration):
34
- # type: (DependencyConfiguration) -> Mapping[str, str]
81
+ def dump(
82
+ cls,
83
+ dependency_configuration, # type: DependencyConfiguration
84
+ target, # type: Union[UniversalTarget, MarkerEnvironment]
85
+ ):
86
+ # type: (...) -> Mapping[str, str]
35
87
 
36
88
  dep_config_file = os.path.join(safe_mkdtemp(), "dep_config.json")
37
89
  with open(dep_config_file, "w") as fp:
@@ -41,14 +93,36 @@ class PatchContext(object):
41
93
  "overridden": [
42
94
  str(override) for override in dependency_configuration.all_overrides()
43
95
  ],
96
+ "universal_target": (
97
+ {
98
+ "implementation": (
99
+ str(target.implementation) if target.implementation else None
100
+ ),
101
+ "requires_python": [
102
+ str(specifier_set) for specifier_set in target.requires_python
103
+ ],
104
+ "systems": [str(system) for system in target.systems],
105
+ }
106
+ if isinstance(target, UniversalTarget)
107
+ else None
108
+ ),
109
+ "marker_environment": (
110
+ target.as_dict() if isinstance(target, MarkerEnvironment) else None
111
+ ),
44
112
  },
45
113
  fp,
46
114
  )
47
115
  return {cls._PEX_DEP_CONFIG_FILE_ENV_VAR_NAME: dep_config_file}
48
116
 
117
+ dependency_configuration = attr.ib() # type: DependencyConfiguration
118
+ target = attr.ib() # type: Union[UniversalTarget, MarkerEnvironment]
119
+
49
120
 
50
- def patch(dependency_configuration):
51
- # type: (DependencyConfiguration) -> Optional[DownloadObserver]
121
+ def patch(
122
+ dependency_configuration, # type: DependencyConfiguration
123
+ target, # type: Union[UniversalTarget, MarkerEnvironment]
124
+ ):
125
+ # type: (...) -> Optional[DownloadObserver]
52
126
 
53
127
  if not dependency_configuration:
54
128
  return None
@@ -57,9 +131,7 @@ def patch(dependency_configuration):
57
131
  analyzer=None,
58
132
  patch_set=PatchSet.create(
59
133
  Patch.from_code_resource(
60
- __name__,
61
- "requires.py",
62
- **PatchContext.dump_dependency_configuration(dependency_configuration)
134
+ __name__, "requires.py", **PatchContext.dump(dependency_configuration, target)
63
135
  )
64
136
  ),
65
137
  )
@@ -1,7 +1,7 @@
1
1
  # Copyright 2024 Pex project contributors.
2
2
  # Licensed under the Apache License, Version 2.0 (see LICENSE).
3
3
 
4
- from __future__ import absolute_import, print_function
4
+ from __future__ import absolute_import
5
5
 
6
6
  import logging
7
7
  import sys
@@ -14,9 +14,34 @@ def patch():
14
14
 
15
15
  from pex.common import pluralize
16
16
  from pex.dist_metadata import Requirement as PexRequirement
17
+ from pex.pep_508 import MarkerEnvironment
17
18
  from pex.pip.dependencies import PatchContext
19
+ from pex.resolve.target_system import UniversalTarget
20
+ from pex.typing import TYPE_CHECKING
18
21
 
19
- dependency_configuration = PatchContext.load_dependency_configuration()
22
+ if TYPE_CHECKING:
23
+ from typing import Dict, Optional, Sequence
24
+
25
+ patch_context = PatchContext.load()
26
+ dependency_configuration = patch_context.dependency_configuration
27
+ target = patch_context.target
28
+
29
+ marker_environment = None # type: Optional[Dict[str, str]]
30
+ if isinstance(target, MarkerEnvironment):
31
+ marker_environment = target.as_dict()
32
+
33
+ def are_exhaustive(
34
+ universal_target, # type: UniversalTarget
35
+ overrides, # type: Sequence[Requirement]
36
+ ):
37
+ # type: (...) -> bool
38
+
39
+ markers = [override.marker for override in overrides if override.marker]
40
+ if len(markers) < len(overrides):
41
+ # We have at least one override without a marker; i.e.: the override always applies.
42
+ return True
43
+
44
+ return universal_target.are_exhaustive(markers=markers)
20
45
 
21
46
  def create_requires(orig_requires):
22
47
  def requires(self, *args, **kwargs):
@@ -37,7 +62,17 @@ def patch():
37
62
  )
38
63
  )
39
64
  continue
40
- overrides = dependency_configuration.overrides_for(requirement)
65
+
66
+ overrides = list(dependency_configuration.overrides_for(requirement))
67
+ if marker_environment:
68
+ overrides = [
69
+ override
70
+ for override in overrides
71
+ if not override.marker or override.marker.evaluate(marker_environment)
72
+ ]
73
+ elif overrides and not are_exhaustive(patch_context.target, overrides):
74
+ overrides.append(req)
75
+
41
76
  if overrides:
42
77
  logger.debug(
43
78
  "[{type}: patched {orig_requires}] Overrode {dep} from {dist} with "
@@ -12,6 +12,7 @@ from pex.pep_425 import CompatibilityTags
12
12
  from pex.pip.download_observer import DownloadObserver, Patch, PatchSet
13
13
  from pex.platforms import PlatformSpec
14
14
  from pex.targets import AbbreviatedPlatform, CompletePlatform, Target
15
+ from pex.third_party.packaging.specifiers import SpecifierSet
15
16
  from pex.tracer import TRACER
16
17
  from pex.typing import TYPE_CHECKING
17
18
 
@@ -156,7 +157,7 @@ def patch(target):
156
157
  "environment markers defined and an abbreviated platform should always have at least the"
157
158
  "`python_version` environment marker defined. Given: {target}".format(target=target)
158
159
  )
159
- requires_python = (
160
+ requires_python = SpecifierSet(
160
161
  "=={full_version}".format(full_version=target.marker_environment.python_full_version)
161
162
  if target.marker_environment.python_full_version
162
163
  else "=={version}.*".format(version=target.marker_environment.python_version)
@@ -179,7 +180,7 @@ def patch_tags(
179
180
 
180
181
 
181
182
  def patch_requires_python(
182
- requires_python, # type: Iterable[str]
183
+ requires_python, # type: Iterable[SpecifierSet]
183
184
  patches_dir=None, # type: Optional[str]
184
185
  ):
185
186
  # type: (...) -> Patch
@@ -193,7 +194,7 @@ def patch_requires_python(
193
194
  """
194
195
  with TRACER.timed(
195
196
  "Calculating compatible python versions for {requires_python}".format(
196
- requires_python=requires_python
197
+ requires_python=" or ".join(map(str, requires_python))
197
198
  )
198
199
  ):
199
200
  python_full_versions = list(iter_compatible_versions(requires_python))
pex/pip/installation.py CHANGED
@@ -221,7 +221,7 @@ def _install_wheel(wheel_path):
221
221
  with atomic_directory(installed_wheel_dir) as atomic_dir:
222
222
  if not atomic_dir.is_finalized():
223
223
  installed_wheel = pep_427.install_wheel_chroot(
224
- wheel_path=wheel_path, destination=atomic_dir.work_dir
224
+ wheel=wheel_path, destination=atomic_dir.work_dir
225
225
  )
226
226
  runtime_key_dir = InstalledWheelDir.create(
227
227
  wheel_name=wheel_name,
@@ -384,7 +384,7 @@ class PipInstallation(object):
384
384
  "{pip_version} requires Python {requires_python}.".format(
385
385
  pip_requirement=self.version.requirement,
386
386
  pip_version=self.version.value,
387
- python_impl=self.interpreter.identity.interpreter,
387
+ python_impl=self.interpreter.identity.implementation,
388
388
  python_version=self.interpreter.identity.version_str,
389
389
  python_binary=self.interpreter.binary,
390
390
  requires_python=self.version.requires_python,
pex/pip/local_project.py CHANGED
@@ -4,9 +4,8 @@
4
4
  from __future__ import absolute_import
5
5
 
6
6
  import os.path
7
- import tarfile
8
7
 
9
- from pex import hashing
8
+ from pex import hashing, sdist
10
9
  from pex.build_system import pep_517
11
10
  from pex.common import temporary_dir
12
11
  from pex.pip.version import PipVersionValue
@@ -33,25 +32,18 @@ def digest_local_project(
33
32
  # type: (...) -> Union[str, Error]
34
33
  with TRACER.timed("Fingerprinting local project at {directory}".format(directory=directory)):
35
34
  with temporary_dir() as td:
36
- sdist_or_error = pep_517.build_sdist(
35
+ sdist_path_or_error = pep_517.build_sdist(
37
36
  project_directory=directory,
38
37
  dist_dir=os.path.join(td, "dists"),
39
38
  pip_version=pip_version,
40
39
  target=target,
41
40
  resolver=resolver,
42
41
  )
43
- if isinstance(sdist_or_error, Error):
44
- return sdist_or_error
45
- sdist = sdist_or_error
42
+ if isinstance(sdist_path_or_error, Error):
43
+ return sdist_path_or_error
44
+ sdist_path = sdist_path_or_error
46
45
 
47
46
  extract_dir = dest_dir or os.path.join(td, "extracted")
48
- with tarfile.open(sdist) as tf:
49
- tf.extractall(extract_dir)
50
- listing = os.listdir(extract_dir)
51
- assert len(listing) == 1, (
52
- "Expected sdist generated for {directory} to contain one top-level directory, "
53
- "found:\n{listing}".format(directory=directory, listing="\n".join(listing))
54
- )
55
- project_dir = os.path.join(extract_dir, listing[0])
47
+ project_dir = sdist.extract_tarball(sdist_path, dest_dir=extract_dir)
56
48
  hashing.dir_hash(directory=project_dir, digest=digest)
57
49
  return os.path.join(extract_dir, project_dir)
@@ -0,0 +1,78 @@
1
+ # Copyright 2025 Pex project contributors.
2
+ # Licensed under the Apache License, Version 2.0 (see LICENSE).
3
+
4
+ from __future__ import absolute_import
5
+
6
+ import json
7
+ import os
8
+
9
+ from pex.common import safe_mkdtemp
10
+ from pex.pep_508 import MarkerEnvironment
11
+ from pex.pip.download_observer import DownloadObserver, Patch, PatchSet
12
+ from pex.pip.version import PipVersion, PipVersionValue
13
+ from pex.resolve.package_repository import PackageRepositories, ReposConfiguration
14
+ from pex.resolve.target_system import MarkerEnv, UniversalTarget
15
+ from pex.typing import TYPE_CHECKING
16
+
17
+ if TYPE_CHECKING:
18
+ from typing import Dict, Mapping, Optional, Union
19
+
20
+ import attr # vendor:skip
21
+ else:
22
+ from pex.third_party import attr
23
+
24
+
25
+ @attr.s(frozen=True)
26
+ class PatchContext(object):
27
+ _PEX_REPOS_CONFIG_FILE_ENV_VAR_NAME = "_PEX_REPOS_CONFIG_FILE"
28
+
29
+ @classmethod
30
+ def load(cls):
31
+ # type: () -> PatchContext
32
+
33
+ dep_config_file = os.environ.pop(cls._PEX_REPOS_CONFIG_FILE_ENV_VAR_NAME)
34
+ with open(dep_config_file) as fp:
35
+ data = json.load(fp)
36
+ return cls(
37
+ pip_version=PipVersion.for_value(data["pip_version"]),
38
+ package_repositories=PackageRepositories.from_dict(data["package_repositories"]),
39
+ )
40
+
41
+ def dump(self):
42
+ # type: () -> Mapping[str, str]
43
+
44
+ repos_config_file = os.path.join(safe_mkdtemp(), "repos_config.json")
45
+ with open(repos_config_file, "w") as fp:
46
+ json.dump(
47
+ {
48
+ "pip_version": str(self.pip_version),
49
+ "package_repositories": self.package_repositories.as_dict(),
50
+ },
51
+ fp,
52
+ )
53
+ return {self._PEX_REPOS_CONFIG_FILE_ENV_VAR_NAME: repos_config_file}
54
+
55
+ pip_version = attr.ib() # type: PipVersionValue
56
+ package_repositories = attr.ib() # type: PackageRepositories
57
+
58
+
59
+ def patch(
60
+ repos_configuration, # type: ReposConfiguration
61
+ pip_version, # type: PipVersionValue
62
+ target, # type: Union[UniversalTarget, MarkerEnvironment]
63
+ ):
64
+ # type: (...) -> Optional[DownloadObserver]
65
+
66
+ target_env = (
67
+ target.marker_env() if isinstance(target, UniversalTarget) else target.as_dict()
68
+ ) # type: Union[MarkerEnv, Dict[str, str]]
69
+ package_repositories = repos_configuration.scoped(target_env)
70
+ if not package_repositories.has_scoped_repositories:
71
+ return None
72
+
73
+ patches = [
74
+ Patch.from_code_resource(
75
+ __name__, "link_collector.py", **PatchContext(pip_version, package_repositories).dump()
76
+ )
77
+ ]
78
+ return DownloadObserver(analyzer=None, patch_set=PatchSet(patches=tuple(patches)))
@@ -0,0 +1,96 @@
1
+ # Copyright 2025 Pex project contributors.
2
+ # Licensed under the Apache License, Version 2.0 (see LICENSE).
3
+
4
+ from __future__ import absolute_import
5
+
6
+ import logging
7
+
8
+ from pex.pip.version import PipVersion
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ def patch():
14
+ # type: () -> None
15
+
16
+ from contextlib import contextmanager
17
+
18
+ from pip._internal.index.collector import LinkCollector
19
+ from pip._internal.models.search_scope import SearchScope
20
+
21
+ from pex.common import pluralize
22
+ from pex.pep_503 import ProjectName
23
+ from pex.pip.package_repositories import PatchContext
24
+ from pex.typing import TYPE_CHECKING
25
+
26
+ if TYPE_CHECKING:
27
+ from typing import Any, Iterator
28
+
29
+ patch_context = PatchContext.load()
30
+
31
+ @contextmanager
32
+ def scoped_repositories(link_collector, project_name):
33
+ # type: (...) -> Iterator[None]
34
+
35
+ project = ProjectName(project_name)
36
+ find_links = patch_context.package_repositories.in_scope_find_links(project)
37
+ if find_links:
38
+ logger.debug(
39
+ "pex: scoped {project_name} to {find_links_repos} {locations}".format(
40
+ project_name=project_name,
41
+ find_links_repos=pluralize(find_links, "find links repo"),
42
+ locations=" and ".join(find_links),
43
+ )
44
+ )
45
+ index_urls = patch_context.package_repositories.in_scope_indexes(project)
46
+ if index_urls:
47
+ logger.debug(
48
+ "pex: scoped {project_name} to {indexes} {locations}".format(
49
+ project_name=project_name,
50
+ indexes=pluralize(index_urls, "index"),
51
+ locations=" and ".join(index_urls),
52
+ )
53
+ )
54
+ if not find_links and not index_urls:
55
+ find_links = list(patch_context.package_repositories.global_find_links)
56
+ index_urls = list(patch_context.package_repositories.global_indexes)
57
+
58
+ kwargs = {}
59
+ if patch_context.pip_version >= PipVersion.v22_3:
60
+ kwargs["no_index"] = link_collector.search_scope.no_index
61
+
62
+ orig_search_scope = link_collector.search_scope
63
+ link_collector.search_scope = SearchScope.create(
64
+ find_links=find_links, index_urls=index_urls, **kwargs
65
+ )
66
+ try:
67
+ yield
68
+ finally:
69
+ link_collector.search_scope = orig_search_scope
70
+
71
+ if patch_context.pip_version is PipVersion.VENDORED:
72
+ orig_collect_links = LinkCollector.collect_links
73
+
74
+ def collect_links(
75
+ self, # type: LinkCollector
76
+ project_name, # type: str
77
+ ):
78
+ # type: (...) -> Any
79
+ with scoped_repositories(self, project_name):
80
+ return orig_collect_links(self, project_name)
81
+
82
+ LinkCollector.collect_links = collect_links
83
+ else:
84
+ orig_collect_sources = LinkCollector.collect_sources
85
+
86
+ def collect_sources(
87
+ self, # type: LinkCollector
88
+ project_name, # type: str
89
+ *args, # type: Any
90
+ **kwargs # type: Any
91
+ ):
92
+ # type: (...) -> Any
93
+ with scoped_repositories(self, project_name):
94
+ return orig_collect_sources(self, project_name, *args, **kwargs)
95
+
96
+ LinkCollector.collect_sources = collect_sources