Skip to content

Commit 08f8301

Browse files
authored
bpo-43012: remove pathlib._Accessor (GH-25701)
Per Pitrou: > The original intent for the “accessor” thing was to have a variant that did all accesses under a filesystem tree in a race condition-free way using openat and friends. It turned out to be much too hairy to actually implement, so was entirely abandoned, but the accessor abstraction was left there. https://discuss.python.org/t/make-pathlib-extensible/3428/2 Accessors are: - Lacking any internal purpose - '_NormalAccessor' is the only implementation - Lacking any firm conceptual difference to `Path` objects themselves (inc. subclasses) - Non-public, i.e. underscore prefixed - '_Accessor' and '_NormalAccessor' - Unofficially used to implement customized `Path` objects, but once once [bpo-24132]() is addressed there will be a supported route for that. This patch preserves all existing behaviour.
1 parent 187930f commit 08f8301

File tree

3 files changed

+78
-130
lines changed

3 files changed

+78
-130
lines changed

Lib/pathlib.py

+59-112
Original file line numberDiff line numberDiff line change
@@ -275,93 +275,6 @@ def make_uri(self, path):
275275
_posix_flavour = _PosixFlavour()
276276

277277

278-
class _Accessor:
279-
"""An accessor implements a particular (system-specific or not) way of
280-
accessing paths on the filesystem."""
281-
282-
283-
class _NormalAccessor(_Accessor):
284-
285-
stat = os.stat
286-
287-
open = io.open
288-
289-
listdir = os.listdir
290-
291-
scandir = os.scandir
292-
293-
chmod = os.chmod
294-
295-
mkdir = os.mkdir
296-
297-
unlink = os.unlink
298-
299-
if hasattr(os, "link"):
300-
link = os.link
301-
else:
302-
def link(self, src, dst):
303-
raise NotImplementedError("os.link() not available on this system")
304-
305-
rmdir = os.rmdir
306-
307-
rename = os.rename
308-
309-
replace = os.replace
310-
311-
if hasattr(os, "symlink"):
312-
symlink = os.symlink
313-
else:
314-
def symlink(self, src, dst, target_is_directory=False):
315-
raise NotImplementedError("os.symlink() not available on this system")
316-
317-
def touch(self, path, mode=0o666, exist_ok=True):
318-
if exist_ok:
319-
# First try to bump modification time
320-
# Implementation note: GNU touch uses the UTIME_NOW option of
321-
# the utimensat() / futimens() functions.
322-
try:
323-
os.utime(path, None)
324-
except OSError:
325-
# Avoid exception chaining
326-
pass
327-
else:
328-
return
329-
flags = os.O_CREAT | os.O_WRONLY
330-
if not exist_ok:
331-
flags |= os.O_EXCL
332-
fd = os.open(path, flags, mode)
333-
os.close(fd)
334-
335-
if hasattr(os, "readlink"):
336-
readlink = os.readlink
337-
else:
338-
def readlink(self, path):
339-
raise NotImplementedError("os.readlink() not available on this system")
340-
341-
def owner(self, path):
342-
try:
343-
import pwd
344-
return pwd.getpwuid(self.stat(path).st_uid).pw_name
345-
except ImportError:
346-
raise NotImplementedError("Path.owner() is unsupported on this system")
347-
348-
def group(self, path):
349-
try:
350-
import grp
351-
return grp.getgrgid(self.stat(path).st_gid).gr_name
352-
except ImportError:
353-
raise NotImplementedError("Path.group() is unsupported on this system")
354-
355-
getcwd = os.getcwd
356-
357-
expanduser = staticmethod(os.path.expanduser)
358-
359-
realpath = staticmethod(os.path.realpath)
360-
361-
362-
_normal_accessor = _NormalAccessor()
363-
364-
365278
#
366279
# Globbing helpers
367280
#
@@ -402,7 +315,7 @@ def select_from(self, parent_path):
402315
path_cls = type(parent_path)
403316
is_dir = path_cls.is_dir
404317
exists = path_cls.exists
405-
scandir = parent_path._accessor.scandir
318+
scandir = path_cls._scandir
406319
if not is_dir(parent_path):
407320
return iter([])
408321
return self._select_from(parent_path, is_dir, exists, scandir)
@@ -949,7 +862,6 @@ class Path(PurePath):
949862
object. You can also instantiate a PosixPath or WindowsPath directly,
950863
but cannot instantiate a WindowsPath on a POSIX system or vice versa.
951864
"""
952-
_accessor = _normal_accessor
953865
__slots__ = ()
954866

955867
def __new__(cls, *args, **kwargs):
@@ -988,7 +900,7 @@ def cwd(cls):
988900
"""Return a new path pointing to the current working directory
989901
(as returned by os.getcwd()).
990902
"""
991-
return cls(cls._accessor.getcwd())
903+
return cls(os.getcwd())
992904

993905
@classmethod
994906
def home(cls):
@@ -1005,16 +917,22 @@ def samefile(self, other_path):
1005917
try:
1006918
other_st = other_path.stat()
1007919
except AttributeError:
1008-
other_st = self._accessor.stat(other_path)
920+
other_st = self.__class__(other_path).stat()
1009921
return os.path.samestat(st, other_st)
1010922

1011923
def iterdir(self):
1012924
"""Iterate over the files in this directory. Does not yield any
1013925
result for the special paths '.' and '..'.
1014926
"""
1015-
for name in self._accessor.listdir(self):
927+
for name in os.listdir(self):
1016928
yield self._make_child_relpath(name)
1017929

930+
def _scandir(self):
931+
# bpo-24132: a future version of pathlib will support subclassing of
932+
# pathlib.Path to customize how the filesystem is accessed. This
933+
# includes scandir(), which is used to implement glob().
934+
return os.scandir(self)
935+
1018936
def glob(self, pattern):
1019937
"""Iterate over this subtree and yield all existing files (of any
1020938
kind, including directories) matching the given relative pattern.
@@ -1050,7 +968,7 @@ def absolute(self):
1050968
"""
1051969
if self.is_absolute():
1052970
return self
1053-
return self._from_parts([self._accessor.getcwd()] + self._parts)
971+
return self._from_parts([self.cwd()] + self._parts)
1054972

1055973
def resolve(self, strict=False):
1056974
"""
@@ -1064,7 +982,7 @@ def check_eloop(e):
1064982
raise RuntimeError("Symlink loop from %r" % e.filename)
1065983

1066984
try:
1067-
s = self._accessor.realpath(self, strict=strict)
985+
s = os.path.realpath(self, strict=strict)
1068986
except OSError as e:
1069987
check_eloop(e)
1070988
raise
@@ -1084,19 +1002,28 @@ def stat(self, *, follow_symlinks=True):
10841002
Return the result of the stat() system call on this path, like
10851003
os.stat() does.
10861004
"""
1087-
return self._accessor.stat(self, follow_symlinks=follow_symlinks)
1005+
return os.stat(self, follow_symlinks=follow_symlinks)
10881006

10891007
def owner(self):
10901008
"""
10911009
Return the login name of the file owner.
10921010
"""
1093-
return self._accessor.owner(self)
1011+
try:
1012+
import pwd
1013+
return pwd.getpwuid(self.stat().st_uid).pw_name
1014+
except ImportError:
1015+
raise NotImplementedError("Path.owner() is unsupported on this system")
10941016

10951017
def group(self):
10961018
"""
10971019
Return the group name of the file gid.
10981020
"""
1099-
return self._accessor.group(self)
1021+
1022+
try:
1023+
import grp
1024+
return grp.getgrgid(self.stat().st_gid).gr_name
1025+
except ImportError:
1026+
raise NotImplementedError("Path.group() is unsupported on this system")
11001027

11011028
def open(self, mode='r', buffering=-1, encoding=None,
11021029
errors=None, newline=None):
@@ -1106,8 +1033,7 @@ def open(self, mode='r', buffering=-1, encoding=None,
11061033
"""
11071034
if "b" not in mode:
11081035
encoding = io.text_encoding(encoding)
1109-
return self._accessor.open(self, mode, buffering, encoding, errors,
1110-
newline)
1036+
return io.open(self, mode, buffering, encoding, errors, newline)
11111037

11121038
def read_bytes(self):
11131039
"""
@@ -1148,21 +1074,38 @@ def readlink(self):
11481074
"""
11491075
Return the path to which the symbolic link points.
11501076
"""
1151-
path = self._accessor.readlink(self)
1152-
return self._from_parts((path,))
1077+
if not hasattr(os, "readlink"):
1078+
raise NotImplementedError("os.readlink() not available on this system")
1079+
return self._from_parts((os.readlink(self),))
11531080

11541081
def touch(self, mode=0o666, exist_ok=True):
11551082
"""
11561083
Create this file with the given access mode, if it doesn't exist.
11571084
"""
1158-
self._accessor.touch(self, mode, exist_ok)
1085+
1086+
if exist_ok:
1087+
# First try to bump modification time
1088+
# Implementation note: GNU touch uses the UTIME_NOW option of
1089+
# the utimensat() / futimens() functions.
1090+
try:
1091+
os.utime(self, None)
1092+
except OSError:
1093+
# Avoid exception chaining
1094+
pass
1095+
else:
1096+
return
1097+
flags = os.O_CREAT | os.O_WRONLY
1098+
if not exist_ok:
1099+
flags |= os.O_EXCL
1100+
fd = os.open(self, flags, mode)
1101+
os.close(fd)
11591102

11601103
def mkdir(self, mode=0o777, parents=False, exist_ok=False):
11611104
"""
11621105
Create a new directory at this given path.
11631106
"""
11641107
try:
1165-
self._accessor.mkdir(self, mode)
1108+
os.mkdir(self, mode)
11661109
except FileNotFoundError:
11671110
if not parents or self.parent == self:
11681111
raise
@@ -1178,7 +1121,7 @@ def chmod(self, mode, *, follow_symlinks=True):
11781121
"""
11791122
Change the permissions of the path, like os.chmod().
11801123
"""
1181-
self._accessor.chmod(self, mode, follow_symlinks=follow_symlinks)
1124+
os.chmod(self, mode, follow_symlinks=follow_symlinks)
11821125

11831126
def lchmod(self, mode):
11841127
"""
@@ -1193,7 +1136,7 @@ def unlink(self, missing_ok=False):
11931136
If the path is a directory, use rmdir() instead.
11941137
"""
11951138
try:
1196-
self._accessor.unlink(self)
1139+
os.unlink(self)
11971140
except FileNotFoundError:
11981141
if not missing_ok:
11991142
raise
@@ -1202,7 +1145,7 @@ def rmdir(self):
12021145
"""
12031146
Remove this directory. The directory must be empty.
12041147
"""
1205-
self._accessor.rmdir(self)
1148+
os.rmdir(self)
12061149

12071150
def lstat(self):
12081151
"""
@@ -1221,7 +1164,7 @@ def rename(self, target):
12211164
12221165
Returns the new Path instance pointing to the target path.
12231166
"""
1224-
self._accessor.rename(self, target)
1167+
os.rename(self, target)
12251168
return self.__class__(target)
12261169

12271170
def replace(self, target):
@@ -1234,23 +1177,27 @@ def replace(self, target):
12341177
12351178
Returns the new Path instance pointing to the target path.
12361179
"""
1237-
self._accessor.replace(self, target)
1180+
os.replace(self, target)
12381181
return self.__class__(target)
12391182

12401183
def symlink_to(self, target, target_is_directory=False):
12411184
"""
12421185
Make this path a symlink pointing to the target path.
12431186
Note the order of arguments (link, target) is the reverse of os.symlink.
12441187
"""
1245-
self._accessor.symlink(target, self, target_is_directory)
1188+
if not hasattr(os, "symlink"):
1189+
raise NotImplementedError("os.symlink() not available on this system")
1190+
os.symlink(target, self, target_is_directory)
12461191

12471192
def hardlink_to(self, target):
12481193
"""
12491194
Make this path a hard link pointing to the same file as *target*.
12501195
12511196
Note the order of arguments (self, target) is the reverse of os.link's.
12521197
"""
1253-
self._accessor.link(target, self)
1198+
if not hasattr(os, "link"):
1199+
raise NotImplementedError("os.link() not available on this system")
1200+
os.link(target, self)
12541201

12551202
def link_to(self, target):
12561203
"""
@@ -1268,7 +1215,7 @@ def link_to(self, target):
12681215
"for removal in Python 3.12. "
12691216
"Use pathlib.Path.hardlink_to() instead.",
12701217
DeprecationWarning, stacklevel=2)
1271-
self._accessor.link(self, target)
1218+
self.__class__(target).hardlink_to(self)
12721219

12731220
# Convenience functions for querying the stat results
12741221

@@ -1425,7 +1372,7 @@ def expanduser(self):
14251372
"""
14261373
if (not (self._drv or self._root) and
14271374
self._parts and self._parts[0][:1] == '~'):
1428-
homedir = self._accessor.expanduser(self._parts[0])
1375+
homedir = os.path.expanduser(self._parts[0])
14291376
if homedir[:1] == "~":
14301377
raise RuntimeError("Could not determine home directory.")
14311378
return self._from_parts([homedir] + self._parts[1:])

0 commit comments

Comments
 (0)