GH-73991: Rework `pathlib.Path.rmtree()` into `delete()` by barneygale · Pull Request #122368 · python/cpython · GitHub
Skip to content
49 changes: 28 additions & 21 deletions Doc/library/pathlib.rst
3 changes: 1 addition & 2 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,7 @@ pathlib
:func:`shutil.copyfile`.
* :meth:`~pathlib.Path.copytree` copies one directory tree to another, like
:func:`shutil.copytree`.
* :meth:`~pathlib.Path.rmtree` recursively removes a directory tree, like
:func:`shutil.rmtree`.
* :meth:`~pathlib.Path.delete` removes a file or directory tree.

(Contributed by Barney Gale in :gh:`73991`.)

Expand Down
29 changes: 15 additions & 14 deletions Lib/pathlib/_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -919,30 +919,26 @@ def rmdir(self):
"""
raise UnsupportedOperation(self._unsupported_msg('rmdir()'))

def rmtree(self, ignore_errors=False, on_error=None):
def delete(self, ignore_errors=False, on_error=None):
"""
Recursively delete this directory tree.
Delete this file or directory (including all sub-directories).

If *ignore_errors* is true, exceptions raised from scanning the tree
and removing files and directories are ignored. Otherwise, if
*on_error* is set, it will be called to handle the error. If neither
*ignore_errors* nor *on_error* are set, exceptions are propagated to
the caller.
If *ignore_errors* is true, exceptions raised from scanning the
filesystem and removing files and directories are ignored. Otherwise,
if *on_error* is set, it will be called to handle the error. If
neither *ignore_errors* nor *on_error* are set, exceptions are
propagated to the caller.
"""
if ignore_errors:
def on_error(err):
pass
elif on_error is None:
def on_error(err):
raise err
try:
if self.is_symlink():
raise OSError("Cannot call rmtree on a symbolic link")
elif self.is_junction():
raise OSError("Cannot call rmtree on a junction")
if self.is_dir(follow_symlinks=False):
results = self.walk(
on_error=on_error,
top_down=False, # Bottom-up so we rmdir() empty directories.
top_down=False, # So we rmdir() empty directories.
follow_symlinks=False)
for dirpath, dirnames, filenames in results:
for name in filenames:
Expand All @@ -955,10 +951,15 @@ def on_error(err):
dirpath.joinpath(name).rmdir()
except OSError as err:
on_error(err)
self.rmdir()
delete_self = self.rmdir
else:
delete_self = self.unlink
try:
Comment thread
barneygale marked this conversation as resolved.
delete_self()
except OSError as err:
err.filename = str(self)
on_error(err)
delete.avoids_symlink_attacks = False

def owner(self, *, follow_symlinks=True):
"""
Expand Down
39 changes: 25 additions & 14 deletions Lib/pathlib/_local.py
Loading