1import fnmatch 2import functools 3import io 4import ntpath 5import os 6import posixpath 7import re 8import sys 9from _collections_abc import Sequence 10from errno import EINVAL, ENOENT, ENOTDIR, EBADF, ELOOP 11from operator import attrgetter 12from stat import S_ISDIR, S_ISLNK, S_ISREG, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO 13from urllib.parse import quote_from_bytes as urlquote_from_bytes 14 15 16supports_symlinks = True 17if os.name == 'nt': 18 import nt 19 if sys.getwindowsversion()[:2] >= (6, 0): 20 from nt import _getfinalpathname 21 else: 22 supports_symlinks = False 23 _getfinalpathname = None 24else: 25 nt = None 26 27 28__all__ = [ 29 "PurePath", "PurePosixPath", "PureWindowsPath", 30 "Path", "PosixPath", "WindowsPath", 31 ] 32 33# 34# Internals 35# 36 37# EBADF - guard against macOS `stat` throwing EBADF 38_IGNORED_ERROS = (ENOENT, ENOTDIR, EBADF, ELOOP) 39 40_IGNORED_WINERRORS = ( 41 21, # ERROR_NOT_READY - drive exists but is not accessible 42 123, # ERROR_INVALID_NAME - fix for bpo-35306 43 1921, # ERROR_CANT_RESOLVE_FILENAME - fix for broken symlink pointing to itself 44) 45 46def _ignore_error(exception): 47 return (getattr(exception, 'errno', None) in _IGNORED_ERROS or 48 getattr(exception, 'winerror', None) in _IGNORED_WINERRORS) 49 50 51def _is_wildcard_pattern(pat): 52 # Whether this pattern needs actual matching using fnmatch, or can 53 # be looked up directly as a file. 54 return "*" in pat or "?" in pat or "[" in pat 55 56 57class _Flavour(object): 58 """A flavour implements a particular (platform-specific) set of path 59 semantics.""" 60 61 def __init__(self): 62 self.join = self.sep.join 63 64 def parse_parts(self, parts): 65 parsed = [] 66 sep = self.sep 67 altsep = self.altsep 68 drv = root = '' 69 it = reversed(parts) 70 for part in it: 71 if not part: 72 continue 73 if altsep: 74 part = part.replace(altsep, sep) 75 drv, root, rel = self.splitroot(part) 76 if sep in rel: 77 for x in reversed(rel.split(sep)): 78 if x and x != '.': 79 parsed.append(sys.intern(x)) 80 else: 81 if rel and rel != '.': 82 parsed.append(sys.intern(rel)) 83 if drv or root: 84 if not drv: 85 # If no drive is present, try to find one in the previous 86 # parts. This makes the result of parsing e.g. 87 # ("C:", "/", "a") reasonably intuitive. 88 for part in it: 89 if not part: 90 continue 91 if altsep: 92 part = part.replace(altsep, sep) 93 drv = self.splitroot(part)[0] 94 if drv: 95 break 96 break 97 if drv or root: 98 parsed.append(drv + root) 99 parsed.reverse() 100 return drv, root, parsed 101 102 def join_parsed_parts(self, drv, root, parts, drv2, root2, parts2): 103 """ 104 Join the two paths represented by the respective 105 (drive, root, parts) tuples. Return a new (drive, root, parts) tuple. 106 """ 107 if root2: 108 if not drv2 and drv: 109 return drv, root2, [drv + root2] + parts2[1:] 110 elif drv2: 111 if drv2 == drv or self.casefold(drv2) == self.casefold(drv): 112 # Same drive => second path is relative to the first 113 return drv, root, parts + parts2[1:] 114 else: 115 # Second path is non-anchored (common case) 116 return drv, root, parts + parts2 117 return drv2, root2, parts2 118 119 120class _WindowsFlavour(_Flavour): 121 # Reference for Windows paths can be found at 122 # http://msdn.microsoft.com/en-us/library/aa365247%28v=vs.85%29.aspx 123 124 sep = '\\' 125 altsep = '/' 126 has_drv = True 127 pathmod = ntpath 128 129 is_supported = (os.name == 'nt') 130 131 drive_letters = set('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ') 132 ext_namespace_prefix = '\\\\?\\' 133 134 reserved_names = ( 135 {'CON', 'PRN', 'AUX', 'NUL'} | 136 {'COM%d' % i for i in range(1, 10)} | 137 {'LPT%d' % i for i in range(1, 10)} 138 ) 139 140 # Interesting findings about extended paths: 141 # - '\\?\c:\a', '//?/c:\a' and '//?/c:/a' are all supported 142 # but '\\?\c:/a' is not 143 # - extended paths are always absolute; "relative" extended paths will 144 # fail. 145 146 def splitroot(self, part, sep=sep): 147 first = part[0:1] 148 second = part[1:2] 149 if (second == sep and first == sep): 150 # XXX extended paths should also disable the collapsing of "." 151 # components (according to MSDN docs). 152 prefix, part = self._split_extended_path(part) 153 first = part[0:1] 154 second = part[1:2] 155 else: 156 prefix = '' 157 third = part[2:3] 158 if (second == sep and first == sep and third != sep): 159 # is a UNC path: 160 # vvvvvvvvvvvvvvvvvvvvv root 161 # \\machine\mountpoint\directory\etc\... 162 # directory ^^^^^^^^^^^^^^ 163 index = part.find(sep, 2) 164 if index != -1: 165 index2 = part.find(sep, index + 1) 166 # a UNC path can't have two slashes in a row 167 # (after the initial two) 168 if index2 != index + 1: 169 if index2 == -1: 170 index2 = len(part) 171 if prefix: 172 return prefix + part[1:index2], sep, part[index2+1:] 173 else: 174 return part[:index2], sep, part[index2+1:] 175 drv = root = '' 176 if second == ':' and first in self.drive_letters: 177 drv = part[:2] 178 part = part[2:] 179 first = third 180 if first == sep: 181 root = first 182 part = part.lstrip(sep) 183 return prefix + drv, root, part 184 185 def casefold(self, s): 186 return s.lower() 187 188 def casefold_parts(self, parts): 189 return [p.lower() for p in parts] 190 191 def compile_pattern(self, pattern): 192 return re.compile(fnmatch.translate(pattern), re.IGNORECASE).fullmatch 193 194 def resolve(self, path, strict=False): 195 s = str(path) 196 if not s: 197 return os.getcwd() 198 previous_s = None 199 if _getfinalpathname is not None: 200 if strict: 201 return self._ext_to_normal(_getfinalpathname(s)) 202 else: 203 tail_parts = [] # End of the path after the first one not found 204 while True: 205 try: 206 s = self._ext_to_normal(_getfinalpathname(s)) 207 except FileNotFoundError: 208 previous_s = s 209 s, tail = os.path.split(s) 210 tail_parts.append(tail) 211 if previous_s == s: 212 return path 213 else: 214 return os.path.join(s, *reversed(tail_parts)) 215 # Means fallback on absolute 216 return None 217 218 def _split_extended_path(self, s, ext_prefix=ext_namespace_prefix): 219 prefix = '' 220 if s.startswith(ext_prefix): 221 prefix = s[:4] 222 s = s[4:] 223 if s.startswith('UNC\\'): 224 prefix += s[:3] 225 s = '\\' + s[3:] 226 return prefix, s 227 228 def _ext_to_normal(self, s): 229 # Turn back an extended path into a normal DOS-like path 230 return self._split_extended_path(s)[1] 231 232 def is_reserved(self, parts): 233 # NOTE: the rules for reserved names seem somewhat complicated 234 # (e.g. r"..\NUL" is reserved but not r"foo\NUL"). 235 # We err on the side of caution and return True for paths which are 236 # not considered reserved by Windows. 237 if not parts: 238 return False 239 if parts[0].startswith('\\\\'): 240 # UNC paths are never reserved 241 return False 242 return parts[-1].partition('.')[0].upper() in self.reserved_names 243 244 def make_uri(self, path): 245 # Under Windows, file URIs use the UTF-8 encoding. 246 drive = path.drive 247 if len(drive) == 2 and drive[1] == ':': 248 # It's a path on a local drive => 'file:///c:/a/b' 249 rest = path.as_posix()[2:].lstrip('/') 250 return 'file:///%s/%s' % ( 251 drive, urlquote_from_bytes(rest.encode('utf-8'))) 252 else: 253 # It's a path on a network drive => 'file://host/share/a/b' 254 return 'file:' + urlquote_from_bytes(path.as_posix().encode('utf-8')) 255 256 def gethomedir(self, username): 257 if 'USERPROFILE' in os.environ: 258 userhome = os.environ['USERPROFILE'] 259 elif 'HOMEPATH' in os.environ: 260 try: 261 drv = os.environ['HOMEDRIVE'] 262 except KeyError: 263 drv = '' 264 userhome = drv + os.environ['HOMEPATH'] 265 else: 266 raise RuntimeError("Can't determine home directory") 267 268 if username: 269 # Try to guess user home directory. By default all users 270 # directories are located in the same place and are named by 271 # corresponding usernames. If current user home directory points 272 # to nonstandard place, this guess is likely wrong. 273 if os.environ['USERNAME'] != username: 274 drv, root, parts = self.parse_parts((userhome,)) 275 if parts[-1] != os.environ['USERNAME']: 276 raise RuntimeError("Can't determine home directory " 277 "for %r" % username) 278 parts[-1] = username 279 if drv or root: 280 userhome = drv + root + self.join(parts[1:]) 281 else: 282 userhome = self.join(parts) 283 return userhome 284 285class _PosixFlavour(_Flavour): 286 sep = '/' 287 altsep = '' 288 has_drv = False 289 pathmod = posixpath 290 291 is_supported = (os.name != 'nt') 292 293 def splitroot(self, part, sep=sep): 294 if part and part[0] == sep: 295 stripped_part = part.lstrip(sep) 296 # According to POSIX path resolution: 297 # http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap04.html#tag_04_11 298 # "A pathname that begins with two successive slashes may be 299 # interpreted in an implementation-defined manner, although more 300 # than two leading slashes shall be treated as a single slash". 301 if len(part) - len(stripped_part) == 2: 302 return '', sep * 2, stripped_part 303 else: 304 return '', sep, stripped_part 305 else: 306 return '', '', part 307 308 def casefold(self, s): 309 return s 310 311 def casefold_parts(self, parts): 312 return parts 313 314 def compile_pattern(self, pattern): 315 return re.compile(fnmatch.translate(pattern)).fullmatch 316 317 def resolve(self, path, strict=False): 318 sep = self.sep 319 accessor = path._accessor 320 seen = {} 321 def _resolve(path, rest): 322 if rest.startswith(sep): 323 path = '' 324 325 for name in rest.split(sep): 326 if not name or name == '.': 327 # current dir 328 continue 329 if name == '..': 330 # parent dir 331 path, _, _ = path.rpartition(sep) 332 continue 333 if path.endswith(sep): 334 newpath = path + name 335 else: 336 newpath = path + sep + name 337 if newpath in seen: 338 # Already seen this path 339 path = seen[newpath] 340 if path is not None: 341 # use cached value 342 continue 343 # The symlink is not resolved, so we must have a symlink loop. 344 raise RuntimeError("Symlink loop from %r" % newpath) 345 # Resolve the symbolic link 346 try: 347 target = accessor.readlink(newpath) 348 except OSError as e: 349 if e.errno != EINVAL and strict: 350 raise 351 # Not a symlink, or non-strict mode. We just leave the path 352 # untouched. 353 path = newpath 354 else: 355 seen[newpath] = None # not resolved symlink 356 path = _resolve(path, target) 357 seen[newpath] = path # resolved symlink 358 359 return path 360 # NOTE: according to POSIX, getcwd() cannot contain path components 361 # which are symlinks. 362 base = '' if path.is_absolute() else os.getcwd() 363 return _resolve(base, str(path)) or sep 364 365 def is_reserved(self, parts): 366 return False 367 368 def make_uri(self, path): 369 # We represent the path using the local filesystem encoding, 370 # for portability to other applications. 371 bpath = bytes(path) 372 return 'file://' + urlquote_from_bytes(bpath) 373 374 def gethomedir(self, username): 375 if not username: 376 try: 377 return os.environ['HOME'] 378 except KeyError: 379 import pwd 380 return pwd.getpwuid(os.getuid()).pw_dir 381 else: 382 import pwd 383 try: 384 return pwd.getpwnam(username).pw_dir 385 except KeyError: 386 raise RuntimeError("Can't determine home directory " 387 "for %r" % username) 388 389 390_windows_flavour = _WindowsFlavour() 391_posix_flavour = _PosixFlavour() 392 393 394class _Accessor: 395 """An accessor implements a particular (system-specific or not) way of 396 accessing paths on the filesystem.""" 397 398 399class _NormalAccessor(_Accessor): 400 401 stat = os.stat 402 403 lstat = os.lstat 404 405 open = os.open 406 407 listdir = os.listdir 408 409 scandir = os.scandir 410 411 chmod = os.chmod 412 413 if hasattr(os, "lchmod"): 414 lchmod = os.lchmod 415 else: 416 def lchmod(self, pathobj, mode): 417 raise NotImplementedError("lchmod() not available on this system") 418 419 mkdir = os.mkdir 420 421 unlink = os.unlink 422 423 if hasattr(os, "link"): 424 link_to = os.link 425 else: 426 @staticmethod 427 def link_to(self, target): 428 raise NotImplementedError("os.link() not available on this system") 429 430 rmdir = os.rmdir 431 432 rename = os.rename 433 434 replace = os.replace 435 436 if nt: 437 if supports_symlinks: 438 symlink = os.symlink 439 else: 440 def symlink(a, b, target_is_directory): 441 raise NotImplementedError("symlink() not available on this system") 442 else: 443 # Under POSIX, os.symlink() takes two args 444 @staticmethod 445 def symlink(a, b, target_is_directory): 446 return os.symlink(a, b) 447 448 utime = os.utime 449 450 # Helper for resolve() 451 def readlink(self, path): 452 return os.readlink(path) 453 454 455_normal_accessor = _NormalAccessor() 456 457 458# 459# Globbing helpers 460# 461 462def _make_selector(pattern_parts, flavour): 463 pat = pattern_parts[0] 464 child_parts = pattern_parts[1:] 465 if pat == '**': 466 cls = _RecursiveWildcardSelector 467 elif '**' in pat: 468 raise ValueError("Invalid pattern: '**' can only be an entire path component") 469 elif _is_wildcard_pattern(pat): 470 cls = _WildcardSelector 471 else: 472 cls = _PreciseSelector 473 return cls(pat, child_parts, flavour) 474 475if hasattr(functools, "lru_cache"): 476 _make_selector = functools.lru_cache()(_make_selector) 477 478 479class _Selector: 480 """A selector matches a specific glob pattern part against the children 481 of a given path.""" 482 483 def __init__(self, child_parts, flavour): 484 self.child_parts = child_parts 485 if child_parts: 486 self.successor = _make_selector(child_parts, flavour) 487 self.dironly = True 488 else: 489 self.successor = _TerminatingSelector() 490 self.dironly = False 491 492 def select_from(self, parent_path): 493 """Iterate over all child paths of `parent_path` matched by this 494 selector. This can contain parent_path itself.""" 495 path_cls = type(parent_path) 496 is_dir = path_cls.is_dir 497 exists = path_cls.exists 498 scandir = parent_path._accessor.scandir 499 if not is_dir(parent_path): 500 return iter([]) 501 return self._select_from(parent_path, is_dir, exists, scandir) 502 503 504class _TerminatingSelector: 505 506 def _select_from(self, parent_path, is_dir, exists, scandir): 507 yield parent_path 508 509 510class _PreciseSelector(_Selector): 511 512 def __init__(self, name, child_parts, flavour): 513 self.name = name 514 _Selector.__init__(self, child_parts, flavour) 515 516 def _select_from(self, parent_path, is_dir, exists, scandir): 517 try: 518 path = parent_path._make_child_relpath(self.name) 519 if (is_dir if self.dironly else exists)(path): 520 for p in self.successor._select_from(path, is_dir, exists, scandir): 521 yield p 522 except PermissionError: 523 return 524 525 526class _WildcardSelector(_Selector): 527 528 def __init__(self, pat, child_parts, flavour): 529 self.match = flavour.compile_pattern(pat) 530 _Selector.__init__(self, child_parts, flavour) 531 532 def _select_from(self, parent_path, is_dir, exists, scandir): 533 try: 534 with scandir(parent_path) as scandir_it: 535 entries = list(scandir_it) 536 for entry in entries: 537 if self.dironly: 538 try: 539 # "entry.is_dir()" can raise PermissionError 540 # in some cases (see bpo-38894), which is not 541 # among the errors ignored by _ignore_error() 542 if not entry.is_dir(): 543 continue 544 except OSError as e: 545 if not _ignore_error(e): 546 raise 547 continue 548 name = entry.name 549 if self.match(name): 550 path = parent_path._make_child_relpath(name) 551 for p in self.successor._select_from(path, is_dir, exists, scandir): 552 yield p 553 except PermissionError: 554 return 555 556 557class _RecursiveWildcardSelector(_Selector): 558 559 def __init__(self, pat, child_parts, flavour): 560 _Selector.__init__(self, child_parts, flavour) 561 562 def _iterate_directories(self, parent_path, is_dir, scandir): 563 yield parent_path 564 try: 565 with scandir(parent_path) as scandir_it: 566 entries = list(scandir_it) 567 for entry in entries: 568 entry_is_dir = False 569 try: 570 entry_is_dir = entry.is_dir() 571 except OSError as e: 572 if not _ignore_error(e): 573 raise 574 if entry_is_dir and not entry.is_symlink(): 575 path = parent_path._make_child_relpath(entry.name) 576 for p in self._iterate_directories(path, is_dir, scandir): 577 yield p 578 except PermissionError: 579 return 580 581 def _select_from(self, parent_path, is_dir, exists, scandir): 582 try: 583 yielded = set() 584 try: 585 successor_select = self.successor._select_from 586 for starting_point in self._iterate_directories(parent_path, is_dir, scandir): 587 for p in successor_select(starting_point, is_dir, exists, scandir): 588 if p not in yielded: 589 yield p 590 yielded.add(p) 591 finally: 592 yielded.clear() 593 except PermissionError: 594 return 595 596 597# 598# Public API 599# 600 601class _PathParents(Sequence): 602 """This object provides sequence-like access to the logical ancestors 603 of a path. Don't try to construct it yourself.""" 604 __slots__ = ('_pathcls', '_drv', '_root', '_parts') 605 606 def __init__(self, path): 607 # We don't store the instance to avoid reference cycles 608 self._pathcls = type(path) 609 self._drv = path._drv 610 self._root = path._root 611 self._parts = path._parts 612 613 def __len__(self): 614 if self._drv or self._root: 615 return len(self._parts) - 1 616 else: 617 return len(self._parts) 618 619 def __getitem__(self, idx): 620 if idx < 0 or idx >= len(self): 621 raise IndexError(idx) 622 return self._pathcls._from_parsed_parts(self._drv, self._root, 623 self._parts[:-idx - 1]) 624 625 def __repr__(self): 626 return "<{}.parents>".format(self._pathcls.__name__) 627 628 629class PurePath(object): 630 """Base class for manipulating paths without I/O. 631 632 PurePath represents a filesystem path and offers operations which 633 don't imply any actual filesystem I/O. Depending on your system, 634 instantiating a PurePath will return either a PurePosixPath or a 635 PureWindowsPath object. You can also instantiate either of these classes 636 directly, regardless of your system. 637 """ 638 __slots__ = ( 639 '_drv', '_root', '_parts', 640 '_str', '_hash', '_pparts', '_cached_cparts', 641 ) 642 643 def __new__(cls, *args): 644 """Construct a PurePath from one or several strings and or existing 645 PurePath objects. The strings and path objects are combined so as 646 to yield a canonicalized path, which is incorporated into the 647 new PurePath object. 648 """ 649 if cls is PurePath: 650 cls = PureWindowsPath if os.name == 'nt' else PurePosixPath 651 return cls._from_parts(args) 652 653 def __reduce__(self): 654 # Using the parts tuple helps share interned path parts 655 # when pickling related paths. 656 return (self.__class__, tuple(self._parts)) 657 658 @classmethod 659 def _parse_args(cls, args): 660 # This is useful when you don't want to create an instance, just 661 # canonicalize some constructor arguments. 662 parts = [] 663 for a in args: 664 if isinstance(a, PurePath): 665 parts += a._parts 666 else: 667 a = os.fspath(a) 668 if isinstance(a, str): 669 # Force-cast str subclasses to str (issue #21127) 670 parts.append(str(a)) 671 else: 672 raise TypeError( 673 "argument should be a str object or an os.PathLike " 674 "object returning str, not %r" 675 % type(a)) 676 return cls._flavour.parse_parts(parts) 677 678 @classmethod 679 def _from_parts(cls, args, init=True): 680 # We need to call _parse_args on the instance, so as to get the 681 # right flavour. 682 self = object.__new__(cls) 683 drv, root, parts = self._parse_args(args) 684 self._drv = drv 685 self._root = root 686 self._parts = parts 687 if init: 688 self._init() 689 return self 690 691 @classmethod 692 def _from_parsed_parts(cls, drv, root, parts, init=True): 693 self = object.__new__(cls) 694 self._drv = drv 695 self._root = root 696 self._parts = parts 697 if init: 698 self._init() 699 return self 700 701 @classmethod 702 def _format_parsed_parts(cls, drv, root, parts): 703 if drv or root: 704 return drv + root + cls._flavour.join(parts[1:]) 705 else: 706 return cls._flavour.join(parts) 707 708 def _init(self): 709 # Overridden in concrete Path 710 pass 711 712 def _make_child(self, args): 713 drv, root, parts = self._parse_args(args) 714 drv, root, parts = self._flavour.join_parsed_parts( 715 self._drv, self._root, self._parts, drv, root, parts) 716 return self._from_parsed_parts(drv, root, parts) 717 718 def __str__(self): 719 """Return the string representation of the path, suitable for 720 passing to system calls.""" 721 try: 722 return self._str 723 except AttributeError: 724 self._str = self._format_parsed_parts(self._drv, self._root, 725 self._parts) or '.' 726 return self._str 727 728 def __fspath__(self): 729 return str(self) 730 731 def as_posix(self): 732 """Return the string representation of the path with forward (/) 733 slashes.""" 734 f = self._flavour 735 return str(self).replace(f.sep, '/') 736 737 def __bytes__(self): 738 """Return the bytes representation of the path. This is only 739 recommended to use under Unix.""" 740 return os.fsencode(self) 741 742 def __repr__(self): 743 return "{}({!r})".format(self.__class__.__name__, self.as_posix()) 744 745 def as_uri(self): 746 """Return the path as a 'file' URI.""" 747 if not self.is_absolute(): 748 raise ValueError("relative path can't be expressed as a file URI") 749 return self._flavour.make_uri(self) 750 751 @property 752 def _cparts(self): 753 # Cached casefolded parts, for hashing and comparison 754 try: 755 return self._cached_cparts 756 except AttributeError: 757 self._cached_cparts = self._flavour.casefold_parts(self._parts) 758 return self._cached_cparts 759 760 def __eq__(self, other): 761 if not isinstance(other, PurePath): 762 return NotImplemented 763 return self._cparts == other._cparts and self._flavour is other._flavour 764 765 def __hash__(self): 766 try: 767 return self._hash 768 except AttributeError: 769 self._hash = hash(tuple(self._cparts)) 770 return self._hash 771 772 def __lt__(self, other): 773 if not isinstance(other, PurePath) or self._flavour is not other._flavour: 774 return NotImplemented 775 return self._cparts < other._cparts 776 777 def __le__(self, other): 778 if not isinstance(other, PurePath) or self._flavour is not other._flavour: 779 return NotImplemented 780 return self._cparts <= other._cparts 781 782 def __gt__(self, other): 783 if not isinstance(other, PurePath) or self._flavour is not other._flavour: 784 return NotImplemented 785 return self._cparts > other._cparts 786 787 def __ge__(self, other): 788 if not isinstance(other, PurePath) or self._flavour is not other._flavour: 789 return NotImplemented 790 return self._cparts >= other._cparts 791 792 drive = property(attrgetter('_drv'), 793 doc="""The drive prefix (letter or UNC path), if any.""") 794 795 root = property(attrgetter('_root'), 796 doc="""The root of the path, if any.""") 797 798 @property 799 def anchor(self): 800 """The concatenation of the drive and root, or ''.""" 801 anchor = self._drv + self._root 802 return anchor 803 804 @property 805 def name(self): 806 """The final path component, if any.""" 807 parts = self._parts 808 if len(parts) == (1 if (self._drv or self._root) else 0): 809 return '' 810 return parts[-1] 811 812 @property 813 def suffix(self): 814 """ 815 The final component's last suffix, if any. 816 817 This includes the leading period. For example: '.txt' 818 """ 819 name = self.name 820 i = name.rfind('.') 821 if 0 < i < len(name) - 1: 822 return name[i:] 823 else: 824 return '' 825 826 @property 827 def suffixes(self): 828 """ 829 A list of the final component's suffixes, if any. 830 831 These include the leading periods. For example: ['.tar', '.gz'] 832 """ 833 name = self.name 834 if name.endswith('.'): 835 return [] 836 name = name.lstrip('.') 837 return ['.' + suffix for suffix in name.split('.')[1:]] 838 839 @property 840 def stem(self): 841 """The final path component, minus its last suffix.""" 842 name = self.name 843 i = name.rfind('.') 844 if 0 < i < len(name) - 1: 845 return name[:i] 846 else: 847 return name 848 849 def with_name(self, name): 850 """Return a new path with the file name changed.""" 851 if not self.name: 852 raise ValueError("%r has an empty name" % (self,)) 853 drv, root, parts = self._flavour.parse_parts((name,)) 854 if (not name or name[-1] in [self._flavour.sep, self._flavour.altsep] 855 or drv or root or len(parts) != 1): 856 raise ValueError("Invalid name %r" % (name)) 857 return self._from_parsed_parts(self._drv, self._root, 858 self._parts[:-1] + [name]) 859 860 def with_suffix(self, suffix): 861 """Return a new path with the file suffix changed. If the path 862 has no suffix, add given suffix. If the given suffix is an empty 863 string, remove the suffix from the path. 864 """ 865 f = self._flavour 866 if f.sep in suffix or f.altsep and f.altsep in suffix: 867 raise ValueError("Invalid suffix %r" % (suffix,)) 868 if suffix and not suffix.startswith('.') or suffix == '.': 869 raise ValueError("Invalid suffix %r" % (suffix)) 870 name = self.name 871 if not name: 872 raise ValueError("%r has an empty name" % (self,)) 873 old_suffix = self.suffix 874 if not old_suffix: 875 name = name + suffix 876 else: 877 name = name[:-len(old_suffix)] + suffix 878 return self._from_parsed_parts(self._drv, self._root, 879 self._parts[:-1] + [name]) 880 881 def relative_to(self, *other): 882 """Return the relative path to another path identified by the passed 883 arguments. If the operation is not possible (because this is not 884 a subpath of the other path), raise ValueError. 885 """ 886 # For the purpose of this method, drive and root are considered 887 # separate parts, i.e.: 888 # Path('c:/').relative_to('c:') gives Path('/') 889 # Path('c:/').relative_to('/') raise ValueError 890 if not other: 891 raise TypeError("need at least one argument") 892 parts = self._parts 893 drv = self._drv 894 root = self._root 895 if root: 896 abs_parts = [drv, root] + parts[1:] 897 else: 898 abs_parts = parts 899 to_drv, to_root, to_parts = self._parse_args(other) 900 if to_root: 901 to_abs_parts = [to_drv, to_root] + to_parts[1:] 902 else: 903 to_abs_parts = to_parts 904 n = len(to_abs_parts) 905 cf = self._flavour.casefold_parts 906 if (root or drv) if n == 0 else cf(abs_parts[:n]) != cf(to_abs_parts): 907 formatted = self._format_parsed_parts(to_drv, to_root, to_parts) 908 raise ValueError("{!r} does not start with {!r}" 909 .format(str(self), str(formatted))) 910 return self._from_parsed_parts('', root if n == 1 else '', 911 abs_parts[n:]) 912 913 @property 914 def parts(self): 915 """An object providing sequence-like access to the 916 components in the filesystem path.""" 917 # We cache the tuple to avoid building a new one each time .parts 918 # is accessed. XXX is this necessary? 919 try: 920 return self._pparts 921 except AttributeError: 922 self._pparts = tuple(self._parts) 923 return self._pparts 924 925 def joinpath(self, *args): 926 """Combine this path with one or several arguments, and return a 927 new path representing either a subpath (if all arguments are relative 928 paths) or a totally different path (if one of the arguments is 929 anchored). 930 """ 931 return self._make_child(args) 932 933 def __truediv__(self, key): 934 try: 935 return self._make_child((key,)) 936 except TypeError: 937 return NotImplemented 938 939 def __rtruediv__(self, key): 940 try: 941 return self._from_parts([key] + self._parts) 942 except TypeError: 943 return NotImplemented 944 945 @property 946 def parent(self): 947 """The logical parent of the path.""" 948 drv = self._drv 949 root = self._root 950 parts = self._parts 951 if len(parts) == 1 and (drv or root): 952 return self 953 return self._from_parsed_parts(drv, root, parts[:-1]) 954 955 @property 956 def parents(self): 957 """A sequence of this path's logical parents.""" 958 return _PathParents(self) 959 960 def is_absolute(self): 961 """True if the path is absolute (has both a root and, if applicable, 962 a drive).""" 963 if not self._root: 964 return False 965 return not self._flavour.has_drv or bool(self._drv) 966 967 def is_reserved(self): 968 """Return True if the path contains one of the special names reserved 969 by the system, if any.""" 970 return self._flavour.is_reserved(self._parts) 971 972 def match(self, path_pattern): 973 """ 974 Return True if this path matches the given pattern. 975 """ 976 cf = self._flavour.casefold 977 path_pattern = cf(path_pattern) 978 drv, root, pat_parts = self._flavour.parse_parts((path_pattern,)) 979 if not pat_parts: 980 raise ValueError("empty pattern") 981 if drv and drv != cf(self._drv): 982 return False 983 if root and root != cf(self._root): 984 return False 985 parts = self._cparts 986 if drv or root: 987 if len(pat_parts) != len(parts): 988 return False 989 pat_parts = pat_parts[1:] 990 elif len(pat_parts) > len(parts): 991 return False 992 for part, pat in zip(reversed(parts), reversed(pat_parts)): 993 if not fnmatch.fnmatchcase(part, pat): 994 return False 995 return True 996 997# Can't subclass os.PathLike from PurePath and keep the constructor 998# optimizations in PurePath._parse_args(). 999os.PathLike.register(PurePath) 1000 1001 1002class PurePosixPath(PurePath): 1003 """PurePath subclass for non-Windows systems. 1004 1005 On a POSIX system, instantiating a PurePath should return this object. 1006 However, you can also instantiate it directly on any system. 1007 """ 1008 _flavour = _posix_flavour 1009 __slots__ = () 1010 1011 1012class PureWindowsPath(PurePath): 1013 """PurePath subclass for Windows systems. 1014 1015 On a Windows system, instantiating a PurePath should return this object. 1016 However, you can also instantiate it directly on any system. 1017 """ 1018 _flavour = _windows_flavour 1019 __slots__ = () 1020 1021 1022# Filesystem-accessing classes 1023 1024 1025class Path(PurePath): 1026 """PurePath subclass that can make system calls. 1027 1028 Path represents a filesystem path but unlike PurePath, also offers 1029 methods to do system calls on path objects. Depending on your system, 1030 instantiating a Path will return either a PosixPath or a WindowsPath 1031 object. You can also instantiate a PosixPath or WindowsPath directly, 1032 but cannot instantiate a WindowsPath on a POSIX system or vice versa. 1033 """ 1034 __slots__ = ( 1035 '_accessor', 1036 '_closed', 1037 ) 1038 1039 def __new__(cls, *args, **kwargs): 1040 if cls is Path: 1041 cls = WindowsPath if os.name == 'nt' else PosixPath 1042 self = cls._from_parts(args, init=False) 1043 if not self._flavour.is_supported: 1044 raise NotImplementedError("cannot instantiate %r on your system" 1045 % (cls.__name__,)) 1046 self._init() 1047 return self 1048 1049 def _init(self, 1050 # Private non-constructor arguments 1051 template=None, 1052 ): 1053 self._closed = False 1054 if template is not None: 1055 self._accessor = template._accessor 1056 else: 1057 self._accessor = _normal_accessor 1058 1059 def _make_child_relpath(self, part): 1060 # This is an optimization used for dir walking. `part` must be 1061 # a single part relative to this path. 1062 parts = self._parts + [part] 1063 return self._from_parsed_parts(self._drv, self._root, parts) 1064 1065 def __enter__(self): 1066 if self._closed: 1067 self._raise_closed() 1068 return self 1069 1070 def __exit__(self, t, v, tb): 1071 self._closed = True 1072 1073 def _raise_closed(self): 1074 raise ValueError("I/O operation on closed path") 1075 1076 def _opener(self, name, flags, mode=0o666): 1077 # A stub for the opener argument to built-in open() 1078 return self._accessor.open(self, flags, mode) 1079 1080 def _raw_open(self, flags, mode=0o777): 1081 """ 1082 Open the file pointed by this path and return a file descriptor, 1083 as os.open() does. 1084 """ 1085 if self._closed: 1086 self._raise_closed() 1087 return self._accessor.open(self, flags, mode) 1088 1089 # Public API 1090 1091 @classmethod 1092 def cwd(cls): 1093 """Return a new path pointing to the current working directory 1094 (as returned by os.getcwd()). 1095 """ 1096 return cls(os.getcwd()) 1097 1098 @classmethod 1099 def home(cls): 1100 """Return a new path pointing to the user's home directory (as 1101 returned by os.path.expanduser('~')). 1102 """ 1103 return cls(cls()._flavour.gethomedir(None)) 1104 1105 def samefile(self, other_path): 1106 """Return whether other_path is the same or not as this file 1107 (as returned by os.path.samefile()). 1108 """ 1109 st = self.stat() 1110 try: 1111 other_st = other_path.stat() 1112 except AttributeError: 1113 other_st = os.stat(other_path) 1114 return os.path.samestat(st, other_st) 1115 1116 def iterdir(self): 1117 """Iterate over the files in this directory. Does not yield any 1118 result for the special paths '.' and '..'. 1119 """ 1120 if self._closed: 1121 self._raise_closed() 1122 for name in self._accessor.listdir(self): 1123 if name in {'.', '..'}: 1124 # Yielding a path object for these makes little sense 1125 continue 1126 yield self._make_child_relpath(name) 1127 if self._closed: 1128 self._raise_closed() 1129 1130 def glob(self, pattern): 1131 """Iterate over this subtree and yield all existing files (of any 1132 kind, including directories) matching the given relative pattern. 1133 """ 1134 if not pattern: 1135 raise ValueError("Unacceptable pattern: {!r}".format(pattern)) 1136 drv, root, pattern_parts = self._flavour.parse_parts((pattern,)) 1137 if drv or root: 1138 raise NotImplementedError("Non-relative patterns are unsupported") 1139 selector = _make_selector(tuple(pattern_parts), self._flavour) 1140 for p in selector.select_from(self): 1141 yield p 1142 1143 def rglob(self, pattern): 1144 """Recursively yield all existing files (of any kind, including 1145 directories) matching the given relative pattern, anywhere in 1146 this subtree. 1147 """ 1148 drv, root, pattern_parts = self._flavour.parse_parts((pattern,)) 1149 if drv or root: 1150 raise NotImplementedError("Non-relative patterns are unsupported") 1151 selector = _make_selector(("**",) + tuple(pattern_parts), self._flavour) 1152 for p in selector.select_from(self): 1153 yield p 1154 1155 def absolute(self): 1156 """Return an absolute version of this path. This function works 1157 even if the path doesn't point to anything. 1158 1159 No normalization is done, i.e. all '.' and '..' will be kept along. 1160 Use resolve() to get the canonical path to a file. 1161 """ 1162 # XXX untested yet! 1163 if self._closed: 1164 self._raise_closed() 1165 if self.is_absolute(): 1166 return self 1167 # FIXME this must defer to the specific flavour (and, under Windows, 1168 # use nt._getfullpathname()) 1169 obj = self._from_parts([os.getcwd()] + self._parts, init=False) 1170 obj._init(template=self) 1171 return obj 1172 1173 def resolve(self, strict=False): 1174 """ 1175 Make the path absolute, resolving all symlinks on the way and also 1176 normalizing it (for example turning slashes into backslashes under 1177 Windows). 1178 """ 1179 if self._closed: 1180 self._raise_closed() 1181 s = self._flavour.resolve(self, strict=strict) 1182 if s is None: 1183 # No symlink resolution => for consistency, raise an error if 1184 # the path doesn't exist or is forbidden 1185 self.stat() 1186 s = str(self.absolute()) 1187 # Now we have no symlinks in the path, it's safe to normalize it. 1188 normed = self._flavour.pathmod.normpath(s) 1189 obj = self._from_parts((normed,), init=False) 1190 obj._init(template=self) 1191 return obj 1192 1193 def stat(self): 1194 """ 1195 Return the result of the stat() system call on this path, like 1196 os.stat() does. 1197 """ 1198 return self._accessor.stat(self) 1199 1200 def owner(self): 1201 """ 1202 Return the login name of the file owner. 1203 """ 1204 import pwd 1205 return pwd.getpwuid(self.stat().st_uid).pw_name 1206 1207 def group(self): 1208 """ 1209 Return the group name of the file gid. 1210 """ 1211 import grp 1212 return grp.getgrgid(self.stat().st_gid).gr_name 1213 1214 def open(self, mode='r', buffering=-1, encoding=None, 1215 errors=None, newline=None): 1216 """ 1217 Open the file pointed by this path and return a file object, as 1218 the built-in open() function does. 1219 """ 1220 if self._closed: 1221 self._raise_closed() 1222 return io.open(self, mode, buffering, encoding, errors, newline, 1223 opener=self._opener) 1224 1225 def read_bytes(self): 1226 """ 1227 Open the file in bytes mode, read it, and close the file. 1228 """ 1229 with self.open(mode='rb') as f: 1230 return f.read() 1231 1232 def read_text(self, encoding=None, errors=None): 1233 """ 1234 Open the file in text mode, read it, and close the file. 1235 """ 1236 with self.open(mode='r', encoding=encoding, errors=errors) as f: 1237 return f.read() 1238 1239 def write_bytes(self, data): 1240 """ 1241 Open the file in bytes mode, write to it, and close the file. 1242 """ 1243 # type-check for the buffer interface before truncating the file 1244 view = memoryview(data) 1245 with self.open(mode='wb') as f: 1246 return f.write(view) 1247 1248 def write_text(self, data, encoding=None, errors=None): 1249 """ 1250 Open the file in text mode, write to it, and close the file. 1251 """ 1252 if not isinstance(data, str): 1253 raise TypeError('data must be str, not %s' % 1254 data.__class__.__name__) 1255 with self.open(mode='w', encoding=encoding, errors=errors) as f: 1256 return f.write(data) 1257 1258 def touch(self, mode=0o666, exist_ok=True): 1259 """ 1260 Create this file with the given access mode, if it doesn't exist. 1261 """ 1262 if self._closed: 1263 self._raise_closed() 1264 if exist_ok: 1265 # First try to bump modification time 1266 # Implementation note: GNU touch uses the UTIME_NOW option of 1267 # the utimensat() / futimens() functions. 1268 try: 1269 self._accessor.utime(self, None) 1270 except OSError: 1271 # Avoid exception chaining 1272 pass 1273 else: 1274 return 1275 flags = os.O_CREAT | os.O_WRONLY 1276 if not exist_ok: 1277 flags |= os.O_EXCL 1278 fd = self._raw_open(flags, mode) 1279 os.close(fd) 1280 1281 def mkdir(self, mode=0o777, parents=False, exist_ok=False): 1282 """ 1283 Create a new directory at this given path. 1284 """ 1285 if self._closed: 1286 self._raise_closed() 1287 try: 1288 self._accessor.mkdir(self, mode) 1289 except FileNotFoundError: 1290 if not parents or self.parent == self: 1291 raise 1292 self.parent.mkdir(parents=True, exist_ok=True) 1293 self.mkdir(mode, parents=False, exist_ok=exist_ok) 1294 except OSError: 1295 # Cannot rely on checking for EEXIST, since the operating system 1296 # could give priority to other errors like EACCES or EROFS 1297 if not exist_ok or not self.is_dir(): 1298 raise 1299 1300 def chmod(self, mode): 1301 """ 1302 Change the permissions of the path, like os.chmod(). 1303 """ 1304 if self._closed: 1305 self._raise_closed() 1306 self._accessor.chmod(self, mode) 1307 1308 def lchmod(self, mode): 1309 """ 1310 Like chmod(), except if the path points to a symlink, the symlink's 1311 permissions are changed, rather than its target's. 1312 """ 1313 if self._closed: 1314 self._raise_closed() 1315 self._accessor.lchmod(self, mode) 1316 1317 def unlink(self, missing_ok=False): 1318 """ 1319 Remove this file or link. 1320 If the path is a directory, use rmdir() instead. 1321 """ 1322 if self._closed: 1323 self._raise_closed() 1324 try: 1325 self._accessor.unlink(self) 1326 except FileNotFoundError: 1327 if not missing_ok: 1328 raise 1329 1330 def rmdir(self): 1331 """ 1332 Remove this directory. The directory must be empty. 1333 """ 1334 if self._closed: 1335 self._raise_closed() 1336 self._accessor.rmdir(self) 1337 1338 def lstat(self): 1339 """ 1340 Like stat(), except if the path points to a symlink, the symlink's 1341 status information is returned, rather than its target's. 1342 """ 1343 if self._closed: 1344 self._raise_closed() 1345 return self._accessor.lstat(self) 1346 1347 def rename(self, target): 1348 """ 1349 Rename this path to the target path. 1350 1351 The target path may be absolute or relative. Relative paths are 1352 interpreted relative to the current working directory, *not* the 1353 directory of the Path object. 1354 1355 Returns the new Path instance pointing to the target path. 1356 """ 1357 if self._closed: 1358 self._raise_closed() 1359 self._accessor.rename(self, target) 1360 return self.__class__(target) 1361 1362 def replace(self, target): 1363 """ 1364 Rename this path to the target path, overwriting if that path exists. 1365 1366 The target path may be absolute or relative. Relative paths are 1367 interpreted relative to the current working directory, *not* the 1368 directory of the Path object. 1369 1370 Returns the new Path instance pointing to the target path. 1371 """ 1372 if self._closed: 1373 self._raise_closed() 1374 self._accessor.replace(self, target) 1375 return self.__class__(target) 1376 1377 def symlink_to(self, target, target_is_directory=False): 1378 """ 1379 Make this path a symlink pointing to the target path. 1380 Note the order of arguments (link, target) is the reverse of os.symlink. 1381 """ 1382 if self._closed: 1383 self._raise_closed() 1384 self._accessor.symlink(target, self, target_is_directory) 1385 1386 def link_to(self, target): 1387 """ 1388 Make the target path a hard link pointing to this path. 1389 1390 Note this function does not make this path a hard link to *target*, 1391 despite the implication of the function and argument names. The order 1392 of arguments (target, link) is the reverse of Path.symlink_to, but 1393 matches that of os.link. 1394 1395 """ 1396 if self._closed: 1397 self._raise_closed() 1398 self._accessor.link_to(self, target) 1399 1400 # Convenience functions for querying the stat results 1401 1402 def exists(self): 1403 """ 1404 Whether this path exists. 1405 """ 1406 try: 1407 self.stat() 1408 except OSError as e: 1409 if not _ignore_error(e): 1410 raise 1411 return False 1412 except ValueError: 1413 # Non-encodable path 1414 return False 1415 return True 1416 1417 def is_dir(self): 1418 """ 1419 Whether this path is a directory. 1420 """ 1421 try: 1422 return S_ISDIR(self.stat().st_mode) 1423 except OSError as e: 1424 if not _ignore_error(e): 1425 raise 1426 # Path doesn't exist or is a broken symlink 1427 # (see https://bitbucket.org/pitrou/pathlib/issue/12/) 1428 return False 1429 except ValueError: 1430 # Non-encodable path 1431 return False 1432 1433 def is_file(self): 1434 """ 1435 Whether this path is a regular file (also True for symlinks pointing 1436 to regular files). 1437 """ 1438 try: 1439 return S_ISREG(self.stat().st_mode) 1440 except OSError as e: 1441 if not _ignore_error(e): 1442 raise 1443 # Path doesn't exist or is a broken symlink 1444 # (see https://bitbucket.org/pitrou/pathlib/issue/12/) 1445 return False 1446 except ValueError: 1447 # Non-encodable path 1448 return False 1449 1450 def is_mount(self): 1451 """ 1452 Check if this path is a POSIX mount point 1453 """ 1454 # Need to exist and be a dir 1455 if not self.exists() or not self.is_dir(): 1456 return False 1457 1458 parent = Path(self.parent) 1459 try: 1460 parent_dev = parent.stat().st_dev 1461 except OSError: 1462 return False 1463 1464 dev = self.stat().st_dev 1465 if dev != parent_dev: 1466 return True 1467 ino = self.stat().st_ino 1468 parent_ino = parent.stat().st_ino 1469 return ino == parent_ino 1470 1471 def is_symlink(self): 1472 """ 1473 Whether this path is a symbolic link. 1474 """ 1475 try: 1476 return S_ISLNK(self.lstat().st_mode) 1477 except OSError as e: 1478 if not _ignore_error(e): 1479 raise 1480 # Path doesn't exist 1481 return False 1482 except ValueError: 1483 # Non-encodable path 1484 return False 1485 1486 def is_block_device(self): 1487 """ 1488 Whether this path is a block device. 1489 """ 1490 try: 1491 return S_ISBLK(self.stat().st_mode) 1492 except OSError as e: 1493 if not _ignore_error(e): 1494 raise 1495 # Path doesn't exist or is a broken symlink 1496 # (see https://bitbucket.org/pitrou/pathlib/issue/12/) 1497 return False 1498 except ValueError: 1499 # Non-encodable path 1500 return False 1501 1502 def is_char_device(self): 1503 """ 1504 Whether this path is a character device. 1505 """ 1506 try: 1507 return S_ISCHR(self.stat().st_mode) 1508 except OSError as e: 1509 if not _ignore_error(e): 1510 raise 1511 # Path doesn't exist or is a broken symlink 1512 # (see https://bitbucket.org/pitrou/pathlib/issue/12/) 1513 return False 1514 except ValueError: 1515 # Non-encodable path 1516 return False 1517 1518 def is_fifo(self): 1519 """ 1520 Whether this path is a FIFO. 1521 """ 1522 try: 1523 return S_ISFIFO(self.stat().st_mode) 1524 except OSError as e: 1525 if not _ignore_error(e): 1526 raise 1527 # Path doesn't exist or is a broken symlink 1528 # (see https://bitbucket.org/pitrou/pathlib/issue/12/) 1529 return False 1530 except ValueError: 1531 # Non-encodable path 1532 return False 1533 1534 def is_socket(self): 1535 """ 1536 Whether this path is a socket. 1537 """ 1538 try: 1539 return S_ISSOCK(self.stat().st_mode) 1540 except OSError as e: 1541 if not _ignore_error(e): 1542 raise 1543 # Path doesn't exist or is a broken symlink 1544 # (see https://bitbucket.org/pitrou/pathlib/issue/12/) 1545 return False 1546 except ValueError: 1547 # Non-encodable path 1548 return False 1549 1550 def expanduser(self): 1551 """ Return a new path with expanded ~ and ~user constructs 1552 (as returned by os.path.expanduser) 1553 """ 1554 if (not (self._drv or self._root) and 1555 self._parts and self._parts[0][:1] == '~'): 1556 homedir = self._flavour.gethomedir(self._parts[0][1:]) 1557 return self._from_parts([homedir] + self._parts[1:]) 1558 1559 return self 1560 1561 1562class PosixPath(Path, PurePosixPath): 1563 """Path subclass for non-Windows systems. 1564 1565 On a POSIX system, instantiating a Path should return this object. 1566 """ 1567 __slots__ = () 1568 1569class WindowsPath(Path, PureWindowsPath): 1570 """Path subclass for Windows systems. 1571 1572 On a Windows system, instantiating a Path should return this object. 1573 """ 1574 __slots__ = () 1575 1576 def owner(self): 1577 raise NotImplementedError("Path.owner() is unsupported on this system") 1578 1579 def group(self): 1580 raise NotImplementedError("Path.group() is unsupported on this system") 1581 1582 def is_mount(self): 1583 raise NotImplementedError("Path.is_mount() is unsupported on this system") 1584