diff --git a/newsfragments/+removedirs-missing.bugfix.rst b/newsfragments/+removedirs-missing.bugfix.rst new file mode 100644 index 0000000..27859b2 --- /dev/null +++ b/newsfragments/+removedirs-missing.bugfix.rst @@ -0,0 +1 @@ +``Path.removedirs_p`` now tolerates missing directories, including repeated cleanup of an already removed tree. diff --git a/path/__init__.py b/path/__init__.py index 731e88e..4a9681b 100644 --- a/path/__init__.py +++ b/path/__init__.py @@ -1404,7 +1404,8 @@ def removedirs(self) -> Self: def removedirs_p(self) -> Self: """Like :meth:`removedirs`, but does not raise an exception if the directory is not empty or does not exist.""" - suppressed = FileExistsError, DirectoryNotEmpty + # 2026-10-11: Repeated cleanup must also tolerate missing directories. + suppressed = FileNotFoundError, FileExistsError, DirectoryNotEmpty with contextlib.suppress(*suppressed), DirectoryNotEmpty.translate(): self.removedirs() return self diff --git a/tests/test_path.py b/tests/test_path.py index 55d733c..3f5ceb0 100644 --- a/tests/test_path.py +++ b/tests/test_path.py @@ -304,6 +304,30 @@ def test_removedirs_p(self, tmpdir): # TODO: shouldn't sub get removed? # assert not (dir / 'sub').is_dir() + @pytest.mark.parametrize('nested', [False, True]) + def test_removedirs_p_missing(self, tmpdir, nested): + # 2026-10-11: Cleanup is harmless when the directory or its parent is absent. + directory = Path(tmpdir) / 'missing' + if nested: + directory /= 'child' + assert directory.removedirs_p() is directory + assert not directory.exists() + + def test_removedirs_p_repeated(self, tmpdir): + # 2026-10-11: Removing an empty tree twice must remain idempotent. + (Path(tmpdir) / 'keep').touch() + directory = (Path(tmpdir) / 'parent' / 'child').makedirs() + assert directory.removedirs_p() is directory + assert directory.removedirs_p() is directory + assert not directory.parent.exists() + + def test_removedirs_p_file(self, tmpdir): + # 2026-10-11: Existing files are still an error rather than absent directories. + file = (Path(tmpdir) / 'file').touch() + with pytest.raises(OSError): + file.removedirs_p() + assert file.is_file() + @pytest.mark.skipif("not hasattr(Path, 'group')") def test_group(self, tmpdir): file = Path(tmpdir).joinpath('file').touch()