Skip to content

Commit

Permalink
Implement --break-system-packages for EXTERNALLY-MANAGED installations
Browse files Browse the repository at this point in the history
The PEP expected an override mechanism to ease the transition to PEP-668
managed installs. This provides a command-line-driven override.
  • Loading branch information
stefanor committed Feb 5, 2023
1 parent 684521f commit 07e5880
Show file tree
Hide file tree
Showing 6 changed files with 38 additions and 3 deletions.
2 changes: 2 additions & 0 deletions news/11780.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Implement ``--break-system-packages`` to permit installing packages into
``EXTERNALLY-MANAGED`` Python installations.
8 changes: 8 additions & 0 deletions src/pip/_internal/cli/cmdoptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,14 @@ class PipOption(Option):
),
)

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",
Expand Down
6 changes: 5 additions & 1 deletion src/pip/_internal/commands/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ def add_options(self) -> None:
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())
Expand Down Expand Up @@ -296,7 +297,10 @@ def run(self, options: Values, args: List[str]) -> int:
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"
Expand Down
4 changes: 3 additions & 1 deletion src/pip/_internal/commands/uninstall.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ def add_options(self) -> None:
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:
Expand Down Expand Up @@ -93,7 +94,8 @@ def run(self, options: Values, args: List[str]) -> int:
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
Expand Down
4 changes: 3 additions & 1 deletion src/pip/_internal/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -696,7 +696,9 @@ def __init__(self, error: Optional[str]) -> None:
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."),
)
Expand Down
17 changes: 17 additions & 0 deletions tests/functional/test_pep668.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,23 @@ def test_fails(script: PipTestEnvironment, arguments: List[str]) -> None:
assert "I am externally managed" in result.stderr


@pytest.mark.parametrize(
"arguments",
[
pytest.param(["install"], id="install"),
pytest.param(["install", "--user"], id="install-user"),
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",
[
Expand Down

0 comments on commit 07e5880

Please sign in to comment.