from collections import Sequence
from contextlib import contextmanager
from errno import EINVAL, ENOENT, ENOTDIR
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",
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')
set(chr(x) for x in range(ord('a'), ord('z') + 1)) |
set(chr(x) for x in range(ord('A'), ord('Z') + 1))
ext_namespace_prefix = '\\\\?\\'
{'CON', 'PRN', 'AUX', 'NUL'} |
{'COM%d' % i for i in range(1, 10)} |
{'LPT%d' % i for i in range(1, 10)}
# Interesting findings about extended paths:
# - '\\?\c:\a', '//?/c:\a' and '//?/c:/a' are all supported
# - extended paths are always absolute; "relative" extended paths will
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 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").
# 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
return parts[-1].partition('.')[0].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):
userhome = os.environ['HOME']
elif '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 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):
def _wrap_strfunc(strfunc):
@functools.wraps(strfunc)
def wrapped(pathobj, *args):
return strfunc(str(pathobj), *args)
return staticmethod(wrapped)
def _wrap_binary_strfunc(strfunc):
@functools.wraps(strfunc)
def wrapped(pathobjA, pathobjB, *args):
return strfunc(str(pathobjA), str(pathobjB), *args)
return staticmethod(wrapped)
stat = _wrap_strfunc(os.stat)
lstat = _wrap_strfunc(os.lstat)
open = _wrap_strfunc(os.open)
listdir = _wrap_strfunc(os.listdir)
scandir = _wrap_strfunc(os.scandir)
chmod = _wrap_strfunc(os.chmod)
if hasattr(os, "lchmod"):
lchmod = _wrap_strfunc(os.lchmod)
def lchmod(self, pathobj, mode):
raise NotImplementedError("lchmod() not available on this system")
mkdir = _wrap_strfunc(os.mkdir)
unlink = _wrap_strfunc(os.unlink)
rmdir = _wrap_strfunc(os.rmdir)
rename = _wrap_binary_strfunc(os.rename)
replace = _wrap_binary_strfunc(os.replace)
symlink = _wrap_binary_strfunc(os.symlink)
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):
return os.symlink(str(a), str(b))
utime = _wrap_strfunc(os.utime)
def readlink(self, path):
_normal_accessor = _NormalAccessor()
def _make_selector(pattern_parts):
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)
if hasattr(functools, "lru_cache"):
_make_selector = functools.lru_cache()(_make_selector)
"""A selector matches a specific glob pattern part against the children
def __init__(self, child_parts):
self.child_parts = child_parts
self.successor = _make_selector(child_parts)
self.successor = _TerminatingSelector()
def select_from(self, parent_path):
"""Iterate over all child paths of `parent_path` matched by this
selector. This can contain parent_path itself."""
path_cls = type(parent_path)
scandir = parent_path._accessor.scandir
if not is_dir(parent_path):
return self._select_from(parent_path, is_dir, exists, scandir)
class _TerminatingSelector:
def _select_from(self, parent_path, is_dir, exists, scandir):
class _PreciseSelector(_Selector):
def __init__(self, name, child_parts):