diff options
author | Stefano Rivera <stefano@rivera.za.net> | 2023-02-05 23:06:59 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-02-06 07:06:59 +0000 |
commit | 9abb3c899a3b6e4dad590791f3c8d2421bce66c5 (patch) | |
tree | 0af46e5981a1a23f0caf8a8bc604c9f79acb25e0 | |
parent | 62fb64ac9697e36efce3f72193e4bff0a39dbe14 (diff) | |
download | pip-9abb3c899a3b6e4dad590791f3c8d2421bce66c5.tar.gz |
Implement `--break-system-packages` for EXTERNALLY-MANAGED installations (#11780)
The PEP 668 expects an override mechanism to ease the transition.
This provides an override.
---------
Co-authored-by: Pradyun Gedam <pradyunsg@gmail.com>
-rw-r--r-- | news/11780.feature.rst | 2 | ||||
-rw-r--r-- | src/pip/_internal/cli/cmdoptions.py | 8 | ||||
-rw-r--r-- | src/pip/_internal/commands/install.py | 6 | ||||
-rw-r--r-- | src/pip/_internal/commands/uninstall.py | 4 | ||||
-rw-r--r-- | src/pip/_internal/exceptions.py | 4 | ||||
-rw-r--r-- | tests/functional/test_pep668.py | 16 |
6 files changed, 37 insertions, 3 deletions
diff --git a/news/11780.feature.rst b/news/11780.feature.rst new file mode 100644 index 000000000..b765de6c5 --- /dev/null +++ b/news/11780.feature.rst @@ -0,0 +1,2 @@ +Implement ``--break-system-packages`` to permit installing packages into +``EXTERNALLY-MANAGED`` Python installations. diff --git a/src/pip/_internal/cli/cmdoptions.py b/src/pip/_internal/cli/cmdoptions.py index 661c489c7..1f804097e 100644 --- a/src/pip/_internal/cli/cmdoptions.py +++ b/src/pip/_internal/cli/cmdoptions.py @@ -164,6 +164,14 @@ require_virtualenv: Callable[..., Option] = partial( ), ) +override_externally_managed: Callable[..., Option] = partial( + Option, + "--break-system-packages", + dest="override_externally_managed", + action="store_true", + help="Allow pip to modify an EXTERNALLY-MANAGED Python installation", +) + python: Callable[..., Option] = partial( Option, "--python", diff --git a/src/pip/_internal/commands/install.py b/src/pip/_internal/commands/install.py index cecaac2bc..b20aeddf8 100644 --- a/src/pip/_internal/commands/install.py +++ b/src/pip/_internal/commands/install.py @@ -215,6 +215,7 @@ class InstallCommand(RequirementCommand): self.cmd_opts.add_option(cmdoptions.use_pep517()) self.cmd_opts.add_option(cmdoptions.no_use_pep517()) self.cmd_opts.add_option(cmdoptions.check_build_deps()) + self.cmd_opts.add_option(cmdoptions.override_externally_managed()) self.cmd_opts.add_option(cmdoptions.config_settings()) self.cmd_opts.add_option(cmdoptions.install_options()) @@ -296,7 +297,10 @@ class InstallCommand(RequirementCommand): and options.target_dir is None and options.prefix_path is None ) - if installing_into_current_environment: + if ( + installing_into_current_environment + and not options.override_externally_managed + ): check_externally_managed() upgrade_strategy = "to-satisfy-only" diff --git a/src/pip/_internal/commands/uninstall.py b/src/pip/_internal/commands/uninstall.py index e5a4c8e10..f198fc313 100644 --- a/src/pip/_internal/commands/uninstall.py +++ b/src/pip/_internal/commands/uninstall.py @@ -58,6 +58,7 @@ class UninstallCommand(Command, SessionCommandMixin): help="Don't ask for confirmation of uninstall deletions.", ) self.cmd_opts.add_option(cmdoptions.root_user_action()) + self.cmd_opts.add_option(cmdoptions.override_externally_managed()) self.parser.insert_option_group(0, self.cmd_opts) def run(self, options: Values, args: List[str]) -> int: @@ -93,7 +94,8 @@ class UninstallCommand(Command, SessionCommandMixin): f'"pip help {self.name}")' ) - check_externally_managed() + if not options.override_externally_managed: + check_externally_managed() protect_pip_from_modification_on_windows( modifying_pip="pip" in reqs_to_uninstall diff --git a/src/pip/_internal/exceptions.py b/src/pip/_internal/exceptions.py index d28713ff7..d4527295d 100644 --- a/src/pip/_internal/exceptions.py +++ b/src/pip/_internal/exceptions.py @@ -696,7 +696,9 @@ class ExternallyManagedEnvironment(DiagnosticPipError): context=context, note_stmt=( "If you believe this is a mistake, please contact your " - "Python installation or OS distribution provider." + "Python installation or OS distribution provider. " + "You can override this, at the risk of breaking your Python " + "installation or OS, by passing --break-system-packages." ), hint_stmt=Text("See PEP 668 for the detailed specification."), ) diff --git a/tests/functional/test_pep668.py b/tests/functional/test_pep668.py index 1fed85e70..3c1085668 100644 --- a/tests/functional/test_pep668.py +++ b/tests/functional/test_pep668.py @@ -45,6 +45,22 @@ def test_fails(script: PipTestEnvironment, arguments: List[str]) -> None: @pytest.mark.parametrize( "arguments", [ + pytest.param(["install"], id="install"), + pytest.param(["install", "--dry-run"], id="install-dry-run"), + pytest.param(["uninstall", "-y"], id="uninstall"), + ], +) +@pytest.mark.usefixtures("patch_check_externally_managed") +def test_succeeds_when_overridden( + script: PipTestEnvironment, arguments: List[str] +) -> None: + result = script.pip(*arguments, "pip", "--break-system-packages") + assert "I am externally managed" not in result.stderr + + +@pytest.mark.parametrize( + "arguments", + [ pytest.param(["install", "--root"], id="install-root"), pytest.param(["install", "--prefix"], id="install-prefix"), pytest.param(["install", "--target"], id="install-target"), |