Skip to content
56 changes: 28 additions & 28 deletions Doc/library/pathlib.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1633,6 +1633,34 @@ Copying, renaming and deleting
Added return value, return the new :class:`!Path` instance.


.. method:: Path.delete(ignore_errors=False, on_error=None)
Comment thread
barneygale marked this conversation as resolved.
Outdated

Recursively remove this file or directory tree.
Comment thread
barneygale marked this conversation as resolved.
Outdated

If *ignore_errors* is true, errors resulting from failed removals will be
ignored. If *ignore_errors* is false or omitted, and a function is given to
*on_error*, it will be called each time an exception is raised. If neither
*ignore_errors* nor *on_error* are supplied, exceptions are propagated to
the caller.

If the optional argument *on_error* is specified, it should be a callable;
it will be called with one argument of type :exc:`OSError`. The
callable can handle the error to continue the deletion process or re-raise
it to stop. Note that the filename is available as the
:attr:`~OSError.filename` attribute of the exception object.
Comment thread
barneygale marked this conversation as resolved.
Outdated

.. note::

On platforms that support the necessary fd-based functions, a symlink
attack-resistant version of :meth:`~Path.delete` is used to delete
directory trees. On other platforms, the :func:`~Path.delete`
implementation is susceptible to a symlink attack: given proper timing
and circumstances, attackers can manipulate symlinks on the filesystem
to delete files they would not be able to access otherwise.
Comment thread
barneygale marked this conversation as resolved.
Outdated

.. versionadded:: 3.14


.. method:: Path.unlink(missing_ok=False)

Remove this file or symbolic link. If the path points to a directory,
Comment thread
barneygale marked this conversation as resolved.
Outdated
Expand All @@ -1653,34 +1681,6 @@ Copying, renaming and deleting
Remove this directory. The directory must be empty.
Comment thread
barneygale marked this conversation as resolved.
Outdated


.. method:: Path.rmtree(ignore_errors=False, on_error=None)

Recursively delete this entire directory tree. The path must not refer to a symlink.

If *ignore_errors* is true, errors resulting from failed removals will be
ignored. If *ignore_errors* is false or omitted, and a function is given to
*on_error*, it will be called each time an exception is raised. If neither
*ignore_errors* nor *on_error* are supplied, exceptions are propagated to
the caller.

.. note::

On platforms that support the necessary fd-based functions, a symlink
attack-resistant version of :meth:`~Path.rmtree` is used by default. On
other platforms, the :func:`~Path.rmtree` implementation is susceptible
to a symlink attack: given proper timing and circumstances, attackers
can manipulate symlinks on the filesystem to delete files they would not
be able to access otherwise.

If the optional argument *on_error* is specified, it should be a callable;
it will be called with one argument of type :exc:`OSError`. The
callable can handle the error to continue the deletion process or re-raise
it to stop. Note that the filename is available as the :attr:`~OSError.filename`
attribute of the exception object.

.. versionadded:: 3.14


Permissions and ownership
^^^^^^^^^^^^^^^^^^^^^^^^^

Expand Down
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
53 changes: 26 additions & 27 deletions Lib/pathlib/_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -919,15 +919,15 @@ 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.
Recursively remove this file or directory tree.

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):
Expand All @@ -936,26 +936,25 @@ def on_error(err):
def on_error(err):
raise err
try:
Comment thread
barneygale marked this conversation as resolved.
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")
results = self.walk(
on_error=on_error,
top_down=False, # Bottom-up so we rmdir() empty directories.
follow_symlinks=False)
for dirpath, dirnames, filenames in results:
for name in filenames:
try:
dirpath.joinpath(name).unlink()
except OSError as err:
on_error(err)
for name in dirnames:
try:
dirpath.joinpath(name).rmdir()
except OSError as err:
on_error(err)
self.rmdir()
if self.is_dir(follow_symlinks=False):
results = self.walk(
on_error=on_error,
top_down=False, # So we rmdir() empty directories.
follow_symlinks=False)
for dirpath, dirnames, filenames in results:
for name in filenames:
try:
dirpath.joinpath(name).unlink()
except OSError as err:
on_error(err)
for name in dirnames:
try:
dirpath.joinpath(name).rmdir()
except OSError as err:
on_error(err)
self.rmdir()
else:
self.unlink()
except OSError as err:
err.filename = str(self)
on_error(err)
Expand Down
38 changes: 24 additions & 14 deletions Lib/pathlib/_local.py
Original file line number Diff line number Diff line change
Expand Up @@ -830,24 +830,34 @@ def rmdir(self):
"""
os.rmdir(self)

def rmtree(self, ignore_errors=False, on_error=None):
def delete(self, ignore_errors=False, on_error=None):
"""
Recursively delete this directory tree.
Recursively remove this file or directory tree.

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 on_error:
def onexc(func, filename, err):
err.filename = filename
on_error(err)
else:
if self.is_dir(follow_symlinks=False):
onexc = None
import shutil
shutil.rmtree(str(self), ignore_errors, onexc=onexc)
if on_error:
def onexc(func, filename, err):
err.filename = filename
on_error(err)
import shutil
shutil.rmtree(str(self), ignore_errors, onexc=onexc)
else:
try:
self.unlink()
except OSError as err:
if not ignore_errors:
if on_error:
on_error(err)
else:
raise


def rename(self, target):
"""
Expand Down
Loading