From faa110a95be29c67c1bb3cd5ff9fd1ffb3a8f345 Mon Sep 17 00:00:00 2001 From: fhgffy <102001626+fhgffy@users.noreply.github.com> Date: Sun, 11 Oct 2026 04:32:30 +0800 Subject: [PATCH] Tolerate missing directories in removedirs_p --- newsfragments/+removedirs-missing.bugfix.rst | 1 + path/__init__.py | 3 ++- tests/test_path.py | 24 ++++++++++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 newsfragments/+removedirs-missing.bugfix.rst 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 33dd979..243d458 100644 --- a/path/__init__.py +++ b/path/__init__.py @@ -1407,7 +1407,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.""" - with contextlib.suppress(FileExistsError, DirectoryNotEmpty): + # 2026-10-11: Repeated cleanup must also tolerate missing directories. + with contextlib.suppress(FileNotFoundError, FileExistsError, DirectoryNotEmpty): with DirectoryNotEmpty.translate(): self.removedirs() return self diff --git a/tests/test_path.py b/tests/test_path.py index b424bb6..e802a8e 100644 --- a/tests/test_path.py +++ b/tests/test_path.py @@ -305,6 +305,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()