codekingpro/portable-devtools
114k
1"""Object-oriented filesystem paths.2 3This module provides classes to represent abstract paths and concrete4paths with operations that have semantics appropriate for different5operating systems.6"""7 8import io9import ntpath10import operator11import os12import posixpath13import sys14from errno import *15from glob import _StringGlobber, _no_recurse_symlinks16from itertools import chain17from stat import S_ISDIR, S_ISREG, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO18from _collections_abc import Sequence19 20try:21 import pwd22except ImportError:23 pwd = None24try:25 import grp26except ImportError:27 grp = None28 29from pathlib._os import (30 PathInfo, DirEntryInfo,31 ensure_different_files, ensure_distinct_paths,32 copyfile2, copyfileobj, magic_open, copy_info,33)34 35 36__all__ = [37 "UnsupportedOperation",38 "PurePath", "PurePosixPath", "PureWindowsPath",39 "Path", "PosixPath", "WindowsPath",40 ]41 42 43class UnsupportedOperation(NotImplementedError):44 """An exception that is raised when an unsupported operation is attempted.45 """46 pass47 48 49class _PathParents(Sequence):50 """This object provides sequence-like access to the logical ancestors51 of a path. Don't try to construct it yourself."""52 __slots__ = ('_path', '_drv', '_root', '_tail')53 54 def __init__(self, path):55 self._path = path56 self._drv = path.drive57 self._root = path.root58 self._tail = path._tail59 60 def __len__(self):61 return len(self._tail)62 63 def __getitem__(self, idx):64 if isinstance(idx, slice):65 return tuple(self[i] for i in range(*idx.indices(len(self))))66 67 if idx >= len(self) or idx < -len(self):68 raise IndexError(idx)69 if idx < 0:70 idx += len(self)71 return self._path._from_parsed_parts(self._drv, self._root,72 self._tail[:-idx - 1])73 74 def __repr__(self):75 return "<{}.parents>".format(type(self._path).__name__)76 77 78class PurePath:79 """Base class for manipulating paths without I/O.80 81 PurePath represents a filesystem path and offers operations which82 don't imply any actual filesystem I/O. Depending on your system,83 instantiating a PurePath will return either a PurePosixPath or a84 PureWindowsPath object. You can also instantiate either of these classes85 directly, regardless of your system.86 """87 88 __slots__ = (89 # The `_raw_paths` slot stores unjoined string paths. This is set in90 # the `__init__()` method.91 '_raw_paths',92 93 # The `_drv`, `_root` and `_tail_cached` slots store parsed and94 # normalized parts of the path. They are set when any of the `drive`,95 # `root` or `_tail` properties are accessed for the first time. The96 # three-part division corresponds to the result of97 # `os.path.splitroot()`, except that the tail is further split on path98 # separators (i.e. it is a list of strings), and that the root and99 # tail are normalized.100 '_drv', '_root', '_tail_cached',101 102 # The `_str` slot stores the string representation of the path,103 # computed from the drive, root and tail when `__str__()` is called104 # for the first time. It's used to implement `_str_normcase`105 '_str',106 107 # The `_str_normcase_cached` slot stores the string path with108 # normalized case. It is set when the `_str_normcase` property is109 # accessed for the first time. It's used to implement `__eq__()`110 # `__hash__()`, and `_parts_normcase`111 '_str_normcase_cached',112 113 # The `_parts_normcase_cached` slot stores the case-normalized114 # string path after splitting on path separators. It's set when the115 # `_parts_normcase` property is accessed for the first time. It's used116 # to implement comparison methods like `__lt__()`.117 '_parts_normcase_cached',118 119 # The `_hash` slot stores the hash of the case-normalized string120 # path. It's set when `__hash__()` is called for the first time.121 '_hash',122 )123 parser = os.path124 125 def __new__(cls, *args, **kwargs):126 """Construct a PurePath from one or several strings and or existing127 PurePath objects. The strings and path objects are combined so as128 to yield a canonicalized path, which is incorporated into the129 new PurePath object.130 """131 if cls is PurePath:132 cls = PureWindowsPath if os.name == 'nt' else PurePosixPath133 return object.__new__(cls)134 135 def __init__(self, *args):136 paths = []137 for arg in args:138 if isinstance(arg, PurePath):139 if arg.parser is not self.parser:140 # GH-103631: Convert separators for backwards compatibility.141 paths.append(arg.as_posix())142 else:143 paths.extend(arg._raw_paths)144 else:145 try:146 path = os.fspath(arg)147 except TypeError:148 path = arg149 if not isinstance(path, str):150 raise TypeError(151 "argument should be a str or an os.PathLike "152 "object where __fspath__ returns a str, "153 f"not {type(path).__name__!r}")154 paths.append(path)155 self._raw_paths = paths156 157 def with_segments(self, *pathsegments):158 """Construct a new path object from any number of path-like objects.159 Subclasses may override this method to customize how new path objects160 are created from methods like `iterdir()`.161 """162 return type(self)(*pathsegments)163 164 def joinpath(self, *pathsegments):165 """Combine this path with one or several arguments, and return a166 new path representing either a subpath (if all arguments are relative167 paths) or a totally different path (if one of the arguments is168 anchored).169 """170 return self.with_segments(self, *pathsegments)171 172 def __truediv__(self, key):173 try:174 return self.with_segments(self, key)175 except TypeError:176 return NotImplemented177 178 def __rtruediv__(self, key):179 try:180 return self.with_segments(key, self)181 except TypeError:182 return NotImplemented183 184 def __reduce__(self):185 return self.__class__, tuple(self._raw_paths)186 187 def __repr__(self):188 return "{}({!r})".format(self.__class__.__name__, self.as_posix())189 190 def __fspath__(self):191 return str(self)192 193 def __bytes__(self):194 """Return the bytes representation of the path. This is only195 recommended to use under Unix."""196 return os.fsencode(self)197 198 @property199 def _str_normcase(self):200 # String with normalized case, for hashing and equality checks201 try:202 return self._str_normcase_cached203 except AttributeError:204 if self.parser is posixpath:205 self._str_normcase_cached = str(self)206 else:207 self._str_normcase_cached = str(self).lower()208 return self._str_normcase_cached209 210 def __hash__(self):211 try:212 return self._hash213 except AttributeError:214 self._hash = hash(self._str_normcase)215 return self._hash216 217 def __eq__(self, other):218 if not isinstance(other, PurePath):219 return NotImplemented220 return self._str_normcase == other._str_normcase and self.parser is other.parser221 222 @property223 def _parts_normcase(self):224 # Cached parts with normalized case, for comparisons.225 try:226 return self._parts_normcase_cached227 except AttributeError:228 self._parts_normcase_cached = self._str_normcase.split(self.parser.sep)229 return self._parts_normcase_cached230 231 def __lt__(self, other):232 if not isinstance(other, PurePath) or self.parser is not other.parser:233 return NotImplemented234 return self._parts_normcase < other._parts_normcase235 236 def __le__(self, other):237 if not isinstance(other, PurePath) or self.parser is not other.parser:238 return NotImplemented239 return self._parts_normcase <= other._parts_normcase240 241 def __gt__(self, other):242 if not isinstance(other, PurePath) or self.parser is not other.parser:243 return NotImplemented244 return self._parts_normcase > other._parts_normcase245 246 def __ge__(self, other):247 if not isinstance(other, PurePath) or self.parser is not other.parser:248 return NotImplemented249 return self._parts_normcase >= other._parts_normcase250 251 def __str__(self):252 """Return the string representation of the path, suitable for253 passing to system calls."""254 try:255 return self._str256 except AttributeError:257 self._str = self._format_parsed_parts(self.drive, self.root,258 self._tail) or '.'259 return self._str260 261 @classmethod262 def _format_parsed_parts(cls, drv, root, tail):263 if drv or root:264 return drv + root + cls.parser.sep.join(tail)265 elif tail and cls.parser.splitdrive(tail[0])[0]:266 tail = ['.'] + tail267 return cls.parser.sep.join(tail)268 269 def _from_parsed_parts(self, drv, root, tail):270 path = self._from_parsed_string(self._format_parsed_parts(drv, root, tail))271 path._drv = drv272 path._root = root273 path._tail_cached = tail274 return path275 276 def _from_parsed_string(self, path_str):277 path = self.with_segments(path_str)278 path._str = path_str or '.'279 return path280 281 @classmethod282 def _parse_path(cls, path):283 if not path:284 return '', '', []285 sep = cls.parser.sep286 altsep = cls.parser.altsep287 if altsep:288 path = path.replace(altsep, sep)289 drv, root, rel = cls.parser.splitroot(path)290 if not root and drv.startswith(sep) and not drv.endswith(sep):291 drv_parts = drv.split(sep)292 if len(drv_parts) == 4 and drv_parts[2] not in '?.':293 # e.g. //server/share294 root = sep295 elif len(drv_parts) == 6:296 # e.g. //?/unc/server/share297 root = sep298 return drv, root, [x for x in rel.split(sep) if x and x != '.']299 300 @classmethod301 def _parse_pattern(cls, pattern):302 """Parse a glob pattern to a list of parts. This is much like303 _parse_path, except:304 305 - Rather than normalizing and returning the drive and root, we raise306 NotImplementedError if either are present.307 - If the path has no real parts, we raise ValueError.308 - If the path ends in a slash, then a final empty part is added.309 """310 drv, root, rel = cls.parser.splitroot(pattern)311 if root or drv:312 raise NotImplementedError("Non-relative patterns are unsupported")313 sep = cls.parser.sep314 altsep = cls.parser.altsep315 if altsep:316 rel = rel.replace(altsep, sep)317 parts = [x for x in rel.split(sep) if x and x != '.']318 if not parts:319 raise ValueError(f"Unacceptable pattern: {str(pattern)!r}")320 elif rel.endswith(sep):321 # GH-65238: preserve trailing slash in glob patterns.322 parts.append('')323 return parts324 325 def as_posix(self):326 """Return the string representation of the path with forward (/)327 slashes."""328 return str(self).replace(self.parser.sep, '/')329 330 @property331 def _raw_path(self):332 paths = self._raw_paths333 if len(paths) == 1:334 return paths[0]335 elif paths:336 # Join path segments from the initializer.337 return self.parser.join(*paths)338 else:339 return ''340 341 @property342 def drive(self):343 """The drive prefix (letter or UNC path), if any."""344 try:345 return self._drv346 except AttributeError:347 self._drv, self._root, self._tail_cached = self._parse_path(self._raw_path)348 return self._drv349 350 @property351 def root(self):352 """The root of the path, if any."""353 try:354 return self._root355 except AttributeError:356 self._drv, self._root, self._tail_cached = self._parse_path(self._raw_path)357 return self._root358 359 @property360 def _tail(self):361 try:362 return self._tail_cached363 except AttributeError:364 self._drv, self._root, self._tail_cached = self._parse_path(self._raw_path)365 return self._tail_cached366 367 @property368 def anchor(self):369 """The concatenation of the drive and root, or ''."""370 return self.drive + self.root371 372 @property373 def parts(self):374 """An object providing sequence-like access to the375 components in the filesystem path."""376 if self.drive or self.root:377 return (self.drive + self.root,) + tuple(self._tail)378 else:379 return tuple(self._tail)380 381 @property382 def parent(self):383 """The logical parent of the path."""384 drv = self.drive385 root = self.root386 tail = self._tail387 if not tail:388 return self389 return self._from_parsed_parts(drv, root, tail[:-1])390 391 @property392 def parents(self):393 """A sequence of this path's logical parents."""394 # The value of this property should not be cached on the path object,395 # as doing so would introduce a reference cycle.396 return _PathParents(self)397 398 @property399 def name(self):400 """The final path component, if any."""401 tail = self._tail402 if not tail:403 return ''404 return tail[-1]405 406 def with_name(self, name):407 """Return a new path with the file name changed."""408 p = self.parser409 if not name or p.sep in name or (p.altsep and p.altsep in name) or name == '.':410 raise ValueError(f"Invalid name {name!r}")411 tail = self._tail.copy()412 if not tail:413 raise ValueError(f"{self!r} has an empty name")414 tail[-1] = name415 return self._from_parsed_parts(self.drive, self.root, tail)416 417 def with_stem(self, stem):418 """Return a new path with the stem changed."""419 suffix = self.suffix420 if not suffix:421 return self.with_name(stem)422 elif not stem:423 # If the suffix is non-empty, we can't make the stem empty.424 raise ValueError(f"{self!r} has a non-empty suffix")425 else:426 return self.with_name(stem + suffix)427 428 def with_suffix(self, suffix):429 """Return a new path with the file suffix changed. If the path430 has no suffix, add given suffix. If the given suffix is an empty431 string, remove the suffix from the path.432 """433 stem = self.stem434 if not stem:435 # If the stem is empty, we can't make the suffix non-empty.436 raise ValueError(f"{self!r} has an empty name")437 elif suffix and not suffix.startswith('.'):438 raise ValueError(f"Invalid suffix {suffix!r}")439 else:440 return self.with_name(stem + suffix)441 442 @property443 def stem(self):444 """The final path component, minus its last suffix."""445 name = self.name446 i = name.rfind('.')447 if i != -1:448 stem = name[:i]449 # Stem must contain at least one non-dot character.450 if stem.lstrip('.'):451 return stem452 return name453 454 @property455 def suffix(self):456 """457 The final component's last suffix, if any.458 459 This includes the leading period. For example: '.txt'460 """461 name = self.name.lstrip('.')462 i = name.rfind('.')463 if i != -1:464 return name[i:]465 return ''466 467 @property468 def suffixes(self):469 """470 A list of the final component's suffixes, if any.471 472 These include the leading periods. For example: ['.tar', '.gz']473 """474 return ['.' + ext for ext in self.name.lstrip('.').split('.')[1:]]475 476 def relative_to(self, other, *, walk_up=False):477 """Return the relative path to another path identified by the passed478 arguments. If the operation is not possible (because this is not479 related to the other path), raise ValueError.480 481 The *walk_up* parameter controls whether `..` may be used to resolve482 the path.483 """484 if not hasattr(other, 'with_segments'):485 other = self.with_segments(other)486 for step, path in enumerate(chain([other], other.parents)):487 if path == self or path in self.parents:488 break489 elif not walk_up:490 raise ValueError(f"{str(self)!r} is not in the subpath of {str(other)!r}")491 elif path.name == '..':492 raise ValueError(f"'..' segment in {str(other)!r} cannot be walked")493 else:494 raise ValueError(f"{str(self)!r} and {str(other)!r} have different anchors")495 parts = ['..'] * step + self._tail[len(path._tail):]496 return self._from_parsed_parts('', '', parts)497 498 def is_relative_to(self, other):499 """Return True if the path is relative to another path or False.500 """501 if not hasattr(other, 'with_segments'):502 other = self.with_segments(other)503 return other == self or other in self.parents504 505 def is_absolute(self):506 """True if the path is absolute (has both a root and, if applicable,507 a drive)."""508 if self.parser is posixpath:509 # Optimization: work with raw paths on POSIX.510 for path in self._raw_paths:511 if path.startswith('/'):512 return True513 return False514 return self.parser.isabs(self)515 516 def is_reserved(self):517 """Return True if the path contains one of the special names reserved518 by the system, if any."""519 import warnings520 msg = ("pathlib.PurePath.is_reserved() is deprecated and scheduled "521 "for removal in Python 3.15. Use os.path.isreserved() to "522 "detect reserved paths on Windows.")523 warnings._deprecated("pathlib.PurePath.is_reserved", msg, remove=(3, 15))524 if self.parser is ntpath:525 return self.parser.isreserved(self)526 return False527 528 def as_uri(self):529 """Return the path as a URI."""530 import warnings531 msg = ("pathlib.PurePath.as_uri() is deprecated and scheduled "532 "for removal in Python 3.19. Use pathlib.Path.as_uri().")533 warnings._deprecated("pathlib.PurePath.as_uri", msg, remove=(3, 19))534 if not self.is_absolute():535 raise ValueError("relative path can't be expressed as a file URI")536 537 drive = self.drive538 if len(drive) == 2 and drive[1] == ':':539 # It's a path on a local drive => 'file:///c:/a/b'540 prefix = 'file:///' + drive541 path = self.as_posix()[2:]542 elif drive:543 # It's a path on a network drive => 'file://host/share/a/b'544 prefix = 'file:'545 path = self.as_posix()546 else:547 # It's a posix path => 'file:///etc/hosts'548 prefix = 'file://'549 path = str(self)550 from urllib.parse import quote_from_bytes551 return prefix + quote_from_bytes(os.fsencode(path))552 553 def full_match(self, pattern, *, case_sensitive=None):554 """555 Return True if this path matches the given glob-style pattern. The556 pattern is matched against the entire path.557 """558 if not hasattr(pattern, 'with_segments'):559 pattern = self.with_segments(pattern)560 if case_sensitive is None:561 case_sensitive = self.parser is posixpath562 563 # The string representation of an empty path is a single dot ('.'). Empty564 # paths shouldn't match wildcards, so we change it to the empty string.565 path = str(self) if self.parts else ''566 pattern = str(pattern) if pattern.parts else ''567 globber = _StringGlobber(self.parser.sep, case_sensitive, recursive=True)568 return globber.compile(pattern)(path) is not None569 570 def match(self, path_pattern, *, case_sensitive=None):571 """572 Return True if this path matches the given pattern. If the pattern is573 relative, matching is done from the right; otherwise, the entire path574 is matched. The recursive wildcard '**' is *not* supported by this575 method.576 """577 if not hasattr(path_pattern, 'with_segments'):578 path_pattern = self.with_segments(path_pattern)579 if case_sensitive is None:580 case_sensitive = self.parser is posixpath581 path_parts = self.parts[::-1]582 pattern_parts = path_pattern.parts[::-1]583 if not pattern_parts:584 raise ValueError("empty pattern")585 if len(path_parts) < len(pattern_parts):586 return False587 if len(path_parts) > len(pattern_parts) and path_pattern.anchor:588 return False589 globber = _StringGlobber(self.parser.sep, case_sensitive)590 for path_part, pattern_part in zip(path_parts, pattern_parts):591 match = globber.compile(pattern_part)592 if match(path_part) is None:593 return False594 return True595 596# Subclassing os.PathLike makes isinstance() checks slower,597# which in turn makes Path construction slower. Register instead!598os.PathLike.register(PurePath)599 600 601class PurePosixPath(PurePath):602 """PurePath subclass for non-Windows systems.603 604 On a POSIX system, instantiating a PurePath should return this object.605 However, you can also instantiate it directly on any system.606 """607 parser = posixpath608 __slots__ = ()609 610 611class PureWindowsPath(PurePath):612 """PurePath subclass for Windows systems.613 614 On a Windows system, instantiating a PurePath should return this object.615 However, you can also instantiate it directly on any system.616 """617 parser = ntpath618 __slots__ = ()619 620 621class Path(PurePath):622 """PurePath subclass that can make system calls.623 624 Path represents a filesystem path but unlike PurePath, also offers625 methods to do system calls on path objects. Depending on your system,626 instantiating a Path will return either a PosixPath or a WindowsPath627 object. You can also instantiate a PosixPath or WindowsPath directly,628 but cannot instantiate a WindowsPath on a POSIX system or vice versa.629 """630 __slots__ = ('_info',)631 632 def __new__(cls, *args, **kwargs):633 if cls is Path:634 cls = WindowsPath if os.name == 'nt' else PosixPath635 return object.__new__(cls)636 637 @property638 def info(self):639 """640 A PathInfo object that exposes the file type and other file attributes641 of this path.642 """643 try:644 return self._info645 except AttributeError:646 self._info = PathInfo(self)647 return self._info648 649 def stat(self, *, follow_symlinks=True):650 """651 Return the result of the stat() system call on this path, like652 os.stat() does.653 """654 return os.stat(self, follow_symlinks=follow_symlinks)655 656 def lstat(self):657 """658 Like stat(), except if the path points to a symlink, the symlink's659 status information is returned, rather than its target's.660 """661 return os.lstat(self)662 663 def exists(self, *, follow_symlinks=True):664 """665 Whether this path exists.666 667 This method normally follows symlinks; to check whether a symlink exists,668 add the argument follow_symlinks=False.669 """670 if follow_symlinks:671 return os.path.exists(self)672 return os.path.lexists(self)673 674 def is_dir(self, *, follow_symlinks=True):675 """676 Whether this path is a directory.677 """678 if follow_symlinks:679 return os.path.isdir(self)680 try:681 return S_ISDIR(self.stat(follow_symlinks=follow_symlinks).st_mode)682 except (OSError, ValueError):683 return False684 685 def is_file(self, *, follow_symlinks=True):686 """687 Whether this path is a regular file (also True for symlinks pointing688 to regular files).689 """690 if follow_symlinks:691 return os.path.isfile(self)692 try:693 return S_ISREG(self.stat(follow_symlinks=follow_symlinks).st_mode)694 except (OSError, ValueError):695 return False696 697 def is_mount(self):698 """699 Check if this path is a mount point700 """701 return os.path.ismount(self)702 703 def is_symlink(self):704 """705 Whether this path is a symbolic link.706 """707 return os.path.islink(self)708 709 def is_junction(self):710 """711 Whether this path is a junction.712 """713 return os.path.isjunction(self)714 715 def is_block_device(self):716 """717 Whether this path is a block device.718 """719 try:720 return S_ISBLK(self.stat().st_mode)721 except (OSError, ValueError):722 return False723 724 def is_char_device(self):725 """726 Whether this path is a character device.727 """728 try:729 return S_ISCHR(self.stat().st_mode)730 except (OSError, ValueError):731 return False732 733 def is_fifo(self):734 """735 Whether this path is a FIFO.736 """737 try:738 return S_ISFIFO(self.stat().st_mode)739 except (OSError, ValueError):740 return False741 742 def is_socket(self):743 """744 Whether this path is a socket.745 """746 try:747 return S_ISSOCK(self.stat().st_mode)748 except (OSError, ValueError):749 return False750 751 def samefile(self, other_path):752 """Return whether other_path is the same or not as this file753 (as returned by os.path.samefile()).754 """755 st = self.stat()756 try:757 other_st = other_path.stat()758 except AttributeError:759 other_st = self.with_segments(other_path).stat()760 return (st.st_ino == other_st.st_ino and761 st.st_dev == other_st.st_dev)762 763 def open(self, mode='r', buffering=-1, encoding=None,764 errors=None, newline=None):765 """766 Open the file pointed to by this path and return a file object, as767 the built-in open() function does.768 """769 if "b" not in mode:770 encoding = io.text_encoding(encoding)771 return io.open(self, mode, buffering, encoding, errors, newline)772 773 def read_bytes(self):774 """775 Open the file in bytes mode, read it, and close the file.776 """777 with self.open(mode='rb', buffering=0) as f:778 return f.read()779 780 def read_text(self, encoding=None, errors=None, newline=None):781 """782 Open the file in text mode, read it, and close the file.783 """784 # Call io.text_encoding() here to ensure any warning is raised at an785 # appropriate stack level.786 encoding = io.text_encoding(encoding)787 with self.open(mode='r', encoding=encoding, errors=errors, newline=newline) as f:788 return f.read()789 790 def write_bytes(self, data):791 """792 Open the file in bytes mode, write to it, and close the file.793 """794 # type-check for the buffer interface before truncating the file795 view = memoryview(data)796 with self.open(mode='wb') as f:797 return f.write(view)798 799 def write_text(self, data, encoding=None, errors=None, newline=None):800 """801 Open the file in text mode, write to it, and close the file.802 """803 # Call io.text_encoding() here to ensure any warning is raised at an804 # appropriate stack level.805 encoding = io.text_encoding(encoding)806 if not isinstance(data, str):807 raise TypeError('data must be str, not %s' %808 data.__class__.__name__)809 with self.open(mode='w', encoding=encoding, errors=errors, newline=newline) as f:810 return f.write(data)811 812 _remove_leading_dot = operator.itemgetter(slice(2, None))813 _remove_trailing_slash = operator.itemgetter(slice(-1))814 815 def _filter_trailing_slash(self, paths):816 sep = self.parser.sep817 anchor_len = len(self.anchor)818 for path_str in paths:819 if len(path_str) > anchor_len and path_str[-1] == sep:820 path_str = path_str[:-1]821 yield path_str822 823 def _from_dir_entry(self, dir_entry, path_str):824 path = self.with_segments(path_str)825 path._str = path_str826 path._info = DirEntryInfo(dir_entry)827 return path828 829 def iterdir(self):830 """Yield path objects of the directory contents.831 832 The children are yielded in arbitrary order, and the833 special entries '.' and '..' are not included.834 """835 root_dir = str(self)836 with os.scandir(root_dir) as scandir_it:837 entries = list(scandir_it)838 if root_dir == '.':839 return (self._from_dir_entry(e, e.name) for e in entries)840 else:841 return (self._from_dir_entry(e, e.path) for e in entries)842 843 def glob(self, pattern, *, case_sensitive=None, recurse_symlinks=False):844 """Iterate over this subtree and yield all existing files (of any845 kind, including directories) matching the given relative pattern.846 """847 sys.audit("pathlib.Path.glob", self, pattern)848 if case_sensitive is None:849 case_sensitive = self.parser is posixpath850 case_pedantic = False851 else:852 # The user has expressed a case sensitivity choice, but we don't853 # know the case sensitivity of the underlying filesystem, so we854 # must use scandir() for everything, including non-wildcard parts.855 case_pedantic = True856 parts = self._parse_pattern(pattern)857 recursive = True if recurse_symlinks else _no_recurse_symlinks858 globber = _StringGlobber(self.parser.sep, case_sensitive, case_pedantic, recursive)859 select = globber.selector(parts[::-1])860 root = str(self)861 paths = select(self.parser.join(root, ''))862 863 # Normalize results864 if root == '.':865 paths = map(self._remove_leading_dot, paths)866 if parts[-1] == '':867 paths = map(self._remove_trailing_slash, paths)868 elif parts[-1] == '**':869 paths = self._filter_trailing_slash(paths)870 paths = map(self._from_parsed_string, paths)871 return paths872 873 def rglob(self, pattern, *, case_sensitive=None, recurse_symlinks=False):874 """Recursively yield all existing files (of any kind, including875 directories) matching the given relative pattern, anywhere in876 this subtree.877 """878 sys.audit("pathlib.Path.rglob", self, pattern)879 pattern = self.parser.join('**', pattern)880 return self.glob(pattern, case_sensitive=case_sensitive, recurse_symlinks=recurse_symlinks)881 882 def walk(self, top_down=True, on_error=None, follow_symlinks=False):883 """Walk the directory tree from this directory, similar to os.walk()."""884 sys.audit("pathlib.Path.walk", self, on_error, follow_symlinks)885 root_dir = str(self)886 if not follow_symlinks:887 follow_symlinks = os._walk_symlinks_as_files888 results = os.walk(root_dir, top_down, on_error, follow_symlinks)889 for path_str, dirnames, filenames in results:890 if root_dir == '.':891 path_str = path_str[2:]892 yield self._from_parsed_string(path_str), dirnames, filenames893 894 def absolute(self):895 """Return an absolute version of this path896 No normalization or symlink resolution is performed.897 898 Use resolve() to resolve symlinks and remove '..' segments.899 """900 if self.is_absolute():901 return self902 if self.root:903 drive = os.path.splitroot(os.getcwd())[0]904 return self._from_parsed_parts(drive, self.root, self._tail)905 if self.drive:906 # There is a CWD on each drive-letter drive.907 cwd = os.path.abspath(self.drive)908 else:909 cwd = os.getcwd()910 if not self._tail:911 # Fast path for "empty" paths, e.g. Path("."), Path("") or Path().912 # We pass only one argument to with_segments() to avoid the cost913 # of joining, and we exploit the fact that getcwd() returns a914 # fully-normalized string by storing it in _str. This is used to915 # implement Path.cwd().916 return self._from_parsed_string(cwd)917 drive, root, rel = os.path.splitroot(cwd)918 if not rel:919 return self._from_parsed_parts(drive, root, self._tail)920 tail = rel.split(self.parser.sep)921 tail.extend(self._tail)922 return self._from_parsed_parts(drive, root, tail)923 924 @classmethod925 def cwd(cls):926 """Return a new path pointing to the current working directory."""927 cwd = os.getcwd()928 path = cls(cwd)929 path._str = cwd # getcwd() returns a normalized path930 return path931 932 def resolve(self, strict=False):933 """934 Make the path absolute, resolving all symlinks on the way and also935 normalizing it.936 """937 938 return self.with_segments(os.path.realpath(self, strict=strict))939 940 if pwd:941 def owner(self, *, follow_symlinks=True):942 """943 Return the login name of the file owner.944 """945 uid = self.stat(follow_symlinks=follow_symlinks).st_uid946 return pwd.getpwuid(uid).pw_name947 else:948 def owner(self, *, follow_symlinks=True):949 """950 Return the login name of the file owner.951 """952 f = f"{type(self).__name__}.owner()"953 raise UnsupportedOperation(f"{f} is unsupported on this system")954 955 if grp:956 def group(self, *, follow_symlinks=True):957 """958 Return the group name of the file gid.959 """960 gid = self.stat(follow_symlinks=follow_symlinks).st_gid961 return grp.getgrgid(gid).gr_name962 else:963 def group(self, *, follow_symlinks=True):964 """965 Return the group name of the file gid.966 """967 f = f"{type(self).__name__}.group()"968 raise UnsupportedOperation(f"{f} is unsupported on this system")969 970 if hasattr(os, "readlink"):971 def readlink(self):972 """973 Return the path to which the symbolic link points.974 """975 return self.with_segments(os.readlink(self))976 else:977 def readlink(self):978 """979 Return the path to which the symbolic link points.980 """981 f = f"{type(self).__name__}.readlink()"982 raise UnsupportedOperation(f"{f} is unsupported on this system")983 984 def touch(self, mode=0o666, exist_ok=True):985 """986 Create this file with the given access mode, if it doesn't exist.987 """988 989 if exist_ok:990 # First try to bump modification time991 # Implementation note: GNU touch uses the UTIME_NOW option of992 # the utimensat() / futimens() functions.993 try:994 os.utime(self, None)995 except OSError:996 # Avoid exception chaining997 pass998 else:999 return1000 flags = os.O_CREAT | os.O_WRONLY1001 if not exist_ok:1002 flags |= os.O_EXCL1003 fd = os.open(self, flags, mode)1004 os.close(fd)1005 1006 def mkdir(self, mode=0o777, parents=False, exist_ok=False):1007 """1008 Create a new directory at this given path.1009 """1010 try:1011 os.mkdir(self, mode)1012 except FileNotFoundError:1013 if not parents or self.parent == self:1014 raise1015 self.parent.mkdir(parents=True, exist_ok=True)1016 self.mkdir(mode, parents=False, exist_ok=exist_ok)1017 except OSError:1018 # Cannot rely on checking for EEXIST, since the operating system1019 # could give priority to other errors like EACCES or EROFS1020 if not exist_ok or not self.is_dir():1021 raise1022 1023 def chmod(self, mode, *, follow_symlinks=True):1024 """1025 Change the permissions of the path, like os.chmod().1026 """1027 os.chmod(self, mode, follow_symlinks=follow_symlinks)1028 1029 def lchmod(self, mode):1030 """1031 Like chmod(), except if the path points to a symlink, the symlink's1032 permissions are changed, rather than its target's.1033 """1034 self.chmod(mode, follow_symlinks=False)1035 1036 def unlink(self, missing_ok=False):1037 """1038 Remove this file or link.1039 If the path is a directory, use rmdir() instead.1040 """1041 try:1042 os.unlink(self)1043 except FileNotFoundError:1044 if not missing_ok:1045 raise1046 1047 def rmdir(self):1048 """1049 Remove this directory. The directory must be empty.1050 """1051 os.rmdir(self)1052 1053 def _delete(self):1054 """1055 Delete this file or directory (including all sub-directories).1056 """1057 if self.is_symlink() or self.is_junction():1058 self.unlink()1059 elif self.is_dir():1060 # Lazy import to improve module import time1061 import shutil1062 shutil.rmtree(self)1063 else:1064 self.unlink()1065 1066 def rename(self, target):1067 """1068 Rename this path to the target path.1069 1070 The target path may be absolute or relative. Relative paths are1071 interpreted relative to the current working directory, *not* the1072 directory of the Path object.1073 1074 Returns the new Path instance pointing to the target path.1075 """1076 os.rename(self, target)1077 if not hasattr(target, 'with_segments'):1078 target = self.with_segments(target)1079 return target1080 1081 def replace(self, target):1082 """1083 Rename this path to the target path, overwriting if that path exists.1084 1085 The target path may be absolute or relative. Relative paths are1086 interpreted relative to the current working directory, *not* the1087 directory of the Path object.1088 1089 Returns the new Path instance pointing to the target path.1090 """1091 os.replace(self, target)1092 if not hasattr(target, 'with_segments'):1093 target = self.with_segments(target)1094 return target1095 1096 def copy(self, target, **kwargs):1097 """1098 Recursively copy this file or directory tree to the given destination.1099 """1100 if not hasattr(target, 'with_segments'):1101 target = self.with_segments(target)1102 ensure_distinct_paths(self, target)1103 target._copy_from(self, **kwargs)1104 return target.joinpath() # Empty join to ensure fresh metadata.1105 1106 def copy_into(self, target_dir, **kwargs):1107 """1108 Copy this file or directory tree into the given existing directory.1109 """1110 name = self.name1111 if not name:1112 raise ValueError(f"{self!r} has an empty name")1113 elif hasattr(target_dir, 'with_segments'):1114 target = target_dir / name1115 else:1116 target = self.with_segments(target_dir, name)1117 return self.copy(target, **kwargs)1118 1119 def _copy_from(self, source, follow_symlinks=True, preserve_metadata=False):1120 """1121 Recursively copy the given path to this path.1122 """1123 if not follow_symlinks and source.info.is_symlink():1124 self._copy_from_symlink(source, preserve_metadata)1125 elif source.info.is_dir():1126 children = source.iterdir()1127 os.mkdir(self)1128 for child in children:1129 self.joinpath(child.name)._copy_from(1130 child, follow_symlinks, preserve_metadata)1131 if preserve_metadata:1132 copy_info(source.info, self)1133 else:1134 self._copy_from_file(source, preserve_metadata)1135 1136 def _copy_from_file(self, source, preserve_metadata=False):1137 ensure_different_files(source, self)1138 with magic_open(source, 'rb') as source_f:1139 with open(self, 'wb') as target_f:1140 copyfileobj(source_f, target_f)1141 if preserve_metadata:1142 copy_info(source.info, self)1143 1144 if copyfile2:1145 # Use fast OS routine for local file copying where available.1146 _copy_from_file_fallback = _copy_from_file1147 def _copy_from_file(self, source, preserve_metadata=False):1148 try:1149 source = os.fspath(source)1150 except TypeError:1151 pass1152 else:1153 copyfile2(source, str(self))1154 return1155 self._copy_from_file_fallback(source, preserve_metadata)1156 1157 if os.name == 'nt':1158 # If a directory-symlink is copied *before* its target, then1159 # os.symlink() incorrectly creates a file-symlink on Windows. Avoid1160 # this by passing *target_is_dir* to os.symlink() on Windows.1161 def _copy_from_symlink(self, source, preserve_metadata=False):1162 os.symlink(str(source.readlink()), self, source.info.is_dir())1163 if preserve_metadata:1164 copy_info(source.info, self, follow_symlinks=False)1165 else:1166 def _copy_from_symlink(self, source, preserve_metadata=False):1167 os.symlink(str(source.readlink()), self)1168 if preserve_metadata:1169 copy_info(source.info, self, follow_symlinks=False)1170 1171 def move(self, target):1172 """1173 Recursively move this file or directory tree to the given destination.1174 """1175 # Use os.replace() if the target is os.PathLike and on the same FS.1176 try:1177 target = self.with_segments(target)1178 except TypeError:1179 pass1180 else:1181 ensure_different_files(self, target)1182 try:1183 os.replace(self, target)1184 except OSError as err:1185 if err.errno != EXDEV:1186 raise1187 else:1188 return target.joinpath() # Empty join to ensure fresh metadata.1189 # Fall back to copy+delete.1190 target = self.copy(target, follow_symlinks=False, preserve_metadata=True)1191 self._delete()1192 return target1193 1194 def move_into(self, target_dir):1195 """1196 Move this file or directory tree into the given existing directory.1197 """1198 name = self.name1199 if not name:1200 raise ValueError(f"{self!r} has an empty name")