Skip to content

Commit 000b5ba

Browse files
authored
Cleanup unused dependencies (#6386)
* Cleanup unused dependencies * Add news * Correction
1 parent 9e706bd commit 000b5ba

File tree

3 files changed

+98
-1
lines changed

3 files changed

+98
-1
lines changed

news/6386.bugfix.rst

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Cleanup unused dependencies when upgrading packages.

pipenv/routines/update.py

+61-1
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,39 @@ def _resolve_and_update_lockfile(
441441
return upgrade_lock_data
442442

443443

444+
def _clean_unused_dependencies(
445+
project, lockfile, category, full_lock_resolution, original_lockfile
446+
):
447+
"""
448+
Remove dependencies that are no longer needed after an upgrade.
449+
450+
Args:
451+
project: The project instance
452+
lockfile: The current lockfile being built
453+
category: The category to clean (e.g., 'default', 'develop')
454+
full_lock_resolution: The complete resolution of dependencies
455+
original_lockfile: The original lockfile before the upgrade
456+
"""
457+
if category not in lockfile or category not in original_lockfile:
458+
return
459+
460+
# Get the set of packages in the new resolution
461+
resolved_packages = set(full_lock_resolution.keys())
462+
463+
# Get the set of packages in the original lockfile for this category
464+
original_packages = set(original_lockfile[category].keys())
465+
466+
# Find packages that were in the original lockfile but not in the new resolution
467+
unused_packages = original_packages - resolved_packages
468+
469+
# Remove unused packages from the lockfile
470+
for package_name in unused_packages:
471+
if package_name in lockfile[category]:
472+
if project.s.is_verbose():
473+
err.print(f"Removing unused dependency: {package_name}")
474+
del lockfile[category][package_name]
475+
476+
444477
def upgrade(
445478
project,
446479
pre=False,
@@ -456,6 +489,11 @@ def upgrade(
456489
):
457490
"""Enhanced upgrade command with dependency conflict detection."""
458491
lockfile = project.lockfile()
492+
# Store the original lockfile for comparison later
493+
original_lockfile = {
494+
k: v.copy() if isinstance(v, dict) else v for k, v in lockfile.items()
495+
}
496+
459497
if not pre:
460498
pre = project.settings.get("allow_prereleases")
461499

@@ -504,6 +542,8 @@ def upgrade(
504542

505543
# Process each category
506544
requested_packages = defaultdict(dict)
545+
category_resolutions = {}
546+
507547
for category in categories:
508548
pipfile_category = get_pipfile_category_using_lockfile_section(category)
509549

@@ -528,7 +568,7 @@ def upgrade(
528568
)
529569

530570
# Resolve dependencies and update lockfile
531-
_resolve_and_update_lockfile(
571+
upgrade_lock_data = _resolve_and_update_lockfile(
532572
project,
533573
requested_packages,
534574
pipfile_category,
@@ -540,6 +580,26 @@ def upgrade(
540580
lockfile,
541581
)
542582

583+
# Store the full resolution for this category
584+
if upgrade_lock_data:
585+
complete_packages = project.parsed_pipfile.get(pipfile_category, {})
586+
full_lock_resolution = venv_resolve_deps(
587+
complete_packages,
588+
which=project._which,
589+
project=project,
590+
lockfile={},
591+
pipfile_category=pipfile_category,
592+
pre=pre,
593+
allow_global=system,
594+
pypi_mirror=pypi_mirror,
595+
)
596+
category_resolutions[category] = full_lock_resolution
597+
598+
# Clean up unused dependencies
599+
_clean_unused_dependencies(
600+
project, lockfile, category, full_lock_resolution, original_lockfile
601+
)
602+
543603
# Reset package args for next category if needed
544604
if not has_package_args:
545605
package_args = []
+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
2+
import pytest
3+
4+
5+
@pytest.mark.upgrade
6+
@pytest.mark.cleanup
7+
def test_upgrade_removes_unused_dependencies(pipenv_instance_pypi):
8+
"""Test that `pipenv upgrade` removes dependencies that are no longer needed."""
9+
with pipenv_instance_pypi() as p:
10+
# Create a Pipfile with Django 3.2.10 (which depends on pytz)
11+
with open(p.pipfile_path, "w") as f:
12+
f.write("""
13+
[[source]]
14+
url = "https://pypi.org/simple"
15+
verify_ssl = true
16+
name = "pypi"
17+
18+
[packages]
19+
django = "==3.2.10"
20+
21+
[dev-packages]
22+
""")
23+
24+
# Install dependencies
25+
c = p.pipenv("install")
26+
assert c.returncode == 0
27+
28+
# Verify pytz is in the lockfile
29+
assert "pytz" in p.lockfile["default"]
30+
31+
# Upgrade Django to 4.2.7 (which doesn't depend on pytz)
32+
c = p.pipenv("upgrade django==4.2.7")
33+
assert c.returncode == 0
34+
35+
# Verify pytz is no longer in the lockfile
36+
assert "pytz" not in p.lockfile["default"]

0 commit comments

Comments
 (0)