Skip to content

Commit aa83bad

Browse files
committed
Implement pip install --break-system-packages, as a PEP-668 override
The PEP expected an override mechanism to ease the transition to PEP-668 managed installs. This provides a command-line-driven override.
1 parent 684521f commit aa83bad

File tree

6 files changed

+36
-2
lines changed

6 files changed

+36
-2
lines changed

news/11780.feature.rst

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Implement ``--break-system-packages`` to permit installing packages into
2+
``EXTERNALLY-MANAGED`` Python installations.

src/pip/_internal/cli/cmdoptions.py

+8
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,14 @@ class PipOption(Option):
164164
),
165165
)
166166

167+
override_externally_managed: Callable[..., Option] = partial(
168+
Option,
169+
"--break-system-packages",
170+
dest="override_externally_managed",
171+
action="store_true",
172+
help="Allow pip to install into an EXTERNALLY-MANAGED Python installation"
173+
)
174+
167175
python: Callable[..., Option] = partial(
168176
Option,
169177
"--python",

src/pip/_internal/commands/install.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ def add_options(self) -> None:
215215
self.cmd_opts.add_option(cmdoptions.use_pep517())
216216
self.cmd_opts.add_option(cmdoptions.no_use_pep517())
217217
self.cmd_opts.add_option(cmdoptions.check_build_deps())
218+
self.cmd_opts.add_option(cmdoptions.override_externally_managed())
218219

219220
self.cmd_opts.add_option(cmdoptions.config_settings())
220221
self.cmd_opts.add_option(cmdoptions.install_options())
@@ -296,7 +297,10 @@ def run(self, options: Values, args: List[str]) -> int:
296297
and options.target_dir is None
297298
and options.prefix_path is None
298299
)
299-
if installing_into_current_environment:
300+
if (
301+
installing_into_current_environment
302+
and not options.override_externally_managed
303+
):
300304
check_externally_managed()
301305

302306
upgrade_strategy = "to-satisfy-only"

src/pip/_internal/commands/uninstall.py

+1
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ def add_options(self) -> None:
5858
help="Don't ask for confirmation of uninstall deletions.",
5959
)
6060
self.cmd_opts.add_option(cmdoptions.root_user_action())
61+
self.cmd_opts.add_option(cmdoptions.override_externally_managed())
6162
self.parser.insert_option_group(0, self.cmd_opts)
6263

6364
def run(self, options: Values, args: List[str]) -> int:

src/pip/_internal/exceptions.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -696,7 +696,9 @@ def __init__(self, error: Optional[str]) -> None:
696696
context=context,
697697
note_stmt=(
698698
"If you believe this is a mistake, please contact your "
699-
"Python installation or OS distribution provider."
699+
"Python installation or OS distribution provider. "
700+
"You can override this, at the risk of breaking your Python "
701+
"installation or OS, by passing --break-system-packages."
700702
),
701703
hint_stmt=Text("See PEP 668 for the detailed specification."),
702704
)

tests/functional/test_pep668.py

+17
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,23 @@ def test_fails(script: PipTestEnvironment, arguments: List[str]) -> None:
4242
assert "I am externally managed" in result.stderr
4343

4444

45+
@pytest.mark.parametrize(
46+
"arguments",
47+
[
48+
pytest.param(["install"], id="install"),
49+
pytest.param(["install", "--user"], id="install-user"),
50+
pytest.param(["install", "--dry-run"], id="install-dry-run"),
51+
pytest.param(["uninstall", "-y"], id="uninstall"),
52+
],
53+
)
54+
@pytest.mark.usefixtures("patch_check_externally_managed")
55+
def test_succeeds_when_overridden(
56+
script: PipTestEnvironment, arguments: List[str]
57+
) -> None:
58+
result = script.pip(*arguments, "pip")
59+
assert "I am externally managed" not in result.stderr
60+
61+
4562
@pytest.mark.parametrize(
4663
"arguments",
4764
[

0 commit comments

Comments
 (0)