Skip to content

Commit a3dd339

Browse files
committed
pythonGH-73991: Make pathlib.Path.delete() private.
Per feedback from Paul Moore on pythonGH-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 625d070 commit a3dd339

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
@@ -1636,7 +1636,7 @@ Copying, moving and deleting
16361636
.. method:: Path.unlink(missing_ok=False)
16371637

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

16411641
If *missing_ok* is false (the default), :exc:`FileNotFoundError` is
16421642
raised if the path does not exist.
@@ -1650,42 +1650,7 @@ Copying, moving and deleting
16501650

16511651
.. method:: Path.rmdir()
16521652

1653-
Remove this directory. The directory must be empty; use
1654-
:meth:`Path.delete` to remove a non-empty directory.
1655-
1656-
1657-
.. method:: Path.delete(ignore_errors=False, on_error=None)
1658-
1659-
Delete this file or directory. If this path refers to a non-empty
1660-
directory, its files and sub-directories are deleted recursively.
1661-
1662-
If *ignore_errors* is true, errors resulting from failed deletions will be
1663-
ignored. If *ignore_errors* is false or omitted, and a callable is given as
1664-
the optional *on_error* argument, it will be called with one argument of
1665-
type :exc:`OSError` each time an exception is raised. The callable can
1666-
handle the error to continue the deletion process or re-raise it to stop.
1667-
Note that the filename is available as the :attr:`~OSError.filename`
1668-
attribute of the exception object. If neither *ignore_errors* nor
1669-
*on_error* are supplied, exceptions are propagated to the caller.
1670-
1671-
.. note::
1672-
1673-
When deleting non-empty directories on platforms that lack the necessary
1674-
file descriptor-based functions, the :meth:`~Path.delete` implementation
1675-
is susceptible to a symlink attack: given proper timing and
1676-
circumstances, attackers can manipulate symlinks on the filesystem to
1677-
delete files they would not be able to access otherwise. Applications
1678-
can use the :data:`~Path.delete.avoids_symlink_attacks` method attribute
1679-
to determine whether the implementation is immune to this attack.
1680-
1681-
.. attribute:: delete.avoids_symlink_attacks
1682-
1683-
Indicates whether the current platform and implementation provides a
1684-
symlink attack resistant version of :meth:`~Path.delete`. Currently
1685-
this is only true for platforms supporting fd-based directory access
1686-
functions.
1687-
1688-
.. versionadded:: 3.14
1653+
Remove this directory. The directory must be empty.
16891654

16901655

16911656
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 given
192192
destination.
193193
* :meth:`~pathlib.Path.move` moves a file or directory tree to a given
194194
destination.
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
@@ -944,7 +944,7 @@ def move(self, target):
944944
if err.errno != EXDEV:
945945
raise
946946
target = self.copy(target, follow_symlinks=False, preserve_metadata=True)
947-
self.delete()
947+
self._delete()
948948
return target
949949

950950
def chmod(self, mode, *, follow_symlinks=True):
@@ -973,47 +973,29 @@ def rmdir(self):
973973
"""
974974
raise UnsupportedOperation(self._unsupported_msg('rmdir()'))
975975

976-
def delete(self, ignore_errors=False, on_error=None):
976+
def _delete(self):
977977
"""
978978
Delete this file or directory (including all sub-directories).
979-
980-
If *ignore_errors* is true, exceptions raised from scanning the
981-
filesystem and removing files and directories are ignored. Otherwise,
982-
if *on_error* is set, it will be called to handle the error. If
983-
neither *ignore_errors* nor *on_error* are set, exceptions are
984-
propagated to the caller.
985979
"""
986-
if ignore_errors:
987-
def on_error(err):
988-
pass
989-
elif on_error is None:
990-
def on_error(err):
991-
raise err
992-
if self.is_dir(follow_symlinks=False):
993-
results = self.walk(
994-
on_error=on_error,
995-
top_down=False, # So we rmdir() empty directories.
996-
follow_symlinks=False)
997-
for dirpath, dirnames, filenames in results:
998-
for name in filenames:
999-
try:
1000-
dirpath.joinpath(name).unlink()
1001-
except OSError as err:
1002-
on_error(err)
1003-
for name in dirnames:
1004-
try:
1005-
dirpath.joinpath(name).rmdir()
1006-
except OSError as err:
1007-
on_error(err)
1008-
delete_self = self.rmdir
980+
if self.is_symlink() or self.is_junction():
981+
self.unlink()
982+
elif self.is_dir():
983+
self._rmtree()
1009984
else:
1010-
delete_self = self.unlink
1011-
try:
1012-
delete_self()
1013-
except OSError as err:
1014-
err.filename = str(self)
1015-
on_error(err)
1016-
delete.avoids_symlink_attacks = False
985+
self.unlink()
986+
987+
def _rmtree(self):
988+
def on_error(err):
989+
raise err
990+
results = self.walk(
991+
on_error=on_error,
992+
top_down=False, # So we rmdir() empty directories.
993+
follow_symlinks=False)
994+
for dirpath, _, filenames in results:
995+
for filename in filenames:
996+
filepath = dirpath / filename
997+
filepath.unlink()
998+
dirpath.rmdir()
1017999

10181000
def owner(self, *, follow_symlinks=True):
10191001
"""

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)