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