import warnings
from _collections_abc import Sequence
from errno import 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
def __init__(self, path):
# We don't store the instance to avoid reference cycles
self._pathcls = type(path)
- self._drv = path._drv
- self._root = path._root
+ self._drv = path.drive
+ self._root = path.root
self._parts = path._parts
def __len__(self):
directly, regardless of your system.
"""
__slots__ = (
- '_drv', '_root', '_parts',
+ '_raw_path', '_drv', '_root', '_parts_cached',
'_str', '_hash', '_parts_tuple', '_parts_normcase_cached',
)
_flavour = os.path
- def __new__(cls, *args):
+ def __new__(cls, *args, **kwargs):
"""Construct a PurePath from one or several strings and or existing
PurePath objects. The strings and path objects are combined so as
to yield a canonicalized path, which is incorporated into the
"""
if cls is PurePath:
cls = PureWindowsPath if os.name == 'nt' else PurePosixPath
- return cls._from_parts(args)
+ return object.__new__(cls)
def __reduce__(self):
# Using the parts tuple helps share interned path parts
# when pickling related paths.
- return (self.__class__, tuple(self._parts))
+ return (self.__class__, self.parts)
- @classmethod
- def _parse_parts(cls, parts):
- if not parts:
- return '', '', []
- elif len(parts) == 1:
- path = os.fspath(parts[0])
+ def __init__(self, *args):
+ if not args:
+ path = ''
+ elif len(args) == 1:
+ path = os.fspath(args[0])
else:
- path = cls._flavour.join(*parts)
- sep = cls._flavour.sep
- altsep = cls._flavour.altsep
+ path = self._flavour.join(*args)
if isinstance(path, str):
# Force-cast str subclasses to str (issue #21127)
path = str(path)
"argument should be a str or an os.PathLike "
"object where __fspath__ returns a str, "
f"not {type(path).__name__!r}")
+ self._raw_path = path
+
+ @classmethod
+ def _parse_path(cls, path):
+ if not path:
+ return '', '', []
+ sep = cls._flavour.sep
+ altsep = cls._flavour.altsep
if altsep:
path = path.replace(altsep, sep)
drv, root, rel = cls._flavour.splitroot(path)
parsed = [sys.intern(x) for x in unfiltered_parsed if x and x != '.']
return drv, root, parsed
- @classmethod
- def _from_parts(cls, args):
- self = object.__new__(cls)
- drv, root, parts = self._parse_parts(args)
+ def _load_parts(self):
+ drv, root, parts = self._parse_path(self._raw_path)
self._drv = drv
self._root = root
- self._parts = parts
- return self
+ self._parts_cached = parts
@classmethod
def _from_parsed_parts(cls, drv, root, parts):
- self = object.__new__(cls)
+ path = cls._format_parsed_parts(drv, root, parts)
+ self = cls(path)
+ self._str = path or '.'
self._drv = drv
self._root = root
- self._parts = parts
+ self._parts_cached = parts
return self
@classmethod
try:
return self._str
except AttributeError:
- self._str = self._format_parsed_parts(self._drv, self._root,
+ self._str = self._format_parsed_parts(self.drive, self.root,
self._parts) or '.'
return self._str
if not self.is_absolute():
raise ValueError("relative path can't be expressed as a file URI")
- drive = self._drv
+ drive = self.drive
if len(drive) == 2 and drive[1] == ':':
# It's a path on a local drive => 'file:///c:/a/b'
prefix = 'file:///' + drive
return NotImplemented
return self._parts_normcase >= other._parts_normcase
- drive = property(attrgetter('_drv'),
- doc="""The drive prefix (letter or UNC path), if any.""")
+ @property
+ def drive(self):
+ """The drive prefix (letter or UNC path), if any."""
+ try:
+ return self._drv
+ except AttributeError:
+ self._load_parts()
+ return self._drv
+
+ @property
+ def root(self):
+ """The root of the path, if any."""
+ try:
+ return self._root
+ except AttributeError:
+ self._load_parts()
+ return self._root
- root = property(attrgetter('_root'),
- doc="""The root of the path, if any.""")
+ @property
+ def _parts(self):
+ try:
+ return self._parts_cached
+ except AttributeError:
+ self._load_parts()
+ return self._parts_cached
@property
def anchor(self):
"""The concatenation of the drive and root, or ''."""
- anchor = self._drv + self._root
+ anchor = self.drive + self.root
return anchor
@property
def name(self):
"""The final path component, if any."""
parts = self._parts
- if len(parts) == (1 if (self._drv or self._root) else 0):
+ if len(parts) == (1 if (self.drive or self.root) else 0):
return ''
return parts[-1]
drv, root, tail = f.splitroot(name)
if drv or root or not tail or f.sep in tail or (f.altsep and f.altsep in tail):
raise ValueError("Invalid name %r" % (name))
- return self._from_parsed_parts(self._drv, self._root,
+ return self._from_parsed_parts(self.drive, self.root,
self._parts[:-1] + [name])
def with_stem(self, stem):
name = name + suffix
else:
name = name[:-len(old_suffix)] + suffix
- return self._from_parsed_parts(self._drv, self._root,
+ return self._from_parsed_parts(self.drive, self.root,
self._parts[:-1] + [name])
def relative_to(self, other, /, *_deprecated, walk_up=False):
paths) or a totally different path (if one of the arguments is
anchored).
"""
- drv1, root1, parts1 = self._drv, self._root, self._parts
- drv2, root2, parts2 = self._parse_parts(args)
- if root2:
- if not drv2 and drv1:
- return self._from_parsed_parts(drv1, root2, [drv1 + root2] + parts2[1:])
- else:
- return self._from_parsed_parts(drv2, root2, parts2)
- elif drv2:
- if drv2 == drv1 or self._flavour.normcase(drv2) == self._flavour.normcase(drv1):
- # Same drive => second path is relative to the first.
- return self._from_parsed_parts(drv1, root1, parts1 + parts2[1:])
- else:
- return self._from_parsed_parts(drv2, root2, parts2)
- else:
- # Second path is non-anchored (common case).
- return self._from_parsed_parts(drv1, root1, parts1 + parts2)
+ return self.__class__(self._raw_path, *args)
def __truediv__(self, key):
try:
def __rtruediv__(self, key):
try:
- return self._from_parts([key] + self._parts)
+ return type(self)(key, self._raw_path)
except TypeError:
return NotImplemented
@property
def parent(self):
"""The logical parent of the path."""
- drv = self._drv
- root = self._root
+ drv = self.drive
+ root = self.root
parts = self._parts
if len(parts) == 1 and (drv or root):
return self
a drive)."""
# ntpath.isabs() is defective - see GH-44626 .
if self._flavour is ntpath:
- return bool(self._drv and self._root)
+ return bool(self.drive and self.root)
return self._flavour.isabs(self)
def is_reserved(self):
Return True if this path matches the given pattern.
"""
path_pattern = self._flavour.normcase(path_pattern)
- drv, root, pat_parts = self._parse_parts((path_pattern,))
+ drv, root, pat_parts = self._parse_path(path_pattern)
if not pat_parts:
raise ValueError("empty pattern")
parts = self._parts_normcase
"""
__slots__ = ()
- def __new__(cls, *args, **kwargs):
+ def __init__(self, *args, **kwargs):
if kwargs:
msg = ("support for supplying keyword arguments to pathlib.PurePath "
"is deprecated and scheduled for removal in Python {remove}")
warnings._deprecated("pathlib.PurePath(**kwargs)", msg, remove=(3, 14))
+ super().__init__(*args)
+
+ def __new__(cls, *args, **kwargs):
if cls is Path:
cls = WindowsPath if os.name == 'nt' else PosixPath
- return cls._from_parts(args)
+ return object.__new__(cls)
def _make_child_relpath(self, part):
# This is an optimization used for dir walking. `part` must be
# a single part relative to this path.
parts = self._parts + [part]
- return self._from_parsed_parts(self._drv, self._root, parts)
+ return self._from_parsed_parts(self.drive, self.root, parts)
def __enter__(self):
# In previous versions of pathlib, __exit__() marked this path as
sys.audit("pathlib.Path.glob", self, pattern)
if not pattern:
raise ValueError("Unacceptable pattern: {!r}".format(pattern))
- drv, root, pattern_parts = self._parse_parts((pattern,))
+ drv, root, pattern_parts = self._parse_path(pattern)
if drv or root:
raise NotImplementedError("Non-relative patterns are unsupported")
if pattern[-1] in (self._flavour.sep, self._flavour.altsep):
this subtree.
"""
sys.audit("pathlib.Path.rglob", self, pattern)
- drv, root, pattern_parts = self._parse_parts((pattern,))
+ drv, root, pattern_parts = self._parse_path(pattern)
if drv or root:
raise NotImplementedError("Non-relative patterns are unsupported")
if pattern and pattern[-1] in (self._flavour.sep, self._flavour.altsep):
"""
if self.is_absolute():
return self
- elif self._drv:
+ elif self.drive:
# There is a CWD on each drive-letter drive.
- cwd = self._flavour.abspath(self._drv)
+ cwd = self._flavour.abspath(self.drive)
else:
cwd = os.getcwd()
- return self._from_parts([cwd] + self._parts)
+ return type(self)(cwd, self._raw_path)
def resolve(self, strict=False):
"""
except OSError as e:
check_eloop(e)
raise
- p = self._from_parts((s,))
+ p = type(self)(s)
# In non-strict mode, realpath() doesn't raise on symlink loops.
# Ensure we get an exception by calling stat()
"""
if not hasattr(os, "readlink"):
raise NotImplementedError("os.readlink() not available on this system")
- return self._from_parts((os.readlink(self),))
+ return type(self)(os.readlink(self))
def touch(self, mode=0o666, exist_ok=True):
"""
""" Return a new path with expanded ~ and ~user constructs
(as returned by os.path.expanduser)
"""
- if (not (self._drv or self._root) and
+ if (not (self.drive or self.root) and
self._parts and self._parts[0][:1] == '~'):
homedir = self._flavour.expanduser(self._parts[0])
if homedir[:1] == "~":
raise RuntimeError("Could not determine home directory.")
- drv, root, parts = self._parse_parts((homedir,))
+ drv, root, parts = self._parse_path(homedir)
return self._from_parsed_parts(drv, root, parts + self._parts[1:])
return self