Thanks to visit codestin.com
Credit goes to github.com

Skip to content

bpo-39682: make pathlib.Path immutable by removing (undocumented) support for "closing" a path by using it as a context manager #18846

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 9 additions & 42 deletions Lib/pathlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -1041,7 +1041,6 @@ class Path(PurePath):
"""
__slots__ = (
'_accessor',
'_closed',
)

def __new__(cls, *args, **kwargs):
Expand All @@ -1058,7 +1057,6 @@ def _init(self,
# Private non-constructor arguments
template=None,
):
self._closed = False
if template is not None:
self._accessor = template._accessor
else:
Expand All @@ -1071,15 +1069,18 @@ def _make_child_relpath(self, part):
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()
Expand All @@ -1090,8 +1091,6 @@ def _raw_open(self, flags, mode=0o777):
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
Expand Down Expand Up @@ -1125,15 +1124,11 @@ def iterdir(self):
"""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
Expand Down Expand Up @@ -1170,8 +1165,6 @@ def absolute(self):
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,
Expand All @@ -1186,8 +1179,6 @@ def resolve(self, strict=False):
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
Expand Down Expand Up @@ -1227,8 +1218,6 @@ def open(self, mode='r', buffering=-1, encoding=None,
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)

Expand Down Expand Up @@ -1278,8 +1267,6 @@ def touch(self, mode=0o666, exist_ok=True):
"""
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
Expand All @@ -1301,8 +1288,6 @@ def mkdir(self, mode=0o777, parents=False, exist_ok=False):
"""
Create a new directory at this given path.
"""
if self._closed:
self._raise_closed()
try:
self._accessor.mkdir(self, mode)
except FileNotFoundError:
Expand All @@ -1320,26 +1305,20 @@ def chmod(self, mode):
"""
Change the permissions of the path, like os.chmod().
"""
if self._closed:
self._raise_closed()
self._accessor.chmod(self, mode)

def lchmod(self, mode):
"""
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):
"""
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:
Expand All @@ -1350,34 +1329,26 @@ def rmdir(self):
"""
Remove this directory. The directory must be empty.
"""
if self._closed:
self._raise_closed()
self._accessor.rmdir(self)

def lstat(self):
"""
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):
"""
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)

Expand All @@ -1387,8 +1358,6 @@ def replace(self, target):
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)

Expand All @@ -1397,8 +1366,6 @@ def symlink_to(self, target, target_is_directory=False):
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
Expand Down
16 changes: 9 additions & 7 deletions Lib/test/test_pathlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -1720,13 +1720,15 @@ def test_with(self):
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'
Expand Down
Original file line number Diff line number Diff line change
@@ -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.