Skip to content

Commit

Permalink
Walk through directory tree for debris cleanup too
Browse files Browse the repository at this point in the history
Bytecode objects are looked for and cleaned up in the entire subdirectory
tree. It's natural to assume that a user expects the same behavior for
when debris is being cleaned up.
  • Loading branch information
bittner committed Jan 11, 2025
1 parent a881610 commit fcda733
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 5 deletions.
12 changes: 9 additions & 3 deletions pyclean/modern.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"""

import logging
import os
from pathlib import Path

BYTECODE_FILES = ['.pyc', '.pyo']
Expand Down Expand Up @@ -61,7 +62,7 @@
}


class CleanupRunner: # pylint: disable=too-few-public-methods
class CleanupRunner:
"""Module-level configuration and value store."""

def __init__(self):
Expand Down Expand Up @@ -182,7 +183,7 @@ def remove_debris_for(topic, directory):
log.debug('Scanning for debris of %s ...', topic.title())

for path_glob in DEBRIS_TOPICS[topic]:
delete_filesystem_objects(directory, path_glob)
delete_filesystem_objects(directory, path_glob, recursive=True)


def remove_freeform_targets(glob_patterns, yes, directory):
Expand All @@ -206,7 +207,7 @@ def remove_freeform_targets(glob_patterns, yes, directory):
delete_filesystem_objects(directory, path_glob, prompt=not yes)


def delete_filesystem_objects(directory, path_glob, prompt=False):
def delete_filesystem_objects(directory, path_glob, prompt=False, recursive=False):
"""
Identifies all pathnames matching a specific glob pattern, and attempts
to delete them in the proper order, optionally asking for confirmation.
Expand All @@ -233,6 +234,11 @@ def delete_filesystem_objects(directory, path_glob, prompt=False):
continue
Runner.rmdir(dir_object)

if recursive:
subdirs = (name.path for name in os.scandir(directory) if name.is_dir())
for subdir in subdirs:
delete_filesystem_objects(Path(subdir), path_glob, prompt, recursive)


def confirm(message):
"""An interactive confirmation prompt."""
Expand Down
33 changes: 31 additions & 2 deletions tests/test_modern.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import logging
from argparse import Namespace
from pathlib import Path
from tempfile import TemporaryDirectory
from unittest.mock import Mock, call, patch

import pytest
Expand Down Expand Up @@ -324,13 +325,41 @@ def test_debris_loop(mock_delete_fs_obj, debris_topic):
"""
fileobject_globs = pyclean.modern.DEBRIS_TOPICS[debris_topic]
directory = Path()
expected_calls = [call(directory, pattern) for pattern in fileobject_globs]
expected_calls = [
call(directory, pattern, recursive=True) for pattern in fileobject_globs
]

remove_debris_for(debris_topic, directory)

assert mock_delete_fs_obj.call_args_list == expected_calls


def test_debris_recursive():
"""
Does ``delete_filesystem_objects`` walk through the folder hierarchy
when deleting debris?
"""
topic = 'cache'
topic_globs = pyclean.modern.DEBRIS_TOPICS[topic]
topic_folder = topic_globs[1]

with TemporaryDirectory() as tmpdirname:
directory = Path(tmpdirname)
dir_topic = directory / topic_folder
dir_topic.mkdir()
dir_nested_topic = directory / 'nested' / topic_folder
dir_nested_topic.mkdir(parents=True)

assert dir_nested_topic.exists()
assert dir_topic.exists()

remove_debris_for(topic, directory)

assert not dir_topic.exists()
assert not dir_nested_topic.exists()
assert dir_nested_topic.parent.exists()


@patch('pyclean.modern.delete_filesystem_objects')
def test_erase_loop(mock_delete_fs_obj):
"""
Expand All @@ -341,7 +370,7 @@ def test_erase_loop(mock_delete_fs_obj):

remove_freeform_targets(patterns, yes=False, directory=directory)

assert mock_delete_fs_obj.call_args_list == [
assert mock_delete_fs_obj.mock_calls == [
call(directory, 'foo.txt', prompt=True),
]

Expand Down

0 comments on commit fcda733

Please sign in to comment.