Skip to content

Commit 565e28f

Browse files
Fix uninject to keep shared dependencies
1 parent cf06cb9 commit 565e28f

File tree

3 files changed

+40
-7
lines changed

3 files changed

+40
-7
lines changed

changelog.d/1672.bugfix.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Prevent uninject from removing dependencies still required by other packages.

src/pipx/commands/uninject.py

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ def uninject_dep(
7878

7979
new_resource_paths = get_include_resource_paths(package_name, venv, local_bin_dir, local_man_dir)
8080

81+
deps_of_uninstalled: Set[str] = set()
82+
8183
if not leave_deps:
8284
orig_not_required_packages = venv.list_installed_packages(not_required=True)
8385
logger.info(f"Original not required packages: {orig_not_required_packages}")
@@ -89,15 +91,36 @@ def uninject_dep(
8991
logger.info(f"New not required packages: {new_not_required_packages}")
9092

9193
deps_of_uninstalled = new_not_required_packages - orig_not_required_packages
92-
if len(deps_of_uninstalled) == 0:
93-
pass
94-
else:
95-
logger.info(f"Dependencies of uninstalled package: {deps_of_uninstalled}")
94+
if deps_of_uninstalled:
95+
logger.info(f"Dependencies of uninstalled package (candidates): {deps_of_uninstalled}")
96+
97+
protected_deps: Set[str] = set()
98+
99+
main_pkg_name = canonicalize_name(venv.pipx_metadata.main_package.package)
100+
main_meta = venv.package_metadata.get(main_pkg_name)
101+
if main_meta is not None:
102+
protected_deps.update(getattr(main_meta, "depends", set()))
96103

97-
for dep_package_name in deps_of_uninstalled:
98-
venv.uninstall_package(package=dep_package_name, was_injected=False)
104+
for injected_name in venv.pipx_metadata.injected_packages:
105+
injected_name_canon = canonicalize_name(injected_name)
106+
if injected_name_canon == package_name:
107+
continue
108+
injected_meta = venv.package_metadata.get(injected_name_canon)
109+
if injected_meta is None:
110+
continue
111+
protected_deps.update(getattr(injected_meta, "depends", set()))
99112

100-
deps_string = " and its dependencies"
113+
logger.info(f"Protected dependencies (still required in venv): {protected_deps}")
114+
115+
deps_to_uninstall = sorted(deps_of_uninstalled - protected_deps)
116+
logger.info(f"Dependencies to uninstall after filtering: {deps_to_uninstall}")
117+
118+
for dep_package_name in deps_to_uninstall:
119+
venv.uninstall_package(package=dep_package_name, was_injected=False)
120+
121+
deps_string = " and its dependencies" if deps_to_uninstall else ""
122+
else:
123+
deps_string = ""
101124
else:
102125
deps_string = ""
103126

tests/test_uninject.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,12 @@ def test_uninject_leave_deps(pipx_temp_env, capsys, caplog):
3939
captured = capsys.readouterr()
4040
assert "Uninjected package black from venv pycowsay" in captured.out
4141
assert "Dependencies of uninstalled package:" not in caplog.text
42+
43+
def test_uninject_keeps_shared_dependencies(pipx_temp_env, capsys):
44+
assert not run_pipx_cli(["install", "pycowsay"])
45+
assert not run_pipx_cli(["inject", "pycowsay", PKG["black"]["spec"]])
46+
assert not run_pipx_cli(["inject", "pycowsay", PKG["pylint"]["spec"]])
47+
assert not run_pipx_cli(["uninject", "pycowsay", "black"])
48+
assert not run_pipx_cli(["list", "--include-injected"])
49+
captured = capsys.readouterr()
50+
assert "pylint" in captured.out

0 commit comments

Comments
 (0)