Skip to content

Commit e6deb9b

Browse files
stefanorpradyunsg
andcommitted
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 <[email protected]>
1 parent 864fd77 commit e6deb9b

File tree

6 files changed

+37
-3
lines changed

6 files changed

+37
-3
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 modify 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

+3-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:
@@ -93,7 +94,8 @@ def run(self, options: Values, args: List[str]) -> int:
9394
f'"pip help {self.name}")'
9495
)
9596

96-
check_externally_managed()
97+
if not options.override_externally_managed:
98+
check_externally_managed()
9799

98100
protect_pip_from_modification_on_windows(
99101
modifying_pip="pip" in reqs_to_uninstall

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

+16
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,22 @@ 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", "--dry-run"], id="install-dry-run"),
50+
pytest.param(["uninstall", "-y"], id="uninstall"),
51+
],
52+
)
53+
@pytest.mark.usefixtures("patch_check_externally_managed")
54+
def test_succeeds_when_overridden(
55+
script: PipTestEnvironment, arguments: List[str]
56+
) -> None:
57+
result = script.pip(*arguments, "pip", "--break-system-packages")
58+
assert "I am externally managed" not in result.stderr
59+
60+
4561
@pytest.mark.parametrize(
4662
"arguments",
4763
[

0 commit comments

Comments
 (0)