summaryrefslogtreecommitdiffstats
path: root/contrib/tools/python3/src/Lib/pathlib.py
diff options
context:
space:
mode:
authorshadchin <[email protected]>2022-04-18 12:39:32 +0300
committershadchin <[email protected]>2022-04-18 12:39:32 +0300
commitd4be68e361f4258cf0848fc70018dfe37a2acc24 (patch)
tree153e294cd97ac8b5d7a989612704a0c1f58e8ad4 /contrib/tools/python3/src/Lib/pathlib.py
parent260c02f5ccf242d9d9b8a873afaf6588c00237d6 (diff)
IGNIETFERRO-1816 Update Python 3 from 3.9.12 to 3.10.4
ref:9f96be6d02ee8044fdd6f124b799b270c20ce641
Diffstat (limited to 'contrib/tools/python3/src/Lib/pathlib.py')
-rw-r--r--contrib/tools/python3/src/Lib/pathlib.py354
1 files changed, 110 insertions, 244 deletions
diff --git a/contrib/tools/python3/src/Lib/pathlib.py b/contrib/tools/python3/src/Lib/pathlib.py
index 7aeda14a141..621fba0e75c 100644
--- a/contrib/tools/python3/src/Lib/pathlib.py
+++ b/contrib/tools/python3/src/Lib/pathlib.py
@@ -6,6 +6,7 @@ import os
import posixpath
import re
import sys
+import warnings
from _collections_abc import Sequence
from errno import EINVAL, ENOENT, ENOTDIR, EBADF, ELOOP
from operator import attrgetter
@@ -13,18 +14,6 @@ from stat import S_ISDIR, S_ISLNK, S_ISREG, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO
from urllib.parse import quote_from_bytes as urlquote_from_bytes
-supports_symlinks = True
-if os.name == 'nt':
- import nt
- if sys.getwindowsversion()[:2] >= (6, 0):
- from nt import _getfinalpathname
- else:
- supports_symlinks = False
- _getfinalpathname = None
-else:
- nt = None
-
-
__all__ = [
"PurePath", "PurePosixPath", "PureWindowsPath",
"Path", "PosixPath", "WindowsPath",
@@ -34,14 +23,17 @@ __all__ = [
# Internals
#
+_WINERROR_NOT_READY = 21 # drive exists but is not accessible
+_WINERROR_INVALID_NAME = 123 # fix for bpo-35306
+_WINERROR_CANT_RESOLVE_FILENAME = 1921 # broken symlink pointing to itself
+
# EBADF - guard against macOS `stat` throwing EBADF
_IGNORED_ERROS = (ENOENT, ENOTDIR, EBADF, ELOOP)
_IGNORED_WINERRORS = (
- 21, # ERROR_NOT_READY - drive exists but is not accessible
- 123, # ERROR_INVALID_NAME - fix for bpo-35306
- 1921, # ERROR_CANT_RESOLVE_FILENAME - fix for broken symlink pointing to itself
-)
+ _WINERROR_NOT_READY,
+ _WINERROR_INVALID_NAME,
+ _WINERROR_CANT_RESOLVE_FILENAME)
def _ignore_error(exception):
return (getattr(exception, 'errno', None) in _IGNORED_ERROS or
@@ -200,30 +192,6 @@ class _WindowsFlavour(_Flavour):
def compile_pattern(self, pattern):
return re.compile(fnmatch.translate(pattern), re.IGNORECASE).fullmatch
- def resolve(self, path, strict=False):
- s = str(path)
- if not s:
- return os.getcwd()
- previous_s = None
- if _getfinalpathname is not None:
- if strict:
- return self._ext_to_normal(_getfinalpathname(s))
- else:
- tail_parts = [] # End of the path after the first one not found
- while True:
- try:
- s = self._ext_to_normal(_getfinalpathname(s))
- except FileNotFoundError:
- previous_s = s
- s, tail = os.path.split(s)
- tail_parts.append(tail)
- if previous_s == s:
- return path
- else:
- return os.path.join(s, *reversed(tail_parts))
- # Means fallback on absolute
- return None
-
def _split_extended_path(self, s, ext_prefix=ext_namespace_prefix):
prefix = ''
if s.startswith(ext_prefix):
@@ -234,10 +202,6 @@ class _WindowsFlavour(_Flavour):
s = '\\' + s[3:]
return prefix, s
- def _ext_to_normal(self, s):
- # Turn back an extended path into a normal DOS-like path
- return self._split_extended_path(s)[1]
-
def is_reserved(self, parts):
# NOTE: the rules for reserved names seem somewhat complicated
# (e.g. r"..\NUL" is reserved but not r"foo\NUL" if "foo" does not
@@ -263,34 +227,6 @@ class _WindowsFlavour(_Flavour):
# It's a path on a network drive => 'file://host/share/a/b'
return 'file:' + urlquote_from_bytes(path.as_posix().encode('utf-8'))
- def gethomedir(self, username):
- if 'USERPROFILE' in os.environ:
- userhome = os.environ['USERPROFILE']
- elif 'HOMEPATH' in os.environ:
- try:
- drv = os.environ['HOMEDRIVE']
- except KeyError:
- drv = ''
- userhome = drv + os.environ['HOMEPATH']
- else:
- raise RuntimeError("Can't determine home directory")
-
- if username:
- # Try to guess user home directory. By default all users
- # directories are located in the same place and are named by
- # corresponding usernames. If current user home directory points
- # to nonstandard place, this guess is likely wrong.
- if os.environ['USERNAME'] != username:
- drv, root, parts = self.parse_parts((userhome,))
- if parts[-1] != os.environ['USERNAME']:
- raise RuntimeError("Can't determine home directory "
- "for %r" % username)
- parts[-1] = username
- if drv or root:
- userhome = drv + root + self.join(parts[1:])
- else:
- userhome = self.join(parts)
- return userhome
class _PosixFlavour(_Flavour):
sep = '/'
@@ -324,54 +260,6 @@ class _PosixFlavour(_Flavour):
def compile_pattern(self, pattern):
return re.compile(fnmatch.translate(pattern)).fullmatch
- def resolve(self, path, strict=False):
- sep = self.sep
- accessor = path._accessor
- seen = {}
- def _resolve(path, rest):
- if rest.startswith(sep):
- path = ''
-
- for name in rest.split(sep):
- if not name or name == '.':
- # current dir
- continue
- if name == '..':
- # parent dir
- path, _, _ = path.rpartition(sep)
- continue
- if path.endswith(sep):
- newpath = path + name
- else:
- newpath = path + sep + name
- if newpath in seen:
- # Already seen this path
- path = seen[newpath]
- if path is not None:
- # use cached value
- continue
- # The symlink is not resolved, so we must have a symlink loop.
- raise RuntimeError("Symlink loop from %r" % newpath)
- # Resolve the symbolic link
- try:
- target = accessor.readlink(newpath)
- except OSError as e:
- if e.errno != EINVAL and strict:
- raise
- # Not a symlink, or non-strict mode. We just leave the path
- # untouched.
- path = newpath
- else:
- seen[newpath] = None # not resolved symlink
- path = _resolve(path, target)
- seen[newpath] = path # resolved symlink
-
- return path
- # NOTE: according to POSIX, getcwd() cannot contain path components
- # which are symlinks.
- base = '' if path.is_absolute() else os.getcwd()
- return _resolve(base, str(path)) or sep
-
def is_reserved(self, parts):
return False
@@ -381,21 +269,6 @@ class _PosixFlavour(_Flavour):
bpath = bytes(path)
return 'file://' + urlquote_from_bytes(bpath)
- def gethomedir(self, username):
- if not username:
- try:
- return os.environ['HOME']
- except KeyError:
- import pwd
- return pwd.getpwuid(os.getuid()).pw_dir
- else:
- import pwd
- try:
- return pwd.getpwnam(username).pw_dir
- except KeyError:
- raise RuntimeError("Can't determine home directory "
- "for %r" % username)
-
_windows_flavour = _WindowsFlavour()
_posix_flavour = _PosixFlavour()
@@ -410,9 +283,7 @@ class _NormalAccessor(_Accessor):
stat = os.stat
- lstat = os.lstat
-
- open = os.open
+ open = io.open
listdir = os.listdir
@@ -420,21 +291,14 @@ class _NormalAccessor(_Accessor):
chmod = os.chmod
- if hasattr(os, "lchmod"):
- lchmod = os.lchmod
- else:
- def lchmod(self, pathobj, mode):
- raise NotImplementedError("lchmod() not available on this system")
-
mkdir = os.mkdir
unlink = os.unlink
if hasattr(os, "link"):
- link_to = os.link
+ link = os.link
else:
- @staticmethod
- def link_to(self, target):
+ def link(self, src, dst):
raise NotImplementedError("os.link() not available on this system")
rmdir = os.rmdir
@@ -443,23 +307,35 @@ class _NormalAccessor(_Accessor):
replace = os.replace
- if nt:
- if supports_symlinks:
- symlink = os.symlink
- else:
- def symlink(a, b, target_is_directory):
- raise NotImplementedError("symlink() not available on this system")
+ if hasattr(os, "symlink"):
+ symlink = os.symlink
else:
- # Under POSIX, os.symlink() takes two args
- @staticmethod
- def symlink(a, b, target_is_directory):
- return os.symlink(a, b)
+ def symlink(self, src, dst, target_is_directory=False):
+ raise NotImplementedError("os.symlink() not available on this system")
- utime = os.utime
+ def touch(self, path, mode=0o666, exist_ok=True):
+ if exist_ok:
+ # First try to bump modification time
+ # Implementation note: GNU touch uses the UTIME_NOW option of
+ # the utimensat() / futimens() functions.
+ try:
+ os.utime(path, None)
+ except OSError:
+ # Avoid exception chaining
+ pass
+ else:
+ return
+ flags = os.O_CREAT | os.O_WRONLY
+ if not exist_ok:
+ flags |= os.O_EXCL
+ fd = os.open(path, flags, mode)
+ os.close(fd)
- # Helper for resolve()
- def readlink(self, path):
- return os.readlink(path)
+ if hasattr(os, "readlink"):
+ readlink = os.readlink
+ else:
+ def readlink(self, path):
+ raise NotImplementedError("os.readlink() not available on this system")
def owner(self, path):
try:
@@ -475,6 +351,12 @@ class _NormalAccessor(_Accessor):
except ImportError:
raise NotImplementedError("Path.group() is unsupported on this system")
+ getcwd = os.getcwd
+
+ expanduser = staticmethod(os.path.expanduser)
+
+ realpath = staticmethod(os.path.realpath)
+
_normal_accessor = _NormalAccessor()
@@ -641,7 +523,10 @@ class _PathParents(Sequence):
return len(self._parts)
def __getitem__(self, idx):
- if idx < 0 or idx >= len(self):
+ if isinstance(idx, slice):
+ return tuple(self[i] for i in range(*idx.indices(len(self))))
+
+ if idx >= len(self) or idx < -len(self):
raise IndexError(idx)
return self._pathcls._from_parsed_parts(self._drv, self._root,
self._parts[:-idx - 1])
@@ -700,7 +585,7 @@ class PurePath(object):
return cls._flavour.parse_parts(parts)
@classmethod
- def _from_parts(cls, args, init=True):
+ def _from_parts(cls, args):
# We need to call _parse_args on the instance, so as to get the
# right flavour.
self = object.__new__(cls)
@@ -708,18 +593,14 @@ class PurePath(object):
self._drv = drv
self._root = root
self._parts = parts
- if init:
- self._init()
return self
@classmethod
- def _from_parsed_parts(cls, drv, root, parts, init=True):
+ def _from_parsed_parts(cls, drv, root, parts):
self = object.__new__(cls)
self._drv = drv
self._root = root
self._parts = parts
- if init:
- self._init()
return self
@classmethod
@@ -729,10 +610,6 @@ class PurePath(object):
else:
return cls._flavour.join(parts)
- def _init(self):
- # Overridden in concrete Path
- pass
-
def _make_child(self, args):
drv, root, parts = self._parse_args(args)
drv, root, parts = self._flavour.join_parsed_parts(
@@ -1072,29 +949,18 @@ class Path(PurePath):
object. You can also instantiate a PosixPath or WindowsPath directly,
but cannot instantiate a WindowsPath on a POSIX system or vice versa.
"""
- __slots__ = (
- '_accessor',
- )
+ _accessor = _normal_accessor
+ __slots__ = ()
def __new__(cls, *args, **kwargs):
if cls is Path:
cls = WindowsPath if os.name == 'nt' else PosixPath
- self = cls._from_parts(args, init=False)
+ self = cls._from_parts(args)
if not self._flavour.is_supported:
raise NotImplementedError("cannot instantiate %r on your system"
% (cls.__name__,))
- self._init()
return self
- def _init(self,
- # Private non-constructor arguments
- template=None,
- ):
- if template is not None:
- self._accessor = template._accessor
- else:
- self._accessor = _normal_accessor
-
def _make_child_relpath(self, part):
# This is an optimization used for dir walking. `part` must be
# a single part relative to this path.
@@ -1115,17 +981,6 @@ class Path(PurePath):
# removed in the future.
pass
- def _opener(self, name, flags, mode=0o666):
- # A stub for the opener argument to built-in open()
- return self._accessor.open(self, flags, mode)
-
- def _raw_open(self, flags, mode=0o777):
- """
- Open the file pointed by this path and return a file descriptor,
- as os.open() does.
- """
- return self._accessor.open(self, flags, mode)
-
# Public API
@classmethod
@@ -1133,14 +988,14 @@ class Path(PurePath):
"""Return a new path pointing to the current working directory
(as returned by os.getcwd()).
"""
- return cls(os.getcwd())
+ return cls(cls._accessor.getcwd())
@classmethod
def home(cls):
"""Return a new path pointing to the user's home directory (as
returned by os.path.expanduser('~')).
"""
- return cls(cls()._flavour.gethomedir(None))
+ return cls("~").expanduser()
def samefile(self, other_path):
"""Return whether other_path is the same or not as this file
@@ -1202,9 +1057,7 @@ class Path(PurePath):
return self
# FIXME this must defer to the specific flavour (and, under Windows,
# use nt._getfullpathname())
- obj = self._from_parts([os.getcwd()] + self._parts, init=False)
- obj._init(template=self)
- return obj
+ return self._from_parts([self._accessor.getcwd()] + self._parts)
def resolve(self, strict=False):
"""
@@ -1212,24 +1065,34 @@ class Path(PurePath):
normalizing it (for example turning slashes into backslashes under
Windows).
"""
- s = self._flavour.resolve(self, strict=strict)
- if s is None:
- # No symlink resolution => for consistency, raise an error if
- # the path doesn't exist or is forbidden
- self.stat()
- s = str(self.absolute())
- # Now we have no symlinks in the path, it's safe to normalize it.
- normed = self._flavour.pathmod.normpath(s)
- obj = self._from_parts((normed,), init=False)
- obj._init(template=self)
- return obj
- def stat(self):
+ def check_eloop(e):
+ winerror = getattr(e, 'winerror', 0)
+ if e.errno == ELOOP or winerror == _WINERROR_CANT_RESOLVE_FILENAME:
+ raise RuntimeError("Symlink loop from %r" % e.filename)
+
+ try:
+ s = self._accessor.realpath(self, strict=strict)
+ except OSError as e:
+ check_eloop(e)
+ raise
+ p = self._from_parts((s,))
+
+ # In non-strict mode, realpath() doesn't raise on symlink loops.
+ # Ensure we get an exception by calling stat()
+ if not strict:
+ try:
+ p.stat()
+ except OSError as e:
+ check_eloop(e)
+ return p
+
+ def stat(self, *, follow_symlinks=True):
"""
Return the result of the stat() system call on this path, like
os.stat() does.
"""
- return self._accessor.stat(self)
+ return self._accessor.stat(self, follow_symlinks=follow_symlinks)
def owner(self):
"""
@@ -1249,8 +1112,10 @@ class Path(PurePath):
Open the file pointed by this path and return a file object, as
the built-in open() function does.
"""
- return io.open(self, mode, buffering, encoding, errors, newline,
- opener=self._opener)
+ if "b" not in mode:
+ encoding = io.text_encoding(encoding)
+ return self._accessor.open(self, mode, buffering, encoding, errors,
+ newline)
def read_bytes(self):
"""
@@ -1263,6 +1128,7 @@ class Path(PurePath):
"""
Open the file in text mode, read it, and close the file.
"""
+ encoding = io.text_encoding(encoding)
with self.open(mode='r', encoding=encoding, errors=errors) as f:
return f.read()
@@ -1275,14 +1141,15 @@ class Path(PurePath):
with self.open(mode='wb') as f:
return f.write(view)
- def write_text(self, data, encoding=None, errors=None):
+ def write_text(self, data, encoding=None, errors=None, newline=None):
"""
Open the file in text mode, write to it, and close the file.
"""
if not isinstance(data, str):
raise TypeError('data must be str, not %s' %
data.__class__.__name__)
- with self.open(mode='w', encoding=encoding, errors=errors) as f:
+ encoding = io.text_encoding(encoding)
+ with self.open(mode='w', encoding=encoding, errors=errors, newline=newline) as f:
return f.write(data)
def readlink(self):
@@ -1290,30 +1157,13 @@ class Path(PurePath):
Return the path to which the symbolic link points.
"""
path = self._accessor.readlink(self)
- obj = self._from_parts((path,), init=False)
- obj._init(template=self)
- return obj
+ return self._from_parts((path,))
def touch(self, mode=0o666, exist_ok=True):
"""
Create this file with the given access mode, if it doesn't exist.
"""
- if exist_ok:
- # First try to bump modification time
- # Implementation note: GNU touch uses the UTIME_NOW option of
- # the utimensat() / futimens() functions.
- try:
- self._accessor.utime(self, None)
- except OSError:
- # Avoid exception chaining
- pass
- else:
- return
- flags = os.O_CREAT | os.O_WRONLY
- if not exist_ok:
- flags |= os.O_EXCL
- fd = self._raw_open(flags, mode)
- os.close(fd)
+ self._accessor.touch(self, mode, exist_ok)
def mkdir(self, mode=0o777, parents=False, exist_ok=False):
"""
@@ -1332,18 +1182,18 @@ class Path(PurePath):
if not exist_ok or not self.is_dir():
raise
- def chmod(self, mode):
+ def chmod(self, mode, *, follow_symlinks=True):
"""
Change the permissions of the path, like os.chmod().
"""
- self._accessor.chmod(self, mode)
+ self._accessor.chmod(self, mode, follow_symlinks=follow_symlinks)
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.
"""
- self._accessor.lchmod(self, mode)
+ self.chmod(mode, follow_symlinks=False)
def unlink(self, missing_ok=False):
"""
@@ -1367,7 +1217,7 @@ 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.
"""
- return self._accessor.lstat(self)
+ return self.stat(follow_symlinks=False)
def rename(self, target):
"""
@@ -1402,6 +1252,14 @@ class Path(PurePath):
"""
self._accessor.symlink(target, self, target_is_directory)
+ def hardlink_to(self, target):
+ """
+ Make this path a hard link pointing to the same file as *target*.
+
+ Note the order of arguments (self, target) is the reverse of os.link's.
+ """
+ self._accessor.link(target, self)
+
def link_to(self, target):
"""
Make the target path a hard link pointing to this path.
@@ -1411,8 +1269,14 @@ class Path(PurePath):
of arguments (target, link) is the reverse of Path.symlink_to, but
matches that of os.link.
+ Deprecated since Python 3.10 and scheduled for removal in Python 3.12.
+ Use `hardlink_to()` instead.
"""
- self._accessor.link_to(self, target)
+ warnings.warn("pathlib.Path.link_to() is deprecated and is scheduled "
+ "for removal in Python 3.12. "
+ "Use pathlib.Path.hardlink_to() instead.",
+ DeprecationWarning, stacklevel=2)
+ self._accessor.link(self, target)
# Convenience functions for querying the stat results
@@ -1569,7 +1433,9 @@ class Path(PurePath):
"""
if (not (self._drv or self._root) and
self._parts and self._parts[0][:1] == '~'):
- homedir = self._flavour.gethomedir(self._parts[0][1:])
+ homedir = self._accessor.expanduser(self._parts[0])
+ if homedir[:1] == "~":
+ raise RuntimeError("Could not determine home directory.")
return self._from_parts([homedir] + self._parts[1:])
return self