Skip to content

Commit 6bae075

Browse files
committed
[commands/cache] make pip cache purge remove everything from http + wheels caches; make pip cache remove prune empty directories.
1 parent b55ec00 commit 6bae075

File tree

2 files changed

+66
-0
lines changed

2 files changed

+66
-0
lines changed

src/pip/_internal/commands/cache.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,24 @@ def remove_cache_items(self, options: Values, args: List[Any]) -> None:
180180
for filename in files:
181181
os.unlink(filename)
182182
logger.verbose("Removed %s", filename)
183+
184+
http_dirs = filesystem.subdirs_with_no_files(self._cache_dir(options, "http"))
185+
wheel_dirs = filesystem.subdirs_with_no_files(
186+
self._cache_dir(options, "wheels")
187+
)
188+
dirs = list(http_dirs) + list(wheel_dirs)
189+
for dirname in dirs:
190+
os.rmdir(dirname)
191+
logger.verbose("Removed %s", dirname)
192+
193+
# selfcheck.json is no longer used by pip.
194+
selfcheck_json = self._cache_dir(options, "selfcheck.json")
195+
if os.path.isfile(selfcheck_json):
196+
os.remove(selfcheck_json)
197+
logger.verbose("Removed legacy selfcheck.json file")
198+
183199
logger.info("Files removed: %s", len(files))
200+
logger.info("Empty directories removed: %s", len(dirs))
184201

185202
def purge_cache(self, options: Values, args: List[Any]) -> None:
186203
if args:

src/pip/_internal/utils/filesystem.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@
66
import stat
77
import sys
88
from contextlib import contextmanager
9+
from pathlib import Path
910
from tempfile import NamedTemporaryFile
1011
from typing import Any, BinaryIO, Iterator, List, Union, cast
1112

1213
from pip._vendor.tenacity import retry, stop_after_delay, wait_fixed
1314

15+
from pip._internal.exceptions import PipError
1416
from pip._internal.utils.compat import get_path_uid
1517
from pip._internal.utils.misc import format_size
1618

@@ -180,3 +182,50 @@ def directory_size(path: str) -> Union[int, float]:
180182

181183
def format_directory_size(path: str) -> str:
182184
return format_size(directory_size(path))
185+
186+
187+
def _leaf_subdirs(path):
188+
"""Traverses the file tree, finding every empty directory."""
189+
190+
path_obj = Path(path)
191+
192+
if not path_obj.exists():
193+
return
194+
195+
for item in path_obj.iterdir():
196+
if not item.is_dir():
197+
continue
198+
199+
subitems = item.iterdir()
200+
201+
# ASSUMPTION: Nothing in subitems will be None or False.
202+
if not any(subitems):
203+
yield item
204+
205+
if not any(subitem.is_file() for subitem in subitems):
206+
yield from _leaf_subdirs(item)
207+
208+
209+
def _leaf_parents_without_files(path, leaf):
210+
"""Yields +leaf+ and each parent directory below +path+, until one of
211+
them includes a file (as opposed to directories or nothing)."""
212+
213+
if not str(leaf).startswith(str(path)):
214+
# If +leaf+ is not a subdirectory of +path+, bail early to avoid
215+
# an endless loop.
216+
raise PipError("leaf is not a subdirectory of path")
217+
218+
path = Path(path)
219+
leaf = Path(leaf)
220+
while leaf != path:
221+
if all(item.is_dir() for item in leaf.iterdir()):
222+
yield str(leaf)
223+
else:
224+
break
225+
leaf = leaf.parent
226+
227+
228+
def subdirs_with_no_files(path):
229+
"""Yields every subdirectory of +path+ that has no files under it."""
230+
for leaf in _leaf_subdirs(path):
231+
yield from _leaf_parents_without_files(path, leaf)

0 commit comments

Comments
 (0)