bpo-39682: make `pathlib.Path` immutable by removing (undocumented) support for "closing" a path by using it as a context manager (GH-18846)
Support for using a path as a context manager remains, and is now a no-op.
This commit is contained in:
parent
975ac326ff
commit
00002e6d8b
|
@ -1041,7 +1041,6 @@ class Path(PurePath):
|
||||||
"""
|
"""
|
||||||
__slots__ = (
|
__slots__ = (
|
||||||
'_accessor',
|
'_accessor',
|
||||||
'_closed',
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def __new__(cls, *args, **kwargs):
|
def __new__(cls, *args, **kwargs):
|
||||||
|
@ -1058,7 +1057,6 @@ class Path(PurePath):
|
||||||
# Private non-constructor arguments
|
# Private non-constructor arguments
|
||||||
template=None,
|
template=None,
|
||||||
):
|
):
|
||||||
self._closed = False
|
|
||||||
if template is not None:
|
if template is not None:
|
||||||
self._accessor = template._accessor
|
self._accessor = template._accessor
|
||||||
else:
|
else:
|
||||||
|
@ -1071,15 +1069,18 @@ class Path(PurePath):
|
||||||
return self._from_parsed_parts(self._drv, self._root, parts)
|
return self._from_parsed_parts(self._drv, self._root, parts)
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
if self._closed:
|
|
||||||
self._raise_closed()
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __exit__(self, t, v, tb):
|
def __exit__(self, t, v, tb):
|
||||||
self._closed = True
|
# https://bugs.python.org/issue39682
|
||||||
|
# In previous versions of pathlib, this method marked this path as
|
||||||
def _raise_closed(self):
|
# closed; subsequent attempts to perform I/O would raise an IOError.
|
||||||
raise ValueError("I/O operation on closed path")
|
# This functionality was never documented, and had the effect of
|
||||||
|
# making Path objects mutable, contrary to PEP 428. In Python 3.9 the
|
||||||
|
# _closed attribute was removed, and this method made a no-op.
|
||||||
|
# This method and __enter__()/__exit__() should be deprecated and
|
||||||
|
# removed in the future.
|
||||||
|
pass
|
||||||
|
|
||||||
def _opener(self, name, flags, mode=0o666):
|
def _opener(self, name, flags, mode=0o666):
|
||||||
# A stub for the opener argument to built-in open()
|
# A stub for the opener argument to built-in open()
|
||||||
|
@ -1090,8 +1091,6 @@ class Path(PurePath):
|
||||||
Open the file pointed by this path and return a file descriptor,
|
Open the file pointed by this path and return a file descriptor,
|
||||||
as os.open() does.
|
as os.open() does.
|
||||||
"""
|
"""
|
||||||
if self._closed:
|
|
||||||
self._raise_closed()
|
|
||||||
return self._accessor.open(self, flags, mode)
|
return self._accessor.open(self, flags, mode)
|
||||||
|
|
||||||
# Public API
|
# Public API
|
||||||
|
@ -1125,15 +1124,11 @@ class Path(PurePath):
|
||||||
"""Iterate over the files in this directory. Does not yield any
|
"""Iterate over the files in this directory. Does not yield any
|
||||||
result for the special paths '.' and '..'.
|
result for the special paths '.' and '..'.
|
||||||
"""
|
"""
|
||||||
if self._closed:
|
|
||||||
self._raise_closed()
|
|
||||||
for name in self._accessor.listdir(self):
|
for name in self._accessor.listdir(self):
|
||||||
if name in {'.', '..'}:
|
if name in {'.', '..'}:
|
||||||
# Yielding a path object for these makes little sense
|
# Yielding a path object for these makes little sense
|
||||||
continue
|
continue
|
||||||
yield self._make_child_relpath(name)
|
yield self._make_child_relpath(name)
|
||||||
if self._closed:
|
|
||||||
self._raise_closed()
|
|
||||||
|
|
||||||
def glob(self, pattern):
|
def glob(self, pattern):
|
||||||
"""Iterate over this subtree and yield all existing files (of any
|
"""Iterate over this subtree and yield all existing files (of any
|
||||||
|
@ -1170,8 +1165,6 @@ class Path(PurePath):
|
||||||
Use resolve() to get the canonical path to a file.
|
Use resolve() to get the canonical path to a file.
|
||||||
"""
|
"""
|
||||||
# XXX untested yet!
|
# XXX untested yet!
|
||||||
if self._closed:
|
|
||||||
self._raise_closed()
|
|
||||||
if self.is_absolute():
|
if self.is_absolute():
|
||||||
return self
|
return self
|
||||||
# FIXME this must defer to the specific flavour (and, under Windows,
|
# FIXME this must defer to the specific flavour (and, under Windows,
|
||||||
|
@ -1186,8 +1179,6 @@ class Path(PurePath):
|
||||||
normalizing it (for example turning slashes into backslashes under
|
normalizing it (for example turning slashes into backslashes under
|
||||||
Windows).
|
Windows).
|
||||||
"""
|
"""
|
||||||
if self._closed:
|
|
||||||
self._raise_closed()
|
|
||||||
s = self._flavour.resolve(self, strict=strict)
|
s = self._flavour.resolve(self, strict=strict)
|
||||||
if s is None:
|
if s is None:
|
||||||
# No symlink resolution => for consistency, raise an error if
|
# No symlink resolution => for consistency, raise an error if
|
||||||
|
@ -1227,8 +1218,6 @@ class Path(PurePath):
|
||||||
Open the file pointed by this path and return a file object, as
|
Open the file pointed by this path and return a file object, as
|
||||||
the built-in open() function does.
|
the built-in open() function does.
|
||||||
"""
|
"""
|
||||||
if self._closed:
|
|
||||||
self._raise_closed()
|
|
||||||
return io.open(self, mode, buffering, encoding, errors, newline,
|
return io.open(self, mode, buffering, encoding, errors, newline,
|
||||||
opener=self._opener)
|
opener=self._opener)
|
||||||
|
|
||||||
|
@ -1278,8 +1267,6 @@ class Path(PurePath):
|
||||||
"""
|
"""
|
||||||
Create this file with the given access mode, if it doesn't exist.
|
Create this file with the given access mode, if it doesn't exist.
|
||||||
"""
|
"""
|
||||||
if self._closed:
|
|
||||||
self._raise_closed()
|
|
||||||
if exist_ok:
|
if exist_ok:
|
||||||
# First try to bump modification time
|
# First try to bump modification time
|
||||||
# Implementation note: GNU touch uses the UTIME_NOW option of
|
# Implementation note: GNU touch uses the UTIME_NOW option of
|
||||||
|
@ -1301,8 +1288,6 @@ class Path(PurePath):
|
||||||
"""
|
"""
|
||||||
Create a new directory at this given path.
|
Create a new directory at this given path.
|
||||||
"""
|
"""
|
||||||
if self._closed:
|
|
||||||
self._raise_closed()
|
|
||||||
try:
|
try:
|
||||||
self._accessor.mkdir(self, mode)
|
self._accessor.mkdir(self, mode)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
|
@ -1320,8 +1305,6 @@ class Path(PurePath):
|
||||||
"""
|
"""
|
||||||
Change the permissions of the path, like os.chmod().
|
Change the permissions of the path, like os.chmod().
|
||||||
"""
|
"""
|
||||||
if self._closed:
|
|
||||||
self._raise_closed()
|
|
||||||
self._accessor.chmod(self, mode)
|
self._accessor.chmod(self, mode)
|
||||||
|
|
||||||
def lchmod(self, mode):
|
def lchmod(self, mode):
|
||||||
|
@ -1329,8 +1312,6 @@ class Path(PurePath):
|
||||||
Like chmod(), except if the path points to a symlink, the symlink's
|
Like chmod(), except if the path points to a symlink, the symlink's
|
||||||
permissions are changed, rather than its target's.
|
permissions are changed, rather than its target's.
|
||||||
"""
|
"""
|
||||||
if self._closed:
|
|
||||||
self._raise_closed()
|
|
||||||
self._accessor.lchmod(self, mode)
|
self._accessor.lchmod(self, mode)
|
||||||
|
|
||||||
def unlink(self, missing_ok=False):
|
def unlink(self, missing_ok=False):
|
||||||
|
@ -1338,8 +1319,6 @@ class Path(PurePath):
|
||||||
Remove this file or link.
|
Remove this file or link.
|
||||||
If the path is a directory, use rmdir() instead.
|
If the path is a directory, use rmdir() instead.
|
||||||
"""
|
"""
|
||||||
if self._closed:
|
|
||||||
self._raise_closed()
|
|
||||||
try:
|
try:
|
||||||
self._accessor.unlink(self)
|
self._accessor.unlink(self)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
|
@ -1350,8 +1329,6 @@ class Path(PurePath):
|
||||||
"""
|
"""
|
||||||
Remove this directory. The directory must be empty.
|
Remove this directory. The directory must be empty.
|
||||||
"""
|
"""
|
||||||
if self._closed:
|
|
||||||
self._raise_closed()
|
|
||||||
self._accessor.rmdir(self)
|
self._accessor.rmdir(self)
|
||||||
|
|
||||||
def lstat(self):
|
def lstat(self):
|
||||||
|
@ -1359,16 +1336,12 @@ class Path(PurePath):
|
||||||
Like stat(), except if the path points to a symlink, the symlink's
|
Like stat(), except if the path points to a symlink, the symlink's
|
||||||
status information is returned, rather than its target's.
|
status information is returned, rather than its target's.
|
||||||
"""
|
"""
|
||||||
if self._closed:
|
|
||||||
self._raise_closed()
|
|
||||||
return self._accessor.lstat(self)
|
return self._accessor.lstat(self)
|
||||||
|
|
||||||
def link_to(self, target):
|
def link_to(self, target):
|
||||||
"""
|
"""
|
||||||
Create a hard link pointing to a path named target.
|
Create a hard link pointing to a path named target.
|
||||||
"""
|
"""
|
||||||
if self._closed:
|
|
||||||
self._raise_closed()
|
|
||||||
self._accessor.link_to(self, target)
|
self._accessor.link_to(self, target)
|
||||||
|
|
||||||
def rename(self, target):
|
def rename(self, target):
|
||||||
|
@ -1376,8 +1349,6 @@ class Path(PurePath):
|
||||||
Rename this path to the given path,
|
Rename this path to the given path,
|
||||||
and return a new Path instance pointing to the given path.
|
and return a new Path instance pointing to the given path.
|
||||||
"""
|
"""
|
||||||
if self._closed:
|
|
||||||
self._raise_closed()
|
|
||||||
self._accessor.rename(self, target)
|
self._accessor.rename(self, target)
|
||||||
return self.__class__(target)
|
return self.__class__(target)
|
||||||
|
|
||||||
|
@ -1387,8 +1358,6 @@ class Path(PurePath):
|
||||||
destination if it exists, and return a new Path instance
|
destination if it exists, and return a new Path instance
|
||||||
pointing to the given path.
|
pointing to the given path.
|
||||||
"""
|
"""
|
||||||
if self._closed:
|
|
||||||
self._raise_closed()
|
|
||||||
self._accessor.replace(self, target)
|
self._accessor.replace(self, target)
|
||||||
return self.__class__(target)
|
return self.__class__(target)
|
||||||
|
|
||||||
|
@ -1397,8 +1366,6 @@ class Path(PurePath):
|
||||||
Make this path a symlink pointing to the given path.
|
Make this path a symlink pointing to the given path.
|
||||||
Note the order of arguments (self, target) is the reverse of os.symlink's.
|
Note the order of arguments (self, target) is the reverse of os.symlink's.
|
||||||
"""
|
"""
|
||||||
if self._closed:
|
|
||||||
self._raise_closed()
|
|
||||||
self._accessor.symlink(target, self, target_is_directory)
|
self._accessor.symlink(target, self, target_is_directory)
|
||||||
|
|
||||||
# Convenience functions for querying the stat results
|
# Convenience functions for querying the stat results
|
||||||
|
|
|
@ -1720,13 +1720,15 @@ class _BasePathTest(object):
|
||||||
next(it2)
|
next(it2)
|
||||||
with p:
|
with p:
|
||||||
pass
|
pass
|
||||||
# I/O operation on closed path.
|
# Using a path as a context manager is a no-op, thus the following
|
||||||
self.assertRaises(ValueError, next, it)
|
# operations should still succeed after the context manage exits.
|
||||||
self.assertRaises(ValueError, next, it2)
|
next(it)
|
||||||
self.assertRaises(ValueError, p.open)
|
next(it2)
|
||||||
self.assertRaises(ValueError, p.resolve)
|
p.exists()
|
||||||
self.assertRaises(ValueError, p.absolute)
|
p.resolve()
|
||||||
self.assertRaises(ValueError, p.__enter__)
|
p.absolute()
|
||||||
|
with p:
|
||||||
|
pass
|
||||||
|
|
||||||
def test_chmod(self):
|
def test_chmod(self):
|
||||||
p = self.cls(BASE) / 'fileA'
|
p = self.cls(BASE) / 'fileA'
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
Remove undocumented support for *closing* a `pathlib.Path` object via its
|
||||||
|
context manager. The context manager magic methods remain, but they are now a
|
||||||
|
no-op, making `Path` objects immutable.
|
Loading…
Reference in New Issue