]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
bpo-39682: make `pathlib.Path` immutable by removing (undocumented) support for ...
authorBarney Gale <barney.gale@gmail.com>
Wed, 1 Apr 2020 14:10:51 +0000 (15:10 +0100)
committerGitHub <noreply@github.com>
Wed, 1 Apr 2020 14:10:51 +0000 (16:10 +0200)
Support for using a path as a context manager remains, and is now a no-op.

Lib/pathlib.py
Lib/test/test_pathlib.py
Misc/NEWS.d/next/Library/2020-03-08-11-00-01.bpo-39682.AxXZNz.rst [new file with mode: 0644]

index 293462000c2a6a8e4dcc6696369cf7218f81828e..96b8b59530c72397b2f5449859d9eb73e7a8505c 100644 (file)
@@ -1041,7 +1041,6 @@ class Path(PurePath):
     """
     __slots__ = (
         '_accessor',
-        '_closed',
     )
 
     def __new__(cls, *args, **kwargs):
@@ -1058,7 +1057,6 @@ class Path(PurePath):
               # Private non-constructor arguments
               template=None,
               ):
-        self._closed = False
         if template is not None:
             self._accessor = template._accessor
         else:
@@ -1071,15 +1069,18 @@ class Path(PurePath):
         return self._from_parsed_parts(self._drv, self._root, parts)
 
     def __enter__(self):
-        if self._closed:
-            self._raise_closed()
         return self
 
     def __exit__(self, t, v, tb):
-        self._closed = True
-
-    def _raise_closed(self):
-        raise ValueError("I/O operation on closed path")
+        # https://bugs.python.org/issue39682
+        # In previous versions of pathlib, this method marked this path as
+        # closed; subsequent attempts to perform I/O would raise an IOError.
+        # 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):
         # 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,
         as os.open() does.
         """
-        if self._closed:
-            self._raise_closed()
         return self._accessor.open(self, flags, mode)
 
     # Public API
@@ -1125,15 +1124,11 @@ class Path(PurePath):
         """Iterate over the files in this directory.  Does not yield any
         result for the special paths '.' and '..'.
         """
-        if self._closed:
-            self._raise_closed()
         for name in self._accessor.listdir(self):
             if name in {'.', '..'}:
                 # Yielding a path object for these makes little sense
                 continue
             yield self._make_child_relpath(name)
-            if self._closed:
-                self._raise_closed()
 
     def glob(self, pattern):
         """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.
         """
         # XXX untested yet!
-        if self._closed:
-            self._raise_closed()
         if self.is_absolute():
             return self
         # 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
         Windows).
         """
-        if self._closed:
-            self._raise_closed()
         s = self._flavour.resolve(self, strict=strict)
         if s is None:
             # 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
         the built-in open() function does.
         """
-        if self._closed:
-            self._raise_closed()
         return io.open(self, mode, buffering, encoding, errors, newline,
                        opener=self._opener)
 
@@ -1278,8 +1267,6 @@ class Path(PurePath):
         """
         Create this file with the given access mode, if it doesn't exist.
         """
-        if self._closed:
-            self._raise_closed()
         if exist_ok:
             # First try to bump modification time
             # 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.
         """
-        if self._closed:
-            self._raise_closed()
         try:
             self._accessor.mkdir(self, mode)
         except FileNotFoundError:
@@ -1320,8 +1305,6 @@ class Path(PurePath):
         """
         Change the permissions of the path, like os.chmod().
         """
-        if self._closed:
-            self._raise_closed()
         self._accessor.chmod(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
         permissions are changed, rather than its target's.
         """
-        if self._closed:
-            self._raise_closed()
         self._accessor.lchmod(self, mode)
 
     def unlink(self, missing_ok=False):
@@ -1338,8 +1319,6 @@ class Path(PurePath):
         Remove this file or link.
         If the path is a directory, use rmdir() instead.
         """
-        if self._closed:
-            self._raise_closed()
         try:
             self._accessor.unlink(self)
         except FileNotFoundError:
@@ -1350,8 +1329,6 @@ class Path(PurePath):
         """
         Remove this directory.  The directory must be empty.
         """
-        if self._closed:
-            self._raise_closed()
         self._accessor.rmdir(self)
 
     def lstat(self):
@@ -1359,16 +1336,12 @@ class Path(PurePath):
         Like stat(), except if the path points to a symlink, the symlink's
         status information is returned, rather than its target's.
         """
-        if self._closed:
-            self._raise_closed()
         return self._accessor.lstat(self)
 
     def link_to(self, target):
         """
         Create a hard link pointing to a path named target.
         """
-        if self._closed:
-            self._raise_closed()
         self._accessor.link_to(self, target)
 
     def rename(self, target):
@@ -1376,8 +1349,6 @@ class Path(PurePath):
         Rename this path 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)
         return self.__class__(target)
 
@@ -1387,8 +1358,6 @@ class Path(PurePath):
         destination if it exists, and return a new Path instance
         pointing to the given path.
         """
-        if self._closed:
-            self._raise_closed()
         self._accessor.replace(self, target)
         return self.__class__(target)
 
@@ -1397,8 +1366,6 @@ class Path(PurePath):
         Make this path a symlink pointing to the given path.
         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)
 
     # Convenience functions for querying the stat results
index 5b362a0ff3d9b21345c868d9faf35e08b685b20d..8b19cffba37e9165e144d46477d1c6869eb66b26 100644 (file)
@@ -1720,13 +1720,15 @@ class _BasePathTest(object):
         next(it2)
         with p:
             pass
-        # I/O operation on closed path.
-        self.assertRaises(ValueError, next, it)
-        self.assertRaises(ValueError, next, it2)
-        self.assertRaises(ValueError, p.open)
-        self.assertRaises(ValueError, p.resolve)
-        self.assertRaises(ValueError, p.absolute)
-        self.assertRaises(ValueError, p.__enter__)
+        # Using a path as a context manager is a no-op, thus the following
+        # operations should still succeed after the context manage exits.
+        next(it)
+        next(it2)
+        p.exists()
+        p.resolve()
+        p.absolute()
+        with p:
+            pass
 
     def test_chmod(self):
         p = self.cls(BASE) / 'fileA'
diff --git a/Misc/NEWS.d/next/Library/2020-03-08-11-00-01.bpo-39682.AxXZNz.rst b/Misc/NEWS.d/next/Library/2020-03-08-11-00-01.bpo-39682.AxXZNz.rst
new file mode 100644 (file)
index 0000000..d71a321
--- /dev/null
@@ -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.