Skip to content

Commit 033d537

Browse files
authored
GH-73991: Make pathlib.Path.delete() private. (#123315)
Per feedback from Paul Moore on GH-123158, it's better to defer making `Path.delete()` public than ship it with under-designed error handling capabilities. We leave a remnant `_delete()` method, which is used by `move()`. Any functionality not needed by `move()` is deleted.
1 parent a1ddaae commit 033d537

File tree

7 files changed

+48
-287
lines changed

7 files changed

+48
-287
lines changed

Doc/library/pathlib.rst

Lines changed: 2 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1657,7 +1657,7 @@ Copying, moving and deleting
16571657
.. method:: Path.unlink(missing_ok=False)
16581658

16591659
Remove this file or symbolic link. If the path points to a directory,
1660-
use :func:`Path.rmdir` or :func:`Path.delete` instead.
1660+
use :func:`Path.rmdir` instead.
16611661

16621662
If *missing_ok* is false (the default), :exc:`FileNotFoundError` is
16631663
raised if the path does not exist.
@@ -1671,42 +1671,7 @@ Copying, moving and deleting
16711671

16721672
.. method:: Path.rmdir()
16731673

1674-
Remove this directory. The directory must be empty; use
1675-
:meth:`Path.delete` to remove a non-empty directory.
1676-
1677-
1678-
.. method:: Path.delete(ignore_errors=False, on_error=None)
1679-
1680-
Delete this file or directory. If this path refers to a non-empty
1681-
directory, its files and sub-directories are deleted recursively.
1682-
1683-
If *ignore_errors* is true, errors resulting from failed deletions will be
1684-
ignored. If *ignore_errors* is false or omitted, and a callable is given as
1685-
the optional *on_error* argument, it will be called with one argument of
1686-
type :exc:`OSError` each time an exception is raised. The callable can
1687-
handle the error to continue the deletion process or re-raise it to stop.
1688-
Note that the filename is available as the :attr:`~OSError.filename`
1689-
attribute of the exception object. If neither *ignore_errors* nor
1690-
*on_error* are supplied, exceptions are propagated to the caller.
1691-
1692-
.. note::
1693-
1694-
When deleting non-empty directories on platforms that lack the necessary
1695-
file descriptor-based functions, the :meth:`~Path.delete` implementation
1696-
is susceptible to a symlink attack: given proper timing and
1697-
circumstances, attackers can manipulate symlinks on the filesystem to
1698-
delete files they would not be able to access otherwise. Applications
1699-
can use the :data:`~Path.delete.avoids_symlink_attacks` method attribute
1700-
to determine whether the implementation is immune to this attack.
1701-
1702-
.. attribute:: delete.avoids_symlink_attacks
1703-
1704-
Indicates whether the current platform and implementation provides a
1705-
symlink attack resistant version of :meth:`~Path.delete`. Currently
1706-
this is only true for platforms supporting fd-based directory access
1707-
functions.
1708-
1709-
.. versionadded:: 3.14
1674+
Remove this directory. The directory must be empty.
17101675

17111676

17121677
Permissions and ownership

Doc/whatsnew/3.14.rst

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -185,14 +185,13 @@ os
185185
pathlib
186186
-------
187187

188-
* Add methods to :class:`pathlib.Path` to recursively copy, move, or remove
189-
files and directories:
188+
* Add methods to :class:`pathlib.Path` to recursively copy or move files and
189+
directories:
190190

191191
* :meth:`~pathlib.Path.copy` copies a file or directory tree to a destination.
192192
* :meth:`~pathlib.Path.copy_into` copies *into* a destination directory.
193193
* :meth:`~pathlib.Path.move` moves a file or directory tree to a destination.
194194
* :meth:`~pathlib.Path.move_into` moves *into* a destination directory.
195-
* :meth:`~pathlib.Path.delete` removes a file or directory tree.
196195

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

Lib/pathlib/_abc.py

Lines changed: 20 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -962,7 +962,7 @@ def move(self, target):
962962
if err.errno != EXDEV:
963963
raise
964964
target = self.copy(target, follow_symlinks=False, preserve_metadata=True)
965-
self.delete()
965+
self._delete()
966966
return target
967967

968968
def move_into(self, target_dir):
@@ -1004,47 +1004,29 @@ def rmdir(self):
10041004
"""
10051005
raise UnsupportedOperation(self._unsupported_msg('rmdir()'))
10061006

1007-
def delete(self, ignore_errors=False, on_error=None):
1007+
def _delete(self):
10081008
"""
10091009
Delete this file or directory (including all sub-directories).
1010-
1011-
If *ignore_errors* is true, exceptions raised from scanning the
1012-
filesystem and removing files and directories are ignored. Otherwise,
1013-
if *on_error* is set, it will be called to handle the error. If
1014-
neither *ignore_errors* nor *on_error* are set, exceptions are
1015-
propagated to the caller.
10161010
"""
1017-
if ignore_errors:
1018-
def on_error(err):
1019-
pass
1020-
elif on_error is None:
1021-
def on_error(err):
1022-
raise err
1023-
if self.is_dir(follow_symlinks=False):
1024-
results = self.walk(
1025-
on_error=on_error,
1026-
top_down=False, # So we rmdir() empty directories.
1027-
follow_symlinks=False)
1028-
for dirpath, dirnames, filenames in results:
1029-
for name in filenames:
1030-
try:
1031-
dirpath.joinpath(name).unlink()
1032-
except OSError as err:
1033-
on_error(err)
1034-
for name in dirnames:
1035-
try:
1036-
dirpath.joinpath(name).rmdir()
1037-
except OSError as err:
1038-
on_error(err)
1039-
delete_self = self.rmdir
1011+
if self.is_symlink() or self.is_junction():
1012+
self.unlink()
1013+
elif self.is_dir():
1014+
self._rmtree()
10401015
else:
1041-
delete_self = self.unlink
1042-
try:
1043-
delete_self()
1044-
except OSError as err:
1045-
err.filename = str(self)
1046-
on_error(err)
1047-
delete.avoids_symlink_attacks = False
1016+
self.unlink()
1017+
1018+
def _rmtree(self):
1019+
def on_error(err):
1020+
raise err
1021+
results = self.walk(
1022+
on_error=on_error,
1023+
top_down=False, # So we rmdir() empty directories.
1024+
follow_symlinks=False)
1025+
for dirpath, _, filenames in results:
1026+
for filename in filenames:
1027+
filepath = dirpath / filename
1028+
filepath.unlink()
1029+
dirpath.rmdir()
10481030

10491031
def owner(self, *, follow_symlinks=True):
10501032
"""

Lib/pathlib/_local.py

Lines changed: 1 addition & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -824,34 +824,7 @@ def rmdir(self):
824824
"""
825825
os.rmdir(self)
826826

827-
def delete(self, ignore_errors=False, on_error=None):
828-
"""
829-
Delete this file or directory (including all sub-directories).
830-
831-
If *ignore_errors* is true, exceptions raised from scanning the
832-
filesystem and removing files and directories are ignored. Otherwise,
833-
if *on_error* is set, it will be called to handle the error. If
834-
neither *ignore_errors* nor *on_error* are set, exceptions are
835-
propagated to the caller.
836-
"""
837-
if self.is_dir(follow_symlinks=False):
838-
onexc = None
839-
if on_error:
840-
def onexc(func, filename, err):
841-
err.filename = filename
842-
on_error(err)
843-
shutil.rmtree(str(self), ignore_errors, onexc=onexc)
844-
else:
845-
try:
846-
self.unlink()
847-
except OSError as err:
848-
if not ignore_errors:
849-
if on_error:
850-
on_error(err)
851-
else:
852-
raise
853-
854-
delete.avoids_symlink_attacks = shutil.rmtree.avoids_symlink_attacks
827+
_rmtree = shutil.rmtree
855828

856829
def rename(self, target):
857830
"""

0 commit comments

Comments
 (0)