from _collections_abc import Sequence
from errno import EINVAL, ENOENT, ENOTDIR, EBADF, ELOOP
from operator import attrgetter
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
if sys.getwindowsversion()[:2] >= (6, 0):
from nt import _getfinalpathname
supports_symlinks = False
"PurePath", "PurePosixPath", "PureWindowsPath",
"Path", "PosixPath", "WindowsPath",
# EBADF - guard against macOS `stat` throwing EBADF
_IGNORED_ERROS = (ENOENT, ENOTDIR, EBADF, ELOOP)
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
def _ignore_error(exception):
return (getattr(exception, 'errno', None) in _IGNORED_ERROS or
getattr(exception, 'winerror', None) in _IGNORED_WINERRORS)
def _is_wildcard_pattern(pat):
# Whether this pattern needs actual matching using fnmatch, or can
# be looked up directly as a file.
return "*" in pat or "?" in pat or "[" in pat
"""A flavour implements a particular (platform-specific) set of path
self.join = self.sep.join
def parse_parts(self, parts):
part = part.replace(altsep, sep)
drv, root, rel = self.splitroot(part)
for x in reversed(rel.split(sep)):
parsed.append(sys.intern(x))
parsed.append(sys.intern(rel))
# If no drive is present, try to find one in the previous
# parts. This makes the result of parsing e.g.
# ("C:", "/", "a") reasonably intuitive.
part = part.replace(altsep, sep)
drv = self.splitroot(part)[0]
parsed.append(drv + root)
def join_parsed_parts(self, drv, root, parts, drv2, root2, parts2):
Join the two paths represented by the respective
(drive, root, parts) tuples. Return a new (drive, root, parts) tuple.
return drv, root2, [drv + root2] + parts2[1:]
if drv2 == drv or self.casefold(drv2) == self.casefold(drv):
# Same drive => second path is relative to the first
return drv, root, parts + parts2[1:]
# Second path is non-anchored (common case)
return drv, root, parts + parts2
return drv2, root2, parts2
class _WindowsFlavour(_Flavour):
# Reference for Windows paths can be found at
# http://msdn.microsoft.com/en-us/library/aa365247%28v=vs.85%29.aspx
is_supported = (os.name == 'nt')
drive_letters = set('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')
ext_namespace_prefix = '\\\\?\\'
{'CON', 'PRN', 'AUX', 'NUL', 'CONIN$', 'CONOUT$'} |
{'COM%s' % c for c in '123456789\xb9\xb2\xb3'} |
{'LPT%s' % c for c in '123456789\xb9\xb2\xb3'}
# Interesting findings about extended paths:
# * '\\?\c:\a' is an extended path, which bypasses normal Windows API
# path processing. Thus relative paths are not resolved and slash is not
# translated to backslash. It has the native NT path limit of 32767
# characters, but a bit less after resolving device symbolic links,
# such as '\??\C:' => '\Device\HarddiskVolume2'.
# * '\\?\c:/a' looks for a device named 'C:/a' because slash is a
# regular name character in the object namespace.
# * '\\?\c:\foo/bar' is invalid because '/' is illegal in NT filesystems.
# The only path separator at the filesystem level is backslash.
# * '//?/c:\a' and '//?/c:/a' are effectively equivalent to '\\.\c:\a' and
# thus limited to MAX_PATH.
# * Prior to Windows 8, ANSI API bytes paths are limited to MAX_PATH,
# even with the '\\?\' prefix.
def splitroot(self, part, sep=sep):
if (second == sep and first == sep):
# XXX extended paths should also disable the collapsing of "."
# components (according to MSDN docs).
prefix, part = self._split_extended_path(part)
if (second == sep and first == sep and third != sep):
# vvvvvvvvvvvvvvvvvvvvv root
# \\machine\mountpoint\directory\etc\...
# directory ^^^^^^^^^^^^^^
index = part.find(sep, 2)
index2 = part.find(sep, index + 1)
# a UNC path can't have two slashes in a row
# (after the initial two)
return prefix + part[1:index2], sep, part[index2+1:]
return part[:index2], sep, part[index2+1:]
if second == ':' and first in self.drive_letters:
return prefix + drv, root, part
def casefold_parts(self, parts):
return [p.lower() for p in parts]
def compile_pattern(self, pattern):
return re.compile(fnmatch.translate(pattern), re.IGNORECASE).fullmatch
def resolve(self, path, strict=False):
if _getfinalpathname is not None:
return self._ext_to_normal(_getfinalpathname(s))
tail_parts = [] # End of the path after the first one not found
s = self._ext_to_normal(_getfinalpathname(s))
except FileNotFoundError:
s, tail = os.path.split(s)
return os.path.join(s, *reversed(tail_parts))
# Means fallback on absolute
def _split_extended_path(self, s, ext_prefix=ext_namespace_prefix):
if s.startswith(ext_prefix):
if s.startswith('UNC\\'):
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
# exist). We err on the side of caution and return True for paths
# which are not considered reserved by Windows.
if parts[0].startswith('\\\\'):
# UNC paths are never reserved
name = parts[-1].partition('.')[0].partition(':')[0].rstrip(' ')
return name.upper() in self.reserved_names
def make_uri(self, path):
# Under Windows, file URIs use the UTF-8 encoding.
if len(drive) == 2 and drive[1] == ':':
# It's a path on a local drive => 'file:///c:/a/b'
rest = path.as_posix()[2:].lstrip('/')
return 'file:///%s/%s' % (
drive, urlquote_from_bytes(rest.encode('utf-8')))
# 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:
drv = os.environ['HOMEDRIVE']
userhome = drv + os.environ['HOMEPATH']
raise RuntimeError("Can't determine home directory")
# 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 "
userhome = drv + root + self.join(parts[1:])
userhome = self.join(parts)
class _PosixFlavour(_Flavour):
is_supported = (os.name != 'nt')
def splitroot(self, part, sep=sep):
if part and part[0] == sep:
stripped_part = part.lstrip(sep)
# According to POSIX path resolution:
# http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap04.html#tag_04_11
# "A pathname that begins with two successive slashes may be
# interpreted in an implementation-defined manner, although more
# than two leading slashes shall be treated as a single slash".
if len(part) - len(stripped_part) == 2:
return '', sep * 2, stripped_part
return '', sep, stripped_part
def casefold_parts(self, parts):
def compile_pattern(self, pattern):
return re.compile(fnmatch.translate(pattern)).fullmatch
def resolve(self, path, strict=False):
accessor = path._accessor
def _resolve(path, rest):
for name in rest.split(sep):
if not name or name == '.':
path, _, _ = path.rpartition(sep)
newpath = path + sep + name
# The symlink is not resolved, so we must have a symlink loop.
raise RuntimeError("Symlink loop from %r" % newpath)
# Resolve the symbolic link
target = accessor.readlink(newpath)
if e.errno != EINVAL and strict:
# Not a symlink, or non-strict mode. We just leave the path
seen[newpath] = None # not resolved symlink
path = _resolve(path, target)
seen[newpath] = path # resolved symlink
# NOTE: according to POSIX, getcwd() cannot contain path components
base = '' if path.is_absolute() else os.getcwd()
return _resolve(base, str(path)) or sep
def is_reserved(self, parts):
def make_uri(self, path):
# We represent the path using the local filesystem encoding,
# for portability to other applications.
return 'file://' + urlquote_from_bytes(bpath)
def gethomedir(self, username):
return os.environ['HOME']
return pwd.getpwuid(os.getuid()).pw_dir
return pwd.getpwnam(username).pw_dir
raise RuntimeError("Can't determine home directory "
_windows_flavour = _WindowsFlavour()
_posix_flavour = _PosixFlavour()
"""An accessor implements a particular (system-specific or not) way of
accessing paths on the filesystem."""
class _NormalAccessor(_Accessor):
if hasattr(os, "lchmod"):
def lchmod(self, pathobj, mode):
raise NotImplementedError("lchmod() not available on this system")
def link_to(self, target):
raise NotImplementedError("os.link() not available on this system")
def symlink(a, b, target_is_directory):
raise NotImplementedError("symlink() not available on this system")
# Under POSIX, os.symlink() takes two args
def symlink(a, b, target_is_directory):
def readlink(self, path):
return pwd.getpwuid(self.stat(path).st_uid).pw_name
raise NotImplementedError("Path.owner() is unsupported on this system")
return grp.getgrgid(self.stat(path).st_gid).gr_name
raise NotImplementedError("Path.group() is unsupported on this system")
_normal_accessor = _NormalAccessor()
def _make_selector(pattern_parts, flavour):
child_parts = pattern_parts[1:]
cls = _RecursiveWildcardSelector
raise ValueError("Invalid pattern: '**' can only be an entire path component")
elif _is_wildcard_pattern(pat):
return cls(pat, child_parts, flavour)
if hasattr(functools, "lru_cache"):
_make_selector = functools.lru_cache()(_make_selector)