diff --git a/pyproject.toml b/pyproject.toml index 7ae46c0c64..d665e2f708 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -274,7 +274,7 @@ max-complexity = 33 # default is 10 "src/pip/__pip-runner__.py" = ["UP"] # Must be compatible with Python 2.7 [tool.ruff.lint.pylint] -max-args = 15 # default is 5 +max-args = 16 # default is 5 max-branches = 28 # default is 12 max-returns = 15 # default is 6 max-statements = 134 # default is 50 diff --git a/src/pip/_internal/build_env/installer.py b/src/pip/_internal/build_env/installer.py index 845b07b2f9..790ad5f3b7 100644 --- a/src/pip/_internal/build_env/installer.py +++ b/src/pip/_internal/build_env/installer.py @@ -208,6 +208,7 @@ def __init__( use_user_site=False, lazy_wheel=False, legacy_resolver=False, + allow_editables=True, ) def install( @@ -281,7 +282,9 @@ def _install_impl(self, requirements: Iterable[str], prefix: Prefix) -> None: reqs_to_build = [ r for r in resolved_set.requirements_to_install if not r.is_wheel ] - _, build_failures = build(reqs_to_build, self._wheel_cache, verify=True) + _, build_failures = build( + reqs_to_build, self._wheel_cache, verify=True, allow_editables=True + ) if build_failures: raise InstallWheelBuildError(build_failures) diff --git a/src/pip/_internal/cli/req_command.py b/src/pip/_internal/cli/req_command.py index 820db8f0a8..f09267f35b 100644 --- a/src/pip/_internal/cli/req_command.py +++ b/src/pip/_internal/cli/req_command.py @@ -162,6 +162,8 @@ def make_requirement_preparer( build_tracker: BuildTracker, session: PipSession, finder: PackageFinder, + *, + allow_editables: bool, use_user_site: bool, download_dir: str | None = None, verbosity: int = 0, @@ -242,6 +244,7 @@ def make_requirement_preparer( lazy_wheel=lazy_wheel, verbosity=verbosity, legacy_resolver=legacy_resolver, + allow_editables=allow_editables, ) @classmethod diff --git a/src/pip/_internal/commands/download.py b/src/pip/_internal/commands/download.py index 8f7a874e4c..8b34982960 100644 --- a/src/pip/_internal/commands/download.py +++ b/src/pip/_internal/commands/download.py @@ -120,6 +120,7 @@ def run(self, options: Values, args: list[str]) -> int: download_dir=options.download_dir, use_user_site=False, verbosity=self.verbosity, + allow_editables=False, ) resolver = self.make_resolver( diff --git a/src/pip/_internal/commands/install.py b/src/pip/_internal/commands/install.py index a5955417cb..e1482b9cd4 100644 --- a/src/pip/_internal/commands/install.py +++ b/src/pip/_internal/commands/install.py @@ -450,12 +450,6 @@ def run(self, options: Values, args: list[str]) -> int: wheel_cache = WheelCache(options.cache_dir) - # Only when installing is it permitted to use PEP 660. - # In other circumstances (pip wheel, pip download) we generate - # regular (i.e. non editable) metadata and wheels. - for req in reqs: - req.permit_editable_wheels = True - preparer = self.make_requirement_preparer( temp_build_dir=directory, options=options, @@ -464,6 +458,7 @@ def run(self, options: Values, args: list[str]) -> int: finder=finder, use_user_site=options.use_user_site, verbosity=self.verbosity, + allow_editables=True, ) resolver = self.make_resolver( preparer=preparer, @@ -529,6 +524,7 @@ def run(self, options: Values, args: list[str]) -> int: reqs_to_build, wheel_cache=wheel_cache, verify=True, + allow_editables=True, ) if build_failures: diff --git a/src/pip/_internal/commands/lock.py b/src/pip/_internal/commands/lock.py index ef15e8daee..73df8649a8 100644 --- a/src/pip/_internal/commands/lock.py +++ b/src/pip/_internal/commands/lock.py @@ -126,12 +126,6 @@ def run(self, options: Values, args: list[str]) -> int: wheel_cache = WheelCache(options.cache_dir) - # Only when installing is it permitted to use PEP 660. - # In other circumstances (pip wheel, pip download) we generate - # regular (i.e. non editable) metadata and wheels. - for req in reqs: - req.permit_editable_wheels = True - preparer = self.make_requirement_preparer( temp_build_dir=directory, options=options, @@ -140,6 +134,7 @@ def run(self, options: Values, args: list[str]) -> int: finder=finder, use_user_site=False, verbosity=self.verbosity, + allow_editables=True, ) resolver = self.make_resolver( preparer=preparer, diff --git a/src/pip/_internal/commands/wheel.py b/src/pip/_internal/commands/wheel.py index a539a4df29..ee25b8265d 100644 --- a/src/pip/_internal/commands/wheel.py +++ b/src/pip/_internal/commands/wheel.py @@ -126,6 +126,7 @@ def run(self, options: Values, args: list[str]) -> int: download_dir=options.wheel_dir, use_user_site=False, verbosity=self.verbosity, + allow_editables=False, ) resolver = self.make_resolver( @@ -154,6 +155,7 @@ def run(self, options: Values, args: list[str]) -> int: reqs_to_build, wheel_cache=wheel_cache, verify=(not options.no_verify), + allow_editables=False, ) for req in build_successes: assert req.link and req.link.is_wheel diff --git a/src/pip/_internal/distributions/base.py b/src/pip/_internal/distributions/base.py index 01f2aa8f70..66cc5426d8 100644 --- a/src/pip/_internal/distributions/base.py +++ b/src/pip/_internal/distributions/base.py @@ -52,5 +52,6 @@ def prepare_distribution_metadata( build_env_installer: BuildEnvironmentInstaller, build_isolation: BuildIsolationMode, check_build_deps: bool, + allow_editables: bool, ) -> None: raise NotImplementedError() diff --git a/src/pip/_internal/distributions/installed.py b/src/pip/_internal/distributions/installed.py index 3321e29787..06cff259a4 100644 --- a/src/pip/_internal/distributions/installed.py +++ b/src/pip/_internal/distributions/installed.py @@ -29,5 +29,6 @@ def prepare_distribution_metadata( build_env_installer: BuildEnvironmentInstaller, build_isolation: BuildIsolationMode, check_build_deps: bool, + allow_editables: bool, ) -> None: pass diff --git a/src/pip/_internal/distributions/sdist.py b/src/pip/_internal/distributions/sdist.py index 007c4822eb..ac2cab4086 100644 --- a/src/pip/_internal/distributions/sdist.py +++ b/src/pip/_internal/distributions/sdist.py @@ -42,6 +42,7 @@ def prepare_distribution_metadata( build_env_installer: BuildEnvironmentInstaller, build_isolation: BuildIsolationMode, check_build_deps: bool, + allow_editables: bool, ) -> None: # Load pyproject.toml and set up backend environment self.req.load_pyproject_toml() @@ -58,7 +59,9 @@ def prepare_distribution_metadata( # to avoid installing build requirements needlessly. self.req.editable_sanity_check() # Install the dynamic build requirements. - self._install_build_reqs(build_env_installer) + self._install_build_reqs( + build_env_installer, allow_editables=allow_editables + ) else: # When not using build isolation, we still need to check that # the build backend supports PEP 660. @@ -74,7 +77,7 @@ def prepare_distribution_metadata( self._raise_conflicts("the backend dependencies", conflicting) if missing: self._raise_missing_reqs(missing) - self.req.prepare_metadata() + self.req.prepare_metadata(allow_editables) def _prepare_build_env( self, @@ -136,14 +139,16 @@ def _get_build_requires_editable(self) -> Iterable[str]: return backend.get_requires_for_build_editable() def _install_build_reqs( - self, build_env_installer: BuildEnvironmentInstaller + self, + build_env_installer: BuildEnvironmentInstaller, + allow_editables: bool, ) -> None: # Install any extra build dependencies that the backend requests. # This must be done in a second pass, as the pyproject.toml # dependencies must be installed before we can call the backend. if ( self.req.editable - and self.req.permit_editable_wheels + and allow_editables and self.req.supports_pyproject_editable ): build_reqs = self._get_build_requires_editable() diff --git a/src/pip/_internal/distributions/wheel.py b/src/pip/_internal/distributions/wheel.py index 20db4ab741..8f2a217fd5 100644 --- a/src/pip/_internal/distributions/wheel.py +++ b/src/pip/_internal/distributions/wheel.py @@ -40,5 +40,6 @@ def prepare_distribution_metadata( build_env_installer: BuildEnvironmentInstaller, build_isolation: BuildIsolationMode, check_build_deps: bool, + allow_editables: bool, ) -> None: pass diff --git a/src/pip/_internal/operations/prepare.py b/src/pip/_internal/operations/prepare.py index c9e9780254..34a07a18e3 100644 --- a/src/pip/_internal/operations/prepare.py +++ b/src/pip/_internal/operations/prepare.py @@ -72,6 +72,7 @@ def _get_prepared_distribution( build_env_installer: BuildEnvironmentInstaller, build_isolation: BuildIsolationMode, check_build_deps: bool, + allow_editables: bool, ) -> BaseDistribution: """Prepare a distribution for installation.""" abstract_dist = make_distribution_for_install_requirement(req) @@ -79,7 +80,7 @@ def _get_prepared_distribution( if tracker_id is not None: with build_tracker.track(req, tracker_id): abstract_dist.prepare_distribution_metadata( - build_env_installer, build_isolation, check_build_deps + build_env_installer, build_isolation, check_build_deps, allow_editables ) return abstract_dist.get_metadata_distribution() @@ -359,6 +360,7 @@ def __init__( lazy_wheel: bool, verbosity: int, legacy_resolver: bool, + allow_editables: bool, ) -> None: super().__init__() @@ -401,6 +403,10 @@ def __init__( # Previous "header" printed for a link-based InstallRequirement self._previous_requirement_header = ("", "") + # Do we allow preparing editable wheels? + # When using pip wheel, editables must still produce regular wheels + self.allow_editables = allow_editables + def _log_preparing_link(self, req: InstallRequirement) -> None: """Provide context for the requirement being prepared.""" if req.link.is_file and not req.is_wheel_from_cache: @@ -780,6 +786,7 @@ def _prepare_linked_requirement( self.build_env_installer, self.build_isolation, self.check_build_deps, + self.allow_editables, ) # If a PEP 658 .metadata file was used, check that fields relevant for @@ -857,6 +864,7 @@ def prepare_editable_requirement( self.build_env_installer, self.build_isolation, self.check_build_deps, + self.allow_editables, ) req.check_if_exists(self.use_user_site) diff --git a/src/pip/_internal/req/constructors.py b/src/pip/_internal/req/constructors.py index daae778865..6dcac66737 100644 --- a/src/pip/_internal/req/constructors.py +++ b/src/pip/_internal/req/constructors.py @@ -260,7 +260,6 @@ def install_req_from_editable( hash_options: dict[str, list[str]] | None = None, constraint: bool = False, user_supplied: bool = False, - permit_editable_wheels: bool = False, config_settings: dict[str, str | list[str]] | None = None, ) -> InstallRequirement: if constraint: @@ -272,7 +271,6 @@ def install_req_from_editable( comes_from=comes_from, user_supplied=user_supplied, editable=True, - permit_editable_wheels=permit_editable_wheels, link=parts.link, constraint=constraint, isolated=isolated, @@ -552,7 +550,6 @@ def install_req_drop_extras(ireq: InstallRequirement) -> InstallRequirement: extras=[], config_settings=ireq.config_settings, user_supplied=ireq.user_supplied, - permit_editable_wheels=ireq.permit_editable_wheels, ) diff --git a/src/pip/_internal/req/req_install.py b/src/pip/_internal/req/req_install.py index 5e94679a69..7191f03a1a 100644 --- a/src/pip/_internal/req/req_install.py +++ b/src/pip/_internal/req/req_install.py @@ -79,7 +79,6 @@ def __init__( constraint: bool = False, extras: Collection[str] = (), user_supplied: bool = False, - permit_editable_wheels: bool = False, locked_link: Link | None = None, locked_version: Version | None = None, ) -> None: @@ -88,7 +87,6 @@ def __init__( self.comes_from = comes_from self.constraint = constraint self.editable = editable - self.permit_editable_wheels = permit_editable_wheels # source_dir is the local directory where the linked requirement is # located, or unpacked. In case unpacking is needed, creating and @@ -527,7 +525,7 @@ def editable_sanity_check(self) -> None: f"Consider using a build backend that supports PEP 660." ) - def prepare_metadata(self) -> None: + def prepare_metadata(self, allow_editables: bool) -> None: """Ensure that project metadata is available. Under PEP 517 and PEP 660, call the backend hook to prepare the metadata. @@ -537,11 +535,7 @@ def prepare_metadata(self) -> None: details = self.name or f"from {self.link}" assert self.pep517_backend is not None - if ( - self.editable - and self.permit_editable_wheels - and self.supports_pyproject_editable - ): + if self.editable and allow_editables and self.supports_pyproject_editable: self.metadata_directory = generate_editable_metadata( build_env=self.build_env, backend=self.pep517_backend, diff --git a/src/pip/_internal/resolution/resolvelib/candidates.py b/src/pip/_internal/resolution/resolvelib/candidates.py index 4bfd829610..b8b853cb76 100644 --- a/src/pip/_internal/resolution/resolvelib/candidates.py +++ b/src/pip/_internal/resolution/resolvelib/candidates.py @@ -101,7 +101,6 @@ def make_install_req_from_editable( comes_from=template.comes_from, isolated=template.isolated, constraint=template.constraint, - permit_editable_wheels=template.permit_editable_wheels, hash_options=template.hash_options, config_settings=template.config_settings, ) diff --git a/src/pip/_internal/wheel_builder.py b/src/pip/_internal/wheel_builder.py index dd1d2b84b3..8a5526142f 100644 --- a/src/pip/_internal/wheel_builder.py +++ b/src/pip/_internal/wheel_builder.py @@ -208,6 +208,7 @@ def build( requirements: Iterable[InstallRequirement], wheel_cache: WheelCache, verify: bool, + allow_editables: bool, ) -> BuildResult: """Build wheels. @@ -232,7 +233,7 @@ def build( req, cache_dir, verify, - req.editable and req.permit_editable_wheels, + req.editable and allow_editables, ) if wheel_file: # Record the download origin in the cache diff --git a/tests/unit/resolution_resolvelib/conftest.py b/tests/unit/resolution_resolvelib/conftest.py index b24ba4e1a2..79d3d36340 100644 --- a/tests/unit/resolution_resolvelib/conftest.py +++ b/tests/unit/resolution_resolvelib/conftest.py @@ -48,6 +48,7 @@ def preparer(finder: PackageFinder) -> Iterator[RequirementPreparer]: finder=finder, use_user_site=False, verbosity=0, + allow_editables=True, ) yield preparer diff --git a/tests/unit/test_req.py b/tests/unit/test_req.py index 5366ff7e7c..7197db1fc6 100644 --- a/tests/unit/test_req.py +++ b/tests/unit/test_req.py @@ -120,6 +120,7 @@ def _basic_resolver( lazy_wheel=False, verbosity=0, legacy_resolver=True, + allow_editables=True, ) yield Resolver( preparer=preparer, @@ -861,7 +862,6 @@ def test_install_req_drop_extras(self, inp: str, out: str) -> None: assert without_extras.constraint == req.constraint assert without_extras.config_settings == req.config_settings assert without_extras.user_supplied == req.user_supplied - assert without_extras.permit_editable_wheels == req.permit_editable_wheels @pytest.mark.parametrize( "inp, extras, out", @@ -908,7 +908,6 @@ def test_install_req_extend_extras( assert extended.constraint == req.constraint assert extended.config_settings == req.config_settings assert extended.user_supplied == req.user_supplied - assert extended.permit_editable_wheels == req.permit_editable_wheels @pytest.mark.parametrize(