From 1346ddb6ae588c1e0cd99f2b9afe698c9a261c96 Mon Sep 17 00:00:00 2001 From: Richard Si Date: Sun, 22 Dec 2024 19:04:44 -0500 Subject: [PATCH 1/2] Upgrade pyproject-hooks to 1.2.0 --- news/pyproject-hooks.vendor.rst | 1 + src/pip/_vendor/pyproject_hooks.pyi | 1 - src/pip/_vendor/pyproject_hooks/__init__.py | 26 +- src/pip/_vendor/pyproject_hooks/_compat.py | 8 - src/pip/_vendor/pyproject_hooks/_impl.py | 282 +++++++++++------- .../pyproject_hooks/_in_process/__init__.py | 7 +- .../_in_process/_in_process.py | 190 +++++++----- src/pip/_vendor/pyproject_hooks/py.typed | 0 src/pip/_vendor/vendor.txt | 2 +- 9 files changed, 318 insertions(+), 199 deletions(-) create mode 100644 news/pyproject-hooks.vendor.rst delete mode 100644 src/pip/_vendor/pyproject_hooks.pyi delete mode 100644 src/pip/_vendor/pyproject_hooks/_compat.py create mode 100644 src/pip/_vendor/pyproject_hooks/py.typed diff --git a/news/pyproject-hooks.vendor.rst b/news/pyproject-hooks.vendor.rst new file mode 100644 index 00000000000..44af87f5e58 --- /dev/null +++ b/news/pyproject-hooks.vendor.rst @@ -0,0 +1 @@ +Upgrade pyproject-hooks to 1.2.0 diff --git a/src/pip/_vendor/pyproject_hooks.pyi b/src/pip/_vendor/pyproject_hooks.pyi deleted file mode 100644 index e68245481d5..00000000000 --- a/src/pip/_vendor/pyproject_hooks.pyi +++ /dev/null @@ -1 +0,0 @@ -from pyproject_hooks import * \ No newline at end of file diff --git a/src/pip/_vendor/pyproject_hooks/__init__.py b/src/pip/_vendor/pyproject_hooks/__init__.py index ddfcf7f72f3..746b89f7e71 100644 --- a/src/pip/_vendor/pyproject_hooks/__init__.py +++ b/src/pip/_vendor/pyproject_hooks/__init__.py @@ -1,8 +1,9 @@ """Wrappers to call pyproject.toml-based build backend hooks. """ +from typing import TYPE_CHECKING + from ._impl import ( - BackendInvalid, BackendUnavailable, BuildBackendHookCaller, HookMissing, @@ -11,13 +12,20 @@ quiet_subprocess_runner, ) -__version__ = '1.0.0' +__version__ = "1.2.0" __all__ = [ - 'BackendUnavailable', - 'BackendInvalid', - 'HookMissing', - 'UnsupportedOperation', - 'default_subprocess_runner', - 'quiet_subprocess_runner', - 'BuildBackendHookCaller', + "BackendUnavailable", + "BackendInvalid", + "HookMissing", + "UnsupportedOperation", + "default_subprocess_runner", + "quiet_subprocess_runner", + "BuildBackendHookCaller", ] + +BackendInvalid = BackendUnavailable # Deprecated alias, previously a separate exception + +if TYPE_CHECKING: + from ._impl import SubprocessRunner + + __all__ += ["SubprocessRunner"] diff --git a/src/pip/_vendor/pyproject_hooks/_compat.py b/src/pip/_vendor/pyproject_hooks/_compat.py deleted file mode 100644 index 95e509c0143..00000000000 --- a/src/pip/_vendor/pyproject_hooks/_compat.py +++ /dev/null @@ -1,8 +0,0 @@ -__all__ = ("tomllib",) - -import sys - -if sys.version_info >= (3, 11): - import tomllib -else: - from pip._vendor import tomli as tomllib diff --git a/src/pip/_vendor/pyproject_hooks/_impl.py b/src/pip/_vendor/pyproject_hooks/_impl.py index 37b0e6531f1..d1e9d7bb8c6 100644 --- a/src/pip/_vendor/pyproject_hooks/_impl.py +++ b/src/pip/_vendor/pyproject_hooks/_impl.py @@ -6,48 +6,72 @@ from os.path import abspath from os.path import join as pjoin from subprocess import STDOUT, check_call, check_output +from typing import TYPE_CHECKING, Any, Iterator, Mapping, Optional, Sequence from ._in_process import _in_proc_script_path +if TYPE_CHECKING: + from typing import Protocol -def write_json(obj, path, **kwargs): - with open(path, 'w', encoding='utf-8') as f: + class SubprocessRunner(Protocol): + """A protocol for the subprocess runner.""" + + def __call__( + self, + cmd: Sequence[str], + cwd: Optional[str] = None, + extra_environ: Optional[Mapping[str, str]] = None, + ) -> None: + ... + + +def write_json(obj: Mapping[str, Any], path: str, **kwargs) -> None: + with open(path, "w", encoding="utf-8") as f: json.dump(obj, f, **kwargs) -def read_json(path): - with open(path, encoding='utf-8') as f: +def read_json(path: str) -> Mapping[str, Any]: + with open(path, encoding="utf-8") as f: return json.load(f) class BackendUnavailable(Exception): """Will be raised if the backend cannot be imported in the hook process.""" - def __init__(self, traceback): - self.traceback = traceback - -class BackendInvalid(Exception): - """Will be raised if the backend is invalid.""" - def __init__(self, backend_name, backend_path, message): - super().__init__(message) + def __init__( + self, + traceback: str, + message: Optional[str] = None, + backend_name: Optional[str] = None, + backend_path: Optional[Sequence[str]] = None, + ) -> None: + # Preserving arg order for the sake of API backward compatibility. self.backend_name = backend_name self.backend_path = backend_path + self.traceback = traceback + super().__init__(message or "Error while importing backend") class HookMissing(Exception): """Will be raised on missing hooks (if a fallback can't be used).""" - def __init__(self, hook_name): + + def __init__(self, hook_name: str) -> None: super().__init__(hook_name) self.hook_name = hook_name class UnsupportedOperation(Exception): """May be raised by build_sdist if the backend indicates that it can't.""" - def __init__(self, traceback): + + def __init__(self, traceback: str) -> None: self.traceback = traceback -def default_subprocess_runner(cmd, cwd=None, extra_environ=None): +def default_subprocess_runner( + cmd: Sequence[str], + cwd: Optional[str] = None, + extra_environ: Optional[Mapping[str, str]] = None, +) -> None: """The default method of calling the wrapper subprocess. This uses :func:`subprocess.check_call` under the hood. @@ -59,7 +83,11 @@ def default_subprocess_runner(cmd, cwd=None, extra_environ=None): check_call(cmd, cwd=cwd, env=env) -def quiet_subprocess_runner(cmd, cwd=None, extra_environ=None): +def quiet_subprocess_runner( + cmd: Sequence[str], + cwd: Optional[str] = None, + extra_environ: Optional[Mapping[str, str]] = None, +) -> None: """Call the subprocess while suppressing output. This uses :func:`subprocess.check_output` under the hood. @@ -71,7 +99,7 @@ def quiet_subprocess_runner(cmd, cwd=None, extra_environ=None): check_output(cmd, cwd=cwd, env=env, stderr=STDOUT) -def norm_and_check(source_tree, requested): +def norm_and_check(source_tree: str, requested: str) -> str: """Normalise and check a backend path. Ensure that the requested backend path is specified as a relative path, @@ -96,17 +124,16 @@ def norm_and_check(source_tree, requested): class BuildBackendHookCaller: - """A wrapper to call the build backend hooks for a source directory. - """ + """A wrapper to call the build backend hooks for a source directory.""" def __init__( - self, - source_dir, - build_backend, - backend_path=None, - runner=None, - python_executable=None, - ): + self, + source_dir: str, + build_backend: str, + backend_path: Optional[Sequence[str]] = None, + runner: Optional["SubprocessRunner"] = None, + python_executable: Optional[str] = None, + ) -> None: """ :param source_dir: The source directory to invoke the build backend for :param build_backend: The build backend spec @@ -121,9 +148,7 @@ def __init__( self.source_dir = abspath(source_dir) self.build_backend = build_backend if backend_path: - backend_path = [ - norm_and_check(self.source_dir, p) for p in backend_path - ] + backend_path = [norm_and_check(self.source_dir, p) for p in backend_path] self.backend_path = backend_path self._subprocess_runner = runner if not python_executable: @@ -131,10 +156,12 @@ def __init__( self.python_executable = python_executable @contextmanager - def subprocess_runner(self, runner): + def subprocess_runner(self, runner: "SubprocessRunner") -> Iterator[None]: """A context manager for temporarily overriding the default :ref:`subprocess runner `. + :param runner: The new subprocess runner to use within the context. + .. code-block:: python hook_caller = BuildBackendHookCaller(...) @@ -148,33 +175,44 @@ def subprocess_runner(self, runner): finally: self._subprocess_runner = prev - def _supported_features(self): + def _supported_features(self) -> Sequence[str]: """Return the list of optional features supported by the backend.""" - return self._call_hook('_supported_features', {}) + return self._call_hook("_supported_features", {}) - def get_requires_for_build_wheel(self, config_settings=None): + def get_requires_for_build_wheel( + self, + config_settings: Optional[Mapping[str, Any]] = None, + ) -> Sequence[str]: """Get additional dependencies required for building a wheel. + :param config_settings: The configuration settings for the build backend :returns: A list of :pep:`dependency specifiers <508>`. - :rtype: list[str] .. admonition:: Fallback If the build backend does not defined a hook with this name, an empty list will be returned. """ - return self._call_hook('get_requires_for_build_wheel', { - 'config_settings': config_settings - }) + return self._call_hook( + "get_requires_for_build_wheel", {"config_settings": config_settings} + ) def prepare_metadata_for_build_wheel( - self, metadata_directory, config_settings=None, - _allow_fallback=True): + self, + metadata_directory: str, + config_settings: Optional[Mapping[str, Any]] = None, + _allow_fallback: bool = True, + ) -> str: """Prepare a ``*.dist-info`` folder with metadata for this project. + :param metadata_directory: The directory to write the metadata to + :param config_settings: The configuration settings for the build backend + :param _allow_fallback: + Whether to allow the fallback to building a wheel and extracting + the metadata from it. Should be passed as a keyword argument only. + :returns: Name of the newly created subfolder within ``metadata_directory``, containing the metadata. - :rtype: str .. admonition:: Fallback @@ -183,17 +221,26 @@ def prepare_metadata_for_build_wheel( wheel via the ``build_wheel`` hook and the dist-info extracted from that will be returned. """ - return self._call_hook('prepare_metadata_for_build_wheel', { - 'metadata_directory': abspath(metadata_directory), - 'config_settings': config_settings, - '_allow_fallback': _allow_fallback, - }) + return self._call_hook( + "prepare_metadata_for_build_wheel", + { + "metadata_directory": abspath(metadata_directory), + "config_settings": config_settings, + "_allow_fallback": _allow_fallback, + }, + ) def build_wheel( - self, wheel_directory, config_settings=None, - metadata_directory=None): + self, + wheel_directory: str, + config_settings: Optional[Mapping[str, Any]] = None, + metadata_directory: Optional[str] = None, + ) -> str: """Build a wheel from this project. + :param wheel_directory: The directory to write the wheel to + :param config_settings: The configuration settings for the build backend + :param metadata_directory: The directory to reuse existing metadata from :returns: The name of the newly created wheel within ``wheel_directory``. @@ -206,35 +253,48 @@ def build_wheel( """ if metadata_directory is not None: metadata_directory = abspath(metadata_directory) - return self._call_hook('build_wheel', { - 'wheel_directory': abspath(wheel_directory), - 'config_settings': config_settings, - 'metadata_directory': metadata_directory, - }) - - def get_requires_for_build_editable(self, config_settings=None): + return self._call_hook( + "build_wheel", + { + "wheel_directory": abspath(wheel_directory), + "config_settings": config_settings, + "metadata_directory": metadata_directory, + }, + ) + + def get_requires_for_build_editable( + self, + config_settings: Optional[Mapping[str, Any]] = None, + ) -> Sequence[str]: """Get additional dependencies required for building an editable wheel. + :param config_settings: The configuration settings for the build backend :returns: A list of :pep:`dependency specifiers <508>`. - :rtype: list[str] .. admonition:: Fallback If the build backend does not defined a hook with this name, an empty list will be returned. """ - return self._call_hook('get_requires_for_build_editable', { - 'config_settings': config_settings - }) + return self._call_hook( + "get_requires_for_build_editable", {"config_settings": config_settings} + ) def prepare_metadata_for_build_editable( - self, metadata_directory, config_settings=None, - _allow_fallback=True): + self, + metadata_directory: str, + config_settings: Optional[Mapping[str, Any]] = None, + _allow_fallback: bool = True, + ) -> Optional[str]: """Prepare a ``*.dist-info`` folder with metadata for this project. + :param metadata_directory: The directory to write the metadata to + :param config_settings: The configuration settings for the build backend + :param _allow_fallback: + Whether to allow the fallback to building a wheel and extracting + the metadata from it. Should be passed as a keyword argument only. :returns: Name of the newly created subfolder within ``metadata_directory``, containing the metadata. - :rtype: str .. admonition:: Fallback @@ -243,17 +303,26 @@ def prepare_metadata_for_build_editable( wheel via the ``build_editable`` hook and the dist-info extracted from that will be returned. """ - return self._call_hook('prepare_metadata_for_build_editable', { - 'metadata_directory': abspath(metadata_directory), - 'config_settings': config_settings, - '_allow_fallback': _allow_fallback, - }) + return self._call_hook( + "prepare_metadata_for_build_editable", + { + "metadata_directory": abspath(metadata_directory), + "config_settings": config_settings, + "_allow_fallback": _allow_fallback, + }, + ) def build_editable( - self, wheel_directory, config_settings=None, - metadata_directory=None): + self, + wheel_directory: str, + config_settings: Optional[Mapping[str, Any]] = None, + metadata_directory: Optional[str] = None, + ) -> str: """Build an editable wheel from this project. + :param wheel_directory: The directory to write the wheel to + :param config_settings: The configuration settings for the build backend + :param metadata_directory: The directory to reuse existing metadata from :returns: The name of the newly created wheel within ``wheel_directory``. @@ -267,43 +336,55 @@ def build_editable( """ if metadata_directory is not None: metadata_directory = abspath(metadata_directory) - return self._call_hook('build_editable', { - 'wheel_directory': abspath(wheel_directory), - 'config_settings': config_settings, - 'metadata_directory': metadata_directory, - }) - - def get_requires_for_build_sdist(self, config_settings=None): + return self._call_hook( + "build_editable", + { + "wheel_directory": abspath(wheel_directory), + "config_settings": config_settings, + "metadata_directory": metadata_directory, + }, + ) + + def get_requires_for_build_sdist( + self, + config_settings: Optional[Mapping[str, Any]] = None, + ) -> Sequence[str]: """Get additional dependencies required for building an sdist. :returns: A list of :pep:`dependency specifiers <508>`. - :rtype: list[str] """ - return self._call_hook('get_requires_for_build_sdist', { - 'config_settings': config_settings - }) - - def build_sdist(self, sdist_directory, config_settings=None): + return self._call_hook( + "get_requires_for_build_sdist", {"config_settings": config_settings} + ) + + def build_sdist( + self, + sdist_directory: str, + config_settings: Optional[Mapping[str, Any]] = None, + ) -> str: """Build an sdist from this project. :returns: The name of the newly created sdist within ``wheel_directory``. """ - return self._call_hook('build_sdist', { - 'sdist_directory': abspath(sdist_directory), - 'config_settings': config_settings, - }) + return self._call_hook( + "build_sdist", + { + "sdist_directory": abspath(sdist_directory), + "config_settings": config_settings, + }, + ) - def _call_hook(self, hook_name, kwargs): - extra_environ = {'PEP517_BUILD_BACKEND': self.build_backend} + def _call_hook(self, hook_name: str, kwargs: Mapping[str, Any]) -> Any: + extra_environ = {"_PYPROJECT_HOOKS_BUILD_BACKEND": self.build_backend} if self.backend_path: backend_path = os.pathsep.join(self.backend_path) - extra_environ['PEP517_BACKEND_PATH'] = backend_path + extra_environ["_PYPROJECT_HOOKS_BACKEND_PATH"] = backend_path with tempfile.TemporaryDirectory() as td: - hook_input = {'kwargs': kwargs} - write_json(hook_input, pjoin(td, 'input.json'), indent=2) + hook_input = {"kwargs": kwargs} + write_json(hook_input, pjoin(td, "input.json"), indent=2) # Run the hook in a subprocess with _in_proc_script_path() as script: @@ -311,20 +392,19 @@ def _call_hook(self, hook_name, kwargs): self._subprocess_runner( [python, abspath(str(script)), hook_name, td], cwd=self.source_dir, - extra_environ=extra_environ + extra_environ=extra_environ, ) - data = read_json(pjoin(td, 'output.json')) - if data.get('unsupported'): - raise UnsupportedOperation(data.get('traceback', '')) - if data.get('no_backend'): - raise BackendUnavailable(data.get('traceback', '')) - if data.get('backend_invalid'): - raise BackendInvalid( + data = read_json(pjoin(td, "output.json")) + if data.get("unsupported"): + raise UnsupportedOperation(data.get("traceback", "")) + if data.get("no_backend"): + raise BackendUnavailable( + data.get("traceback", ""), + message=data.get("backend_error", ""), backend_name=self.build_backend, backend_path=self.backend_path, - message=data.get('backend_error', '') ) - if data.get('hook_missing'): - raise HookMissing(data.get('missing_hook_name') or hook_name) - return data['return_val'] + if data.get("hook_missing"): + raise HookMissing(data.get("missing_hook_name") or hook_name) + return data["return_val"] diff --git a/src/pip/_vendor/pyproject_hooks/_in_process/__init__.py b/src/pip/_vendor/pyproject_hooks/_in_process/__init__.py index 917fa065b3c..906d0ba20e1 100644 --- a/src/pip/_vendor/pyproject_hooks/_in_process/__init__.py +++ b/src/pip/_vendor/pyproject_hooks/_in_process/__init__.py @@ -11,8 +11,11 @@ except AttributeError: # Python 3.8 compatibility def _in_proc_script_path(): - return resources.path(__package__, '_in_process.py') + return resources.path(__package__, "_in_process.py") + else: + def _in_proc_script_path(): return resources.as_file( - resources.files(__package__).joinpath('_in_process.py')) + resources.files(__package__).joinpath("_in_process.py") + ) diff --git a/src/pip/_vendor/pyproject_hooks/_in_process/_in_process.py b/src/pip/_vendor/pyproject_hooks/_in_process/_in_process.py index ee511ff20d7..d689bab7219 100644 --- a/src/pip/_vendor/pyproject_hooks/_in_process/_in_process.py +++ b/src/pip/_vendor/pyproject_hooks/_in_process/_in_process.py @@ -3,8 +3,8 @@ It expects: - Command line args: hook_name, control_dir - Environment variables: - PEP517_BUILD_BACKEND=entry.point:spec - PEP517_BACKEND_PATH=paths (separated with os.pathsep) + _PYPROJECT_HOOKS_BUILD_BACKEND=entry.point:spec + _PYPROJECT_HOOKS_BACKEND_PATH=paths (separated with os.pathsep) - control_dir/input.json: - {"kwargs": {...}} @@ -21,6 +21,7 @@ import traceback from glob import glob from importlib import import_module +from importlib.machinery import PathFinder from os.path import join as pjoin # This file is run as a script, and `import wrappers` is not zip-safe, so we @@ -28,69 +29,93 @@ def write_json(obj, path, **kwargs): - with open(path, 'w', encoding='utf-8') as f: + with open(path, "w", encoding="utf-8") as f: json.dump(obj, f, **kwargs) def read_json(path): - with open(path, encoding='utf-8') as f: + with open(path, encoding="utf-8") as f: return json.load(f) class BackendUnavailable(Exception): """Raised if we cannot import the backend""" - def __init__(self, traceback): - self.traceback = traceback - -class BackendInvalid(Exception): - """Raised if the backend is invalid""" - def __init__(self, message): + def __init__(self, message, traceback=None): + super().__init__(message) self.message = message + self.traceback = traceback class HookMissing(Exception): """Raised if a hook is missing and we are not executing the fallback""" + def __init__(self, hook_name=None): super().__init__(hook_name) self.hook_name = hook_name -def contained_in(filename, directory): - """Test if a file is located within the given directory.""" - filename = os.path.normcase(os.path.abspath(filename)) - directory = os.path.normcase(os.path.abspath(directory)) - return os.path.commonprefix([filename, directory]) == directory - - def _build_backend(): """Find and load the build backend""" - # Add in-tree backend directories to the front of sys.path. - backend_path = os.environ.get('PEP517_BACKEND_PATH') + backend_path = os.environ.get("_PYPROJECT_HOOKS_BACKEND_PATH") + ep = os.environ["_PYPROJECT_HOOKS_BUILD_BACKEND"] + mod_path, _, obj_path = ep.partition(":") + if backend_path: + # Ensure in-tree backend directories have the highest priority when importing. extra_pathitems = backend_path.split(os.pathsep) - sys.path[:0] = extra_pathitems + sys.meta_path.insert(0, _BackendPathFinder(extra_pathitems, mod_path)) - ep = os.environ['PEP517_BUILD_BACKEND'] - mod_path, _, obj_path = ep.partition(':') try: obj = import_module(mod_path) except ImportError: - raise BackendUnavailable(traceback.format_exc()) - - if backend_path: - if not any( - contained_in(obj.__file__, path) - for path in extra_pathitems - ): - raise BackendInvalid("Backend was not loaded from backend-path") + msg = f"Cannot import {mod_path!r}" + raise BackendUnavailable(msg, traceback.format_exc()) if obj_path: - for path_part in obj_path.split('.'): + for path_part in obj_path.split("."): obj = getattr(obj, path_part) return obj +class _BackendPathFinder: + """Implements the MetaPathFinder interface to locate modules in ``backend-path``. + + Since the environment provided by the frontend can contain all sorts of + MetaPathFinders, the only way to ensure the backend is loaded from the + right place is to prepend our own. + """ + + def __init__(self, backend_path, backend_module): + self.backend_path = backend_path + self.backend_module = backend_module + self.backend_parent, _, _ = backend_module.partition(".") + + def find_spec(self, fullname, _path, _target=None): + if "." in fullname: + # Rely on importlib to find nested modules based on parent's path + return None + + # Ignore other items in _path or sys.path and use backend_path instead: + spec = PathFinder.find_spec(fullname, path=self.backend_path) + if spec is None and fullname == self.backend_parent: + # According to the spec, the backend MUST be loaded from backend-path. + # Therefore, we can halt the import machinery and raise a clean error. + msg = f"Cannot find module {self.backend_module!r} in {self.backend_path!r}" + raise BackendUnavailable(msg) + + return spec + + if sys.version_info >= (3, 8): + + def find_distributions(self, context=None): + # Delayed import: Python 3.7 does not contain importlib.metadata + from importlib.metadata import DistributionFinder, MetadataPathFinder + + context = DistributionFinder.Context(path=self.backend_path) + return MetadataPathFinder.find_distributions(context=context) + + def _supported_features(): """Return the list of options features supported by the backend. @@ -133,7 +158,8 @@ def get_requires_for_build_editable(config_settings): def prepare_metadata_for_build_wheel( - metadata_directory, config_settings, _allow_fallback): + metadata_directory, config_settings, _allow_fallback +): """Invoke optional prepare_metadata_for_build_wheel Implements a fallback by building a wheel if the hook isn't defined, @@ -150,12 +176,14 @@ def prepare_metadata_for_build_wheel( # fallback to build_wheel outside the try block to avoid exception chaining # which can be confusing to users and is not relevant whl_basename = backend.build_wheel(metadata_directory, config_settings) - return _get_wheel_metadata_from_wheel(whl_basename, metadata_directory, - config_settings) + return _get_wheel_metadata_from_wheel( + whl_basename, metadata_directory, config_settings + ) def prepare_metadata_for_build_editable( - metadata_directory, config_settings, _allow_fallback): + metadata_directory, config_settings, _allow_fallback +): """Invoke optional prepare_metadata_for_build_editable Implements a fallback by building an editable wheel if the hook isn't @@ -171,24 +199,24 @@ def prepare_metadata_for_build_editable( try: build_hook = backend.build_editable except AttributeError: - raise HookMissing(hook_name='build_editable') + raise HookMissing(hook_name="build_editable") else: whl_basename = build_hook(metadata_directory, config_settings) - return _get_wheel_metadata_from_wheel(whl_basename, - metadata_directory, - config_settings) + return _get_wheel_metadata_from_wheel( + whl_basename, metadata_directory, config_settings + ) else: return hook(metadata_directory, config_settings) -WHEEL_BUILT_MARKER = 'PEP517_ALREADY_BUILT_WHEEL' +WHEEL_BUILT_MARKER = "PYPROJECT_HOOKS_ALREADY_BUILT_WHEEL" def _dist_info_files(whl_zip): """Identify the .dist-info folder inside a wheel ZipFile.""" res = [] for path in whl_zip.namelist(): - m = re.match(r'[^/\\]+-[^/\\]+\.dist-info/', path) + m = re.match(r"[^/\\]+-[^/\\]+\.dist-info/", path) if m: res.append(path) if res: @@ -196,40 +224,41 @@ def _dist_info_files(whl_zip): raise Exception("No .dist-info folder found in wheel") -def _get_wheel_metadata_from_wheel( - whl_basename, metadata_directory, config_settings): +def _get_wheel_metadata_from_wheel(whl_basename, metadata_directory, config_settings): """Extract the metadata from a wheel. Fallback for when the build backend does not define the 'get_wheel_metadata' hook. """ from zipfile import ZipFile - with open(os.path.join(metadata_directory, WHEEL_BUILT_MARKER), 'wb'): + + with open(os.path.join(metadata_directory, WHEEL_BUILT_MARKER), "wb"): pass # Touch marker file whl_file = os.path.join(metadata_directory, whl_basename) with ZipFile(whl_file) as zipf: dist_info = _dist_info_files(zipf) zipf.extractall(path=metadata_directory, members=dist_info) - return dist_info[0].split('/')[0] + return dist_info[0].split("/")[0] def _find_already_built_wheel(metadata_directory): - """Check for a wheel already built during the get_wheel_metadata hook. - """ + """Check for a wheel already built during the get_wheel_metadata hook.""" if not metadata_directory: return None metadata_parent = os.path.dirname(metadata_directory) if not os.path.isfile(pjoin(metadata_parent, WHEEL_BUILT_MARKER)): return None - whl_files = glob(os.path.join(metadata_parent, '*.whl')) + whl_files = glob(os.path.join(metadata_parent, "*.whl")) if not whl_files: - print('Found wheel built marker, but no .whl files') + print("Found wheel built marker, but no .whl files") return None if len(whl_files) > 1: - print('Found multiple .whl files; unspecified behaviour. ' - 'Will call build_wheel.') + print( + "Found multiple .whl files; unspecified behaviour. " + "Will call build_wheel." + ) return None # Exactly one .whl file @@ -248,8 +277,9 @@ def build_wheel(wheel_directory, config_settings, metadata_directory=None): shutil.copy2(prebuilt_whl, wheel_directory) return os.path.basename(prebuilt_whl) - return _build_backend().build_wheel(wheel_directory, config_settings, - metadata_directory) + return _build_backend().build_wheel( + wheel_directory, config_settings, metadata_directory + ) def build_editable(wheel_directory, config_settings, metadata_directory=None): @@ -293,6 +323,7 @@ class _DummyException(Exception): class GotUnsupportedOperation(Exception): """For internal use when backend raises UnsupportedOperation""" + def __init__(self, traceback): self.traceback = traceback @@ -302,20 +333,20 @@ def build_sdist(sdist_directory, config_settings): backend = _build_backend() try: return backend.build_sdist(sdist_directory, config_settings) - except getattr(backend, 'UnsupportedOperation', _DummyException): + except getattr(backend, "UnsupportedOperation", _DummyException): raise GotUnsupportedOperation(traceback.format_exc()) HOOK_NAMES = { - 'get_requires_for_build_wheel', - 'prepare_metadata_for_build_wheel', - 'build_wheel', - 'get_requires_for_build_editable', - 'prepare_metadata_for_build_editable', - 'build_editable', - 'get_requires_for_build_sdist', - 'build_sdist', - '_supported_features', + "get_requires_for_build_wheel", + "prepare_metadata_for_build_wheel", + "build_wheel", + "get_requires_for_build_editable", + "prepare_metadata_for_build_editable", + "build_editable", + "get_requires_for_build_sdist", + "build_sdist", + "_supported_features", } @@ -326,28 +357,33 @@ def main(): control_dir = sys.argv[2] if hook_name not in HOOK_NAMES: sys.exit("Unknown hook: %s" % hook_name) + + # Remove the parent directory from sys.path to avoid polluting the backend + # import namespace with this directory. + here = os.path.dirname(__file__) + if here in sys.path: + sys.path.remove(here) + hook = globals()[hook_name] - hook_input = read_json(pjoin(control_dir, 'input.json')) + hook_input = read_json(pjoin(control_dir, "input.json")) - json_out = {'unsupported': False, 'return_val': None} + json_out = {"unsupported": False, "return_val": None} try: - json_out['return_val'] = hook(**hook_input['kwargs']) + json_out["return_val"] = hook(**hook_input["kwargs"]) except BackendUnavailable as e: - json_out['no_backend'] = True - json_out['traceback'] = e.traceback - except BackendInvalid as e: - json_out['backend_invalid'] = True - json_out['backend_error'] = e.message + json_out["no_backend"] = True + json_out["traceback"] = e.traceback + json_out["backend_error"] = e.message except GotUnsupportedOperation as e: - json_out['unsupported'] = True - json_out['traceback'] = e.traceback + json_out["unsupported"] = True + json_out["traceback"] = e.traceback except HookMissing as e: - json_out['hook_missing'] = True - json_out['missing_hook_name'] = e.hook_name or hook_name + json_out["hook_missing"] = True + json_out["missing_hook_name"] = e.hook_name or hook_name - write_json(json_out, pjoin(control_dir, 'output.json'), indent=2) + write_json(json_out, pjoin(control_dir, "output.json"), indent=2) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/src/pip/_vendor/pyproject_hooks/py.typed b/src/pip/_vendor/pyproject_hooks/py.typed new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/pip/_vendor/vendor.txt b/src/pip/_vendor/vendor.txt index f86b8b54a53..f04a9c1e73c 100644 --- a/src/pip/_vendor/vendor.txt +++ b/src/pip/_vendor/vendor.txt @@ -4,7 +4,7 @@ distro==1.9.0 msgpack==1.1.0 packaging==24.2 platformdirs==4.3.6 -pyproject-hooks==1.0.0 +pyproject-hooks==1.2.0 requests==2.32.3 certifi==2024.8.30 idna==3.10 From a670a6a5b271c4be2b8643e8a9e9ca4bfda5de21 Mon Sep 17 00:00:00 2001 From: Richard Si Date: Sun, 22 Dec 2024 19:23:22 -0500 Subject: [PATCH 2/2] Fix ConfiguredBuildBackendHookCaller type annotations --- .../operations/build/metadata_editable.py | 1 + src/pip/_internal/utils/misc.py | 27 ++++++++++--------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/pip/_internal/operations/build/metadata_editable.py b/src/pip/_internal/operations/build/metadata_editable.py index 27c69f0d1ea..3397ccf0f92 100644 --- a/src/pip/_internal/operations/build/metadata_editable.py +++ b/src/pip/_internal/operations/build/metadata_editable.py @@ -38,4 +38,5 @@ def generate_editable_metadata( except InstallationSubprocessError as error: raise MetadataGenerationFailed(package_details=details) from error + assert distinfo_dir is not None return os.path.join(metadata_dir, distinfo_dir) diff --git a/src/pip/_internal/utils/misc.py b/src/pip/_internal/utils/misc.py index c07405267d6..44f6a05fbdd 100644 --- a/src/pip/_internal/utils/misc.py +++ b/src/pip/_internal/utils/misc.py @@ -19,12 +19,13 @@ Any, BinaryIO, Callable, - Dict, Generator, Iterable, Iterator, List, + Mapping, Optional, + Sequence, TextIO, Tuple, Type, @@ -667,7 +668,7 @@ def __init__( def build_wheel( self, wheel_directory: str, - config_settings: Optional[Dict[str, Union[str, List[str]]]] = None, + config_settings: Optional[Mapping[str, Any]] = None, metadata_directory: Optional[str] = None, ) -> str: cs = self.config_holder.config_settings @@ -678,7 +679,7 @@ def build_wheel( def build_sdist( self, sdist_directory: str, - config_settings: Optional[Dict[str, Union[str, List[str]]]] = None, + config_settings: Optional[Mapping[str, Any]] = None, ) -> str: cs = self.config_holder.config_settings return super().build_sdist(sdist_directory, config_settings=cs) @@ -686,7 +687,7 @@ def build_sdist( def build_editable( self, wheel_directory: str, - config_settings: Optional[Dict[str, Union[str, List[str]]]] = None, + config_settings: Optional[Mapping[str, Any]] = None, metadata_directory: Optional[str] = None, ) -> str: cs = self.config_holder.config_settings @@ -695,27 +696,27 @@ def build_editable( ) def get_requires_for_build_wheel( - self, config_settings: Optional[Dict[str, Union[str, List[str]]]] = None - ) -> List[str]: + self, config_settings: Optional[Mapping[str, Any]] = None + ) -> Sequence[str]: cs = self.config_holder.config_settings return super().get_requires_for_build_wheel(config_settings=cs) def get_requires_for_build_sdist( - self, config_settings: Optional[Dict[str, Union[str, List[str]]]] = None - ) -> List[str]: + self, config_settings: Optional[Mapping[str, Any]] = None + ) -> Sequence[str]: cs = self.config_holder.config_settings return super().get_requires_for_build_sdist(config_settings=cs) def get_requires_for_build_editable( - self, config_settings: Optional[Dict[str, Union[str, List[str]]]] = None - ) -> List[str]: + self, config_settings: Optional[Mapping[str, Any]] = None + ) -> Sequence[str]: cs = self.config_holder.config_settings return super().get_requires_for_build_editable(config_settings=cs) def prepare_metadata_for_build_wheel( self, metadata_directory: str, - config_settings: Optional[Dict[str, Union[str, List[str]]]] = None, + config_settings: Optional[Mapping[str, Any]] = None, _allow_fallback: bool = True, ) -> str: cs = self.config_holder.config_settings @@ -728,9 +729,9 @@ def prepare_metadata_for_build_wheel( def prepare_metadata_for_build_editable( self, metadata_directory: str, - config_settings: Optional[Dict[str, Union[str, List[str]]]] = None, + config_settings: Optional[Mapping[str, Any]] = None, _allow_fallback: bool = True, - ) -> str: + ) -> Optional[str]: cs = self.config_holder.config_settings return super().prepare_metadata_for_build_editable( metadata_directory=metadata_directory,