1# Licensed under the Apache License, Version 2.0 (the "License"); 2# you may not use this file except in compliance with the License. 3# You may obtain a copy of the License at 4# 5# http://www.apache.org/licenses/LICENSE-2.0 6# 7# Unless required by applicable law or agreed to in writing, software 8# distributed under the License is distributed on an "AS IS" BASIS, 9# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10# See the License for the specific language governing permissions and 11# limitations under the License. 12 13"""A fake implementation for pathlib working with FakeFilesystem. 14New in pyfakefs 3.0. 15 16Usage: 17 18* With fake_filesystem_unittest: 19 If using fake_filesystem_unittest.TestCase, pathlib gets replaced 20 by fake_pathlib together with other file system related modules. 21 22* Stand-alone with FakeFilesystem: 23 `filesystem = fake_filesystem.FakeFilesystem()` 24 `fake_pathlib_module = fake_filesystem.FakePathlibModule(filesystem)` 25 `path = fake_pathlib_module.Path('/foo/bar')` 26 27Note: as the implementation is based on FakeFilesystem, all faked classes 28(including PurePosixPath, PosixPath, PureWindowsPath and WindowsPath) 29get the properties of the underlying fake filesystem. 30""" 31import errno 32import fnmatch 33import functools 34import os 35import pathlib 36from pathlib import PurePath 37import re 38import sys 39from urllib.parse import quote_from_bytes as urlquote_from_bytes 40 41from pyfakefs import fake_scandir 42from pyfakefs.extra_packages import use_scandir 43from pyfakefs.fake_filesystem import FakeFileOpen, FakeFilesystem 44 45 46def init_module(filesystem): 47 """Initializes the fake module with the fake file system.""" 48 # pylint: disable=protected-access 49 FakePath.filesystem = filesystem 50 FakePathlibModule.PureWindowsPath._flavour = _FakeWindowsFlavour( 51 filesystem) 52 FakePathlibModule.PurePosixPath._flavour = _FakePosixFlavour(filesystem) 53 54 55def _wrap_strfunc(strfunc): 56 @functools.wraps(strfunc) 57 def _wrapped(pathobj, *args, **kwargs): 58 return strfunc(pathobj.filesystem, str(pathobj), *args, **kwargs) 59 60 return staticmethod(_wrapped) 61 62 63def _wrap_binary_strfunc(strfunc): 64 @functools.wraps(strfunc) 65 def _wrapped(pathobj1, pathobj2, *args): 66 return strfunc( 67 pathobj1.filesystem, str(pathobj1), str(pathobj2), *args) 68 69 return staticmethod(_wrapped) 70 71 72def _wrap_binary_strfunc_reverse(strfunc): 73 @functools.wraps(strfunc) 74 def _wrapped(pathobj1, pathobj2, *args): 75 return strfunc( 76 pathobj2.filesystem, str(pathobj2), str(pathobj1), *args) 77 78 return staticmethod(_wrapped) 79 80 81try: 82 accessor = pathlib._Accessor # type: ignore [attr-defined] 83except AttributeError: 84 accessor = object 85 86 87class _FakeAccessor(accessor): # type: ignore [valid-type, misc] 88 """Accessor which forwards some of the functions to FakeFilesystem methods. 89 """ 90 91 stat = _wrap_strfunc(FakeFilesystem.stat) 92 93 lstat = _wrap_strfunc( 94 lambda fs, path: FakeFilesystem.stat(fs, path, follow_symlinks=False)) 95 96 listdir = _wrap_strfunc(FakeFilesystem.listdir) 97 98 if use_scandir: 99 scandir = _wrap_strfunc(fake_scandir.scandir) 100 101 chmod = _wrap_strfunc(FakeFilesystem.chmod) 102 103 if hasattr(os, "lchmod"): 104 lchmod = _wrap_strfunc(lambda fs, path, mode: FakeFilesystem.chmod( 105 fs, path, mode, follow_symlinks=False)) 106 else: 107 def lchmod(self, pathobj, *args, **kwargs): 108 """Raises not implemented for Windows systems.""" 109 raise NotImplementedError("lchmod() not available on this system") 110 111 def chmod(self, pathobj, *args, **kwargs): 112 if "follow_symlinks" in kwargs: 113 if sys.version_info < (3, 10): 114 raise TypeError("chmod() got an unexpected keyword " 115 "argument 'follow_synlinks'") 116 if (not kwargs["follow_symlinks"] and 117 os.chmod not in os.supports_follow_symlinks): 118 raise NotImplementedError( 119 "`follow_symlinks` for chmod() is not available " 120 "on this system") 121 return pathobj.filesystem.chmod(str(pathobj), *args, **kwargs) 122 123 mkdir = _wrap_strfunc(FakeFilesystem.makedir) 124 125 unlink = _wrap_strfunc(FakeFilesystem.remove) 126 127 rmdir = _wrap_strfunc(FakeFilesystem.rmdir) 128 129 rename = _wrap_binary_strfunc(FakeFilesystem.rename) 130 131 replace = _wrap_binary_strfunc( 132 lambda fs, old_path, new_path: FakeFilesystem.rename( 133 fs, old_path, new_path, force_replace=True)) 134 135 symlink = _wrap_binary_strfunc_reverse( 136 lambda fs, file_path, link_target, target_is_directory: 137 FakeFilesystem.create_symlink(fs, file_path, link_target, 138 create_missing_dirs=False)) 139 140 if (3, 8) <= sys.version_info: 141 link_to = _wrap_binary_strfunc( 142 lambda fs, file_path, link_target: 143 FakeFilesystem.link(fs, file_path, link_target)) 144 145 if sys.version_info >= (3, 10): 146 link = _wrap_binary_strfunc( 147 lambda fs, file_path, link_target: 148 FakeFilesystem.link(fs, file_path, link_target)) 149 150 # this will use the fake filesystem because os is patched 151 def getcwd(self): 152 return os.getcwd() 153 154 readlink = _wrap_strfunc(FakeFilesystem.readlink) 155 156 utime = _wrap_strfunc(FakeFilesystem.utime) 157 158 159_fake_accessor = _FakeAccessor() 160 161flavour = pathlib._Flavour # type: ignore [attr-defined] 162 163 164class _FakeFlavour(flavour): # type: ignore [valid-type, misc] 165 """Fake Flavour implementation used by PurePath and _Flavour""" 166 167 filesystem = None 168 sep = '/' 169 altsep = None 170 has_drv = False 171 172 ext_namespace_prefix = '\\\\?\\' 173 174 drive_letters = ( 175 set(chr(x) for x in range(ord('a'), ord('z') + 1)) | 176 set(chr(x) for x in range(ord('A'), ord('Z') + 1)) 177 ) 178 179 def __init__(self, filesystem): 180 self.filesystem = filesystem 181 self.sep = filesystem.path_separator 182 self.altsep = filesystem.alternative_path_separator 183 self.has_drv = filesystem.is_windows_fs 184 super(_FakeFlavour, self).__init__() 185 186 @staticmethod 187 def _split_extended_path(path, ext_prefix=ext_namespace_prefix): 188 prefix = '' 189 if path.startswith(ext_prefix): 190 prefix = path[:4] 191 path = path[4:] 192 if path.startswith('UNC\\'): 193 prefix += path[:3] 194 path = '\\' + path[3:] 195 return prefix, path 196 197 def _splitroot_with_drive(self, path, sep): 198 first = path[0:1] 199 second = path[1:2] 200 if second == sep and first == sep: 201 # extended paths should also disable the collapsing of "." 202 # components (according to MSDN docs). 203 prefix, path = self._split_extended_path(path) 204 first = path[0:1] 205 second = path[1:2] 206 else: 207 prefix = '' 208 third = path[2:3] 209 if second == sep and first == sep and third != sep: 210 # is a UNC path: 211 # vvvvvvvvvvvvvvvvvvvvv root 212 # \\machine\mountpoint\directory\etc\... 213 # directory ^^^^^^^^^^^^^^ 214 index = path.find(sep, 2) 215 if index != -1: 216 index2 = path.find(sep, index + 1) 217 # a UNC path can't have two slashes in a row 218 # (after the initial two) 219 if index2 != index + 1: 220 if index2 == -1: 221 index2 = len(path) 222 if prefix: 223 return prefix + path[1:index2], sep, path[index2 + 1:] 224 return path[:index2], sep, path[index2 + 1:] 225 drv = root = '' 226 if second == ':' and first in self.drive_letters: 227 drv = path[:2] 228 path = path[2:] 229 first = third 230 if first == sep: 231 root = first 232 path = path.lstrip(sep) 233 return prefix + drv, root, path 234 235 @staticmethod 236 def _splitroot_posix(path, sep): 237 if path and path[0] == sep: 238 stripped_part = path.lstrip(sep) 239 if len(path) - len(stripped_part) == 2: 240 return '', sep * 2, stripped_part 241 return '', sep, stripped_part 242 else: 243 return '', '', path 244 245 def splitroot(self, path, sep=None): 246 """Split path into drive, root and rest.""" 247 if sep is None: 248 sep = self.filesystem.path_separator 249 if self.filesystem.is_windows_fs: 250 return self._splitroot_with_drive(path, sep) 251 return self._splitroot_posix(path, sep) 252 253 def casefold(self, path): 254 """Return the lower-case version of s for a Windows filesystem.""" 255 if self.filesystem.is_windows_fs: 256 return path.lower() 257 return path 258 259 def casefold_parts(self, parts): 260 """Return the lower-case version of parts for a Windows filesystem.""" 261 if self.filesystem.is_windows_fs: 262 return [p.lower() for p in parts] 263 return parts 264 265 def _resolve_posix(self, path, strict): 266 sep = self.sep 267 seen = {} 268 269 def _resolve(path, rest): 270 if rest.startswith(sep): 271 path = '' 272 273 for name in rest.split(sep): 274 if not name or name == '.': 275 # current dir 276 continue 277 if name == '..': 278 # parent dir 279 path, _, _ = path.rpartition(sep) 280 continue 281 newpath = path + sep + name 282 if newpath in seen: 283 # Already seen this path 284 path = seen[newpath] 285 if path is not None: 286 # use cached value 287 continue 288 # The symlink is not resolved, so we must have 289 # a symlink loop. 290 raise RuntimeError("Symlink loop from %r" % newpath) 291 # Resolve the symbolic link 292 try: 293 target = self.filesystem.readlink(newpath) 294 except OSError as e: 295 if e.errno != errno.EINVAL and strict: 296 raise 297 # Not a symlink, or non-strict mode. We just leave the path 298 # untouched. 299 path = newpath 300 else: 301 seen[newpath] = None # not resolved symlink 302 path = _resolve(path, target) 303 seen[newpath] = path # resolved symlink 304 305 return path 306 307 # NOTE: according to POSIX, getcwd() cannot contain path components 308 # which are symlinks. 309 base = '' if path.is_absolute() else self.filesystem.cwd 310 return _resolve(base, str(path)) or sep 311 312 def _resolve_windows(self, path, strict): 313 path = str(path) 314 if not path: 315 return os.getcwd() 316 previous_s = None 317 if strict: 318 if not self.filesystem.exists(path): 319 self.filesystem.raise_os_error(errno.ENOENT, path) 320 return self.filesystem.resolve_path(path) 321 else: 322 while True: 323 try: 324 path = self.filesystem.resolve_path(path) 325 except OSError: 326 previous_s = path 327 path = self.filesystem.splitpath(path)[0] 328 else: 329 if previous_s is None: 330 return path 331 return self.filesystem.joinpaths( 332 path, os.path.basename(previous_s)) 333 334 def resolve(self, path, strict): 335 """Make the path absolute, resolving any symlinks.""" 336 if self.filesystem.is_windows_fs: 337 return self._resolve_windows(path, strict) 338 return self._resolve_posix(path, strict) 339 340 def gethomedir(self, username): 341 """Return the home directory of the current user.""" 342 if not username: 343 try: 344 return os.environ['HOME'] 345 except KeyError: 346 import pwd 347 return pwd.getpwuid(os.getuid()).pw_dir 348 else: 349 import pwd 350 try: 351 return pwd.getpwnam(username).pw_dir 352 except KeyError: 353 raise RuntimeError("Can't determine home directory " 354 "for %r" % username) 355 356 357class _FakeWindowsFlavour(_FakeFlavour): 358 """Flavour used by PureWindowsPath with some Windows specific 359 implementations independent of FakeFilesystem properties. 360 """ 361 reserved_names = ( 362 {'CON', 'PRN', 'AUX', 'NUL'} | 363 {'COM%d' % i for i in range(1, 10)} | 364 {'LPT%d' % i for i in range(1, 10)} 365 ) 366 367 def is_reserved(self, parts): 368 """Return True if the path is considered reserved under Windows.""" 369 370 # NOTE: the rules for reserved names seem somewhat complicated 371 # (e.g. r"..\NUL" is reserved but not r"foo\NUL"). 372 # We err on the side of caution and return True for paths which are 373 # not considered reserved by Windows. 374 if not parts: 375 return False 376 if self.filesystem.is_windows_fs and parts[0].startswith('\\\\'): 377 # UNC paths are never reserved 378 return False 379 return parts[-1].partition('.')[0].upper() in self.reserved_names 380 381 def make_uri(self, path): 382 """Return a file URI for the given path""" 383 384 # Under Windows, file URIs use the UTF-8 encoding. 385 # original version, not faked 386 drive = path.drive 387 if len(drive) == 2 and drive[1] == ':': 388 # It's a path on a local drive => 'file:///c:/a/b' 389 rest = path.as_posix()[2:].lstrip('/') 390 return 'file:///%s/%s' % ( 391 drive, urlquote_from_bytes(rest.encode('utf-8'))) 392 else: 393 # It's a path on a network drive => 'file://host/share/a/b' 394 return ('file:' + 395 urlquote_from_bytes(path.as_posix().encode('utf-8'))) 396 397 def gethomedir(self, username): 398 """Return the home directory of the current user.""" 399 400 # original version, not faked 401 if 'HOME' in os.environ: 402 userhome = os.environ['HOME'] 403 elif 'USERPROFILE' in os.environ: 404 userhome = os.environ['USERPROFILE'] 405 elif 'HOMEPATH' in os.environ: 406 try: 407 drv = os.environ['HOMEDRIVE'] 408 except KeyError: 409 drv = '' 410 userhome = drv + os.environ['HOMEPATH'] 411 else: 412 raise RuntimeError("Can't determine home directory") 413 414 if username: 415 # Try to guess user home directory. By default all users 416 # directories are located in the same place and are named by 417 # corresponding usernames. If current user home directory points 418 # to nonstandard place, this guess is likely wrong. 419 if os.environ['USERNAME'] != username: 420 drv, root, parts = self.parse_parts((userhome,)) 421 if parts[-1] != os.environ['USERNAME']: 422 raise RuntimeError("Can't determine home directory " 423 "for %r" % username) 424 parts[-1] = username 425 if drv or root: 426 userhome = drv + root + self.join(parts[1:]) 427 else: 428 userhome = self.join(parts) 429 return userhome 430 431 def compile_pattern(self, pattern): 432 return re.compile(fnmatch.translate(pattern), re.IGNORECASE).fullmatch 433 434 435class _FakePosixFlavour(_FakeFlavour): 436 """Flavour used by PurePosixPath with some Unix specific implementations 437 independent of FakeFilesystem properties. 438 """ 439 440 def is_reserved(self, parts): 441 return False 442 443 def make_uri(self, path): 444 # We represent the path using the local filesystem encoding, 445 # for portability to other applications. 446 bpath = bytes(path) 447 return 'file://' + urlquote_from_bytes(bpath) 448 449 def gethomedir(self, username): 450 # original version, not faked 451 if not username: 452 try: 453 return os.environ['HOME'] 454 except KeyError: 455 import pwd 456 return pwd.getpwuid(os.getuid()).pw_dir 457 else: 458 import pwd 459 try: 460 return pwd.getpwnam(username).pw_dir 461 except KeyError: 462 raise RuntimeError("Can't determine home directory " 463 "for %r" % username) 464 465 def compile_pattern(self, pattern): 466 return re.compile(fnmatch.translate(pattern)).fullmatch 467 468 469class FakePath(pathlib.Path): 470 """Replacement for pathlib.Path. Reimplement some methods to use 471 fake filesystem. The rest of the methods work as they are, as they will 472 use the fake accessor. 473 New in pyfakefs 3.0. 474 """ 475 476 # the underlying fake filesystem 477 filesystem = None 478 479 def __new__(cls, *args, **kwargs): 480 """Creates the correct subclass based on OS.""" 481 if cls is FakePathlibModule.Path: 482 cls = (FakePathlibModule.WindowsPath 483 if cls.filesystem.is_windows_fs 484 else FakePathlibModule.PosixPath) 485 self = cls._from_parts(args) 486 return self 487 488 @classmethod 489 def _from_parts(cls, args, init=False): # pylint: disable=unused-argument 490 # Overwritten to call _init to set the fake accessor, 491 # which is not done since Python 3.10 492 self = object.__new__(cls) 493 self._init() 494 drv, root, parts = self._parse_args(args) 495 self._drv = drv 496 self._root = root 497 self._parts = parts 498 return self 499 500 @classmethod 501 def _from_parsed_parts(cls, drv, root, parts): 502 # Overwritten to call _init to set the fake accessor, 503 # which is not done since Python 3.10 504 self = object.__new__(cls) 505 self._init() 506 self._drv = drv 507 self._root = root 508 self._parts = parts 509 return self 510 511 def _init(self, template=None): 512 """Initializer called from base class.""" 513 self._accessor = _fake_accessor 514 self._closed = False 515 516 def _path(self): 517 """Returns the underlying path string as used by the fake filesystem. 518 """ 519 return str(self) 520 521 @classmethod 522 def cwd(cls): 523 """Return a new path pointing to the current working directory 524 (as returned by os.getcwd()). 525 """ 526 return cls(cls.filesystem.cwd) 527 528 def resolve(self, strict=None): 529 """Make the path absolute, resolving all symlinks on the way and also 530 normalizing it (for example turning slashes into backslashes 531 under Windows). 532 533 Args: 534 strict: If False (default) no exception is raised if the path 535 does not exist. 536 New in Python 3.6. 537 538 Raises: 539 OSError: if the path doesn't exist (strict=True or Python < 3.6) 540 """ 541 if sys.version_info >= (3, 6): 542 if strict is None: 543 strict = False 544 else: 545 if strict is not None: 546 raise TypeError( 547 "resolve() got an unexpected keyword argument 'strict'") 548 strict = True 549 if self._closed: 550 self._raise_closed() 551 path = self._flavour.resolve(self, strict=strict) 552 if path is None: 553 self.stat() 554 path = str(self.absolute()) 555 path = self.filesystem.absnormpath(path) 556 return FakePath(path) 557 558 def open(self, mode='r', buffering=-1, encoding=None, 559 errors=None, newline=None): 560 """Open the file pointed by this path and return a fake file object. 561 562 Raises: 563 OSError: if the target object is a directory, the path is invalid 564 or permission is denied. 565 """ 566 if self._closed: 567 self._raise_closed() 568 return FakeFileOpen(self.filesystem)( 569 self._path(), mode, buffering, encoding, errors, newline) 570 571 def read_bytes(self): 572 """Open the fake file in bytes mode, read it, and close the file. 573 574 Raises: 575 OSError: if the target object is a directory, the path is 576 invalid or permission is denied. 577 """ 578 with FakeFileOpen(self.filesystem)(self._path(), mode='rb') as f: 579 return f.read() 580 581 def read_text(self, encoding=None, errors=None): 582 """ 583 Open the fake file in text mode, read it, and close the file. 584 """ 585 with FakeFileOpen(self.filesystem)(self._path(), mode='r', 586 encoding=encoding, 587 errors=errors) as f: 588 return f.read() 589 590 def write_bytes(self, data): 591 """Open the fake file in bytes mode, write to it, and close the file. 592 Args: 593 data: the bytes to be written 594 Raises: 595 OSError: if the target object is a directory, the path is 596 invalid or permission is denied. 597 """ 598 # type-check for the buffer interface before truncating the file 599 view = memoryview(data) 600 with FakeFileOpen(self.filesystem)(self._path(), mode='wb') as f: 601 return f.write(view) 602 603 def write_text(self, data, encoding=None, errors=None, newline=None): 604 """Open the fake file in text mode, write to it, and close 605 the file. 606 607 Args: 608 data: the string to be written 609 encoding: the encoding used for the string; if not given, the 610 default locale encoding is used 611 errors: (str) Defines how encoding errors are handled. 612 newline: Controls universal newlines, passed to stream object. 613 New in Python 3.10. 614 Raises: 615 TypeError: if data is not of type 'str'. 616 OSError: if the target object is a directory, the path is 617 invalid or permission is denied. 618 """ 619 if not isinstance(data, str): 620 raise TypeError('data must be str, not %s' % 621 data.__class__.__name__) 622 if newline is not None and sys.version_info < (3, 10): 623 raise TypeError("write_text() got an unexpected " 624 "keyword argument 'newline'") 625 with FakeFileOpen(self.filesystem)(self._path(), 626 mode='w', 627 encoding=encoding, 628 errors=errors, 629 newline=newline) as f: 630 return f.write(data) 631 632 @classmethod 633 def home(cls): 634 """Return a new path pointing to the user's home directory (as 635 returned by os.path.expanduser('~')). 636 """ 637 home = os.path.expanduser("~") 638 if cls.filesystem.is_windows_fs != (os.name == 'nt'): 639 username = os.path.split(home)[1] 640 if cls.filesystem.is_windows_fs: 641 home = os.path.join('C:', 'Users', username) 642 else: 643 home = os.path.join('home', username) 644 cls.filesystem.create_dir(home) 645 return cls(home.replace(os.sep, cls.filesystem.path_separator)) 646 647 def samefile(self, other_path): 648 """Return whether other_path is the same or not as this file 649 (as returned by os.path.samefile()). 650 651 Args: 652 other_path: A path object or string of the file object 653 to be compared with 654 655 Raises: 656 OSError: if the filesystem object doesn't exist. 657 """ 658 st = self.stat() 659 try: 660 other_st = other_path.stat() 661 except AttributeError: 662 other_st = self.filesystem.stat(other_path) 663 return (st.st_ino == other_st.st_ino and 664 st.st_dev == other_st.st_dev) 665 666 def expanduser(self): 667 """ Return a new path with expanded ~ and ~user constructs 668 (as returned by os.path.expanduser) 669 """ 670 return FakePath(os.path.expanduser(self._path()) 671 .replace(os.path.sep, 672 self.filesystem.path_separator)) 673 674 def touch(self, mode=0o666, exist_ok=True): 675 """Create a fake file for the path with the given access mode, 676 if it doesn't exist. 677 678 Args: 679 mode: the file mode for the file if it does not exist 680 exist_ok: if the file already exists and this is True, nothing 681 happens, otherwise FileExistError is raised 682 683 Raises: 684 FileExistsError: if the file exists and exits_ok is False. 685 """ 686 if self._closed: 687 self._raise_closed() 688 if self.exists(): 689 if exist_ok: 690 self.filesystem.utime(self._path(), times=None) 691 else: 692 self.filesystem.raise_os_error(errno.EEXIST, self._path()) 693 else: 694 fake_file = self.open('w') 695 fake_file.close() 696 self.chmod(mode) 697 698 699class FakePathlibModule: 700 """Uses FakeFilesystem to provide a fake pathlib module replacement. 701 Can be used to replace both the standard `pathlib` module and the 702 `pathlib2` package available on PyPi. 703 704 You need a fake_filesystem to use this: 705 `filesystem = fake_filesystem.FakeFilesystem()` 706 `fake_pathlib_module = fake_filesystem.FakePathlibModule(filesystem)` 707 """ 708 709 def __init__(self, filesystem): 710 """ 711 Initializes the module with the given filesystem. 712 713 Args: 714 filesystem: FakeFilesystem used to provide file system information 715 """ 716 init_module(filesystem) 717 self._pathlib_module = pathlib 718 719 class PurePosixPath(PurePath): 720 """A subclass of PurePath, that represents non-Windows filesystem 721 paths""" 722 __slots__ = () 723 724 class PureWindowsPath(PurePath): 725 """A subclass of PurePath, that represents Windows filesystem paths""" 726 __slots__ = () 727 728 class WindowsPath(FakePath, PureWindowsPath): 729 """A subclass of Path and PureWindowsPath that represents 730 concrete Windows filesystem paths. 731 """ 732 __slots__ = () 733 734 def owner(self): 735 raise NotImplementedError( 736 "Path.owner() is unsupported on this system") 737 738 def group(self): 739 raise NotImplementedError( 740 "Path.group() is unsupported on this system") 741 742 def is_mount(self): 743 raise NotImplementedError( 744 "Path.is_mount() is unsupported on this system") 745 746 class PosixPath(FakePath, PurePosixPath): 747 """A subclass of Path and PurePosixPath that represents 748 concrete non-Windows filesystem paths. 749 """ 750 __slots__ = () 751 752 def owner(self): 753 """Return the current user name. It is assumed that the fake 754 file system was created by the current user. 755 """ 756 import pwd 757 758 return pwd.getpwuid(os.getuid()).pw_name 759 760 def group(self): 761 """Return the current group name. It is assumed that the fake 762 file system was created by the current user. 763 """ 764 import grp 765 766 return grp.getgrgid(os.getgid()).gr_name 767 768 Path = FakePath 769 770 def __getattr__(self, name): 771 """Forwards any unfaked calls to the standard pathlib module.""" 772 return getattr(self._pathlib_module, name) 773 774 775class FakePathlibPathModule: 776 """Patches `pathlib.Path` by passing all calls to FakePathlibModule.""" 777 fake_pathlib = None 778 779 def __init__(self, filesystem=None): 780 if self.fake_pathlib is None: 781 self.__class__.fake_pathlib = FakePathlibModule(filesystem) 782 783 def __call__(self, *args, **kwargs): 784 return self.fake_pathlib.Path(*args, **kwargs) 785 786 def __getattr__(self, name): 787 return getattr(self.fake_pathlib.Path, name) 788 789 790class RealPath(pathlib.Path): 791 """Replacement for `pathlib.Path` if it shall not be faked. 792 Needed because `Path` in `pathlib` is always faked, even if `pathlib` 793 itself is not. 794 """ 795 796 def __new__(cls, *args, **kwargs): 797 """Creates the correct subclass based on OS.""" 798 if cls is RealPathlibModule.Path: 799 cls = (RealPathlibModule.WindowsPath if os.name == 'nt' 800 else RealPathlibModule.PosixPath) 801 self = cls._from_parts(args) 802 return self 803 804 805class RealPathlibModule: 806 """Used to replace `pathlib` for skipped modules. 807 As the original `pathlib` is always patched to use the fake path, 808 we need to provide a version which does not do this. 809 """ 810 811 def __init__(self): 812 RealPathlibModule.PureWindowsPath._flavour = pathlib._WindowsFlavour() 813 RealPathlibModule.PurePosixPath._flavour = pathlib._PosixFlavour() 814 self._pathlib_module = pathlib 815 816 class PurePosixPath(PurePath): 817 """A subclass of PurePath, that represents Posix filesystem paths""" 818 __slots__ = () 819 820 class PureWindowsPath(PurePath): 821 """A subclass of PurePath, that represents Windows filesystem paths""" 822 __slots__ = () 823 824 if sys.platform == 'win32': 825 class WindowsPath(RealPath, PureWindowsPath): 826 """A subclass of Path and PureWindowsPath that represents 827 concrete Windows filesystem paths. 828 """ 829 __slots__ = () 830 else: 831 class PosixPath(RealPath, PurePosixPath): 832 """A subclass of Path and PurePosixPath that represents 833 concrete non-Windows filesystem paths. 834 """ 835 __slots__ = () 836 837 Path = RealPath 838 839 def __getattr__(self, name): 840 """Forwards any unfaked calls to the standard pathlib module.""" 841 return getattr(self._pathlib_module, name) 842 843 844class RealPathlibPathModule: 845 """Patches `pathlib.Path` by passing all calls to RealPathlibModule.""" 846 real_pathlib = None 847 848 @classmethod 849 def __instancecheck__(cls, instance): 850 # as we cannot derive from pathlib.Path, we fake 851 # the inheritance to pass isinstance checks - see #666 852 return isinstance(instance, PurePath) 853 854 def __init__(self): 855 if self.real_pathlib is None: 856 self.__class__.real_pathlib = RealPathlibModule() 857 858 def __call__(self, *args, **kwargs): 859 return self.real_pathlib.Path(*args, **kwargs) 860 861 def __getattr__(self, name): 862 return getattr(self.real_pathlib.Path, name) 863