1# Copyright 2009 Google Inc. All Rights Reserved. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15"""Uses :py:class:`FakeOsModule` to provide a 16fake :py:mod:`os` module replacement. 17""" 18 19import errno 20import functools 21import inspect 22import os 23import sys 24import uuid 25from contextlib import contextmanager 26from stat import ( 27 S_IFREG, 28 S_IFSOCK, 29) 30from typing import ( 31 List, 32 Optional, 33 Callable, 34 Union, 35 Any, 36 Tuple, 37 cast, 38 AnyStr, 39 TYPE_CHECKING, 40 Set, 41) 42 43from pyfakefs.fake_file import ( 44 FakeDirectory, 45 FakeDirWrapper, 46 StandardStreamWrapper, 47 FakeFileWrapper, 48 FakePipeWrapper, 49 FakeFile, 50 AnyFileWrapper, 51) 52from pyfakefs.fake_open import FakeFileOpen, _OpenModes 53from pyfakefs.fake_path import FakePathModule 54from pyfakefs.fake_scandir import scandir, walk, ScanDirIter 55from pyfakefs.helpers import ( 56 FakeStatResult, 57 is_called_from_skipped_module, 58 is_int_type, 59 is_byte_string, 60 make_string_path, 61 IS_PYPY, 62 to_string, 63 matching_string, 64 AnyString, 65 to_bytes, 66 PERM_EXE, 67 PERM_DEF, 68 is_root, 69 get_uid, 70 get_gid, 71) 72 73if TYPE_CHECKING: 74 from pyfakefs.fake_filesystem import FakeFilesystem 75 76NR_STD_STREAMS = 3 77 78 79class FakeOsModule: 80 """Uses FakeFilesystem to provide a fake os module replacement. 81 82 Do not create os.path separately from os, as there is a necessary circular 83 dependency between os and os.path to replicate the behavior of the standard 84 Python modules. What you want to do is to just let FakeOsModule take care 85 of `os.path` setup itself. 86 87 # You always want to do this. 88 filesystem = fake_filesystem.FakeFilesystem() 89 my_os_module = fake_os.FakeOsModule(filesystem) 90 """ 91 92 use_original = False 93 94 @staticmethod 95 def dir() -> List[str]: 96 """Return the list of patched function names. Used for patching 97 functions imported from the module. 98 """ 99 _dir = [ 100 "access", 101 "chdir", 102 "chmod", 103 "chown", 104 "close", 105 "dup", 106 "dup2", 107 "fstat", 108 "fsync", 109 "getcwd", 110 "lchmod", 111 "link", 112 "listdir", 113 "lseek", 114 "lstat", 115 "makedirs", 116 "mkdir", 117 "mknod", 118 "open", 119 "read", 120 "readlink", 121 "remove", 122 "removedirs", 123 "rename", 124 "rmdir", 125 "scandir", 126 "stat", 127 "symlink", 128 "umask", 129 "unlink", 130 "utime", 131 "walk", 132 "write", 133 "getcwdb", 134 "replace", 135 ] 136 if sys.platform.startswith("linux"): 137 _dir += [ 138 "fdatasync", 139 "getxattr", 140 "listxattr", 141 "removexattr", 142 "setxattr", 143 ] 144 if sys.platform != "win32": 145 _dir += [ 146 "getgid", 147 "getuid", 148 ] 149 return _dir 150 151 def __init__(self, filesystem: "FakeFilesystem"): 152 """Also exposes self.path (to fake os.path). 153 154 Args: 155 filesystem: FakeFilesystem used to provide file system information 156 """ 157 self.filesystem = filesystem 158 self.os_module: Any = os 159 self.path = FakePathModule(self.filesystem, self) 160 self._supports_follow_symlinks: Optional[Set] = None 161 self._supports_dir_fd: Optional[Set] = None 162 self._supports_effective_ids: Optional[Set] = None 163 self._supports_fd: Optional[Set] = None 164 165 @property 166 def devnull(self) -> str: 167 return self.path.devnull 168 169 @property 170 def sep(self) -> str: 171 return self.path.sep 172 173 @property 174 def altsep(self) -> Optional[str]: 175 return self.path.altsep 176 177 @property 178 def linesep(self) -> str: 179 return self.path.linesep 180 181 @property 182 def pathsep(self) -> str: 183 return self.path.pathsep 184 185 def fdopen(self, fd: int, *args: Any, **kwargs: Any) -> AnyFileWrapper: 186 """Redirector to open() builtin function. 187 188 Args: 189 fd: The file descriptor of the file to open. 190 *args: Pass through args. 191 **kwargs: Pass through kwargs. 192 193 Returns: 194 File object corresponding to file_des. 195 196 Raises: 197 TypeError: if file descriptor is not an integer. 198 """ 199 if not is_int_type(fd): 200 raise TypeError("an integer is required") 201 return FakeFileOpen(self.filesystem)(fd, *args, **kwargs) 202 203 def _umask(self) -> int: 204 """Return the current umask.""" 205 if self.filesystem.is_windows_fs: 206 # windows always returns 0 - it has no real notion of umask 207 return 0 208 if sys.platform == "win32": 209 # if we are testing Unix under Windows we assume a default mask 210 return 0o002 211 else: 212 # under Unix, we return the real umask; 213 # there is no pure getter for umask, so we have to first 214 # set a mode to get the previous one and then re-set that 215 mask = os.umask(0) 216 os.umask(mask) 217 return mask 218 219 def open( 220 self, 221 path: AnyStr, 222 flags: int, 223 mode: Optional[int] = None, 224 *, 225 dir_fd: Optional[int] = None, 226 ) -> int: 227 """Return the file descriptor for a FakeFile. 228 229 Args: 230 path: the path to the file 231 flags: low-level bits to indicate io operation 232 mode: bits to define default permissions 233 Note: only basic modes are supported, OS-specific modes are 234 ignored 235 dir_fd: If not `None`, the file descriptor of a directory, 236 with `file_path` being relative to this directory. 237 238 Returns: 239 A file descriptor. 240 241 Raises: 242 OSError: if the path cannot be found 243 ValueError: if invalid mode is given 244 NotImplementedError: if `os.O_EXCL` is used without `os.O_CREAT` 245 """ 246 path = self._path_with_dir_fd(path, self.open, dir_fd) 247 if mode is None: 248 if self.filesystem.is_windows_fs: 249 mode = 0o666 250 else: 251 mode = 0o777 & ~self._umask() 252 253 has_directory_flag = ( 254 hasattr(os, "O_DIRECTORY") and flags & os.O_DIRECTORY == os.O_DIRECTORY 255 ) 256 if ( 257 has_directory_flag 258 and self.filesystem.exists(path) 259 and not self.filesystem.isdir(path) 260 ): 261 raise OSError(errno.ENOTDIR, "path is not a directory", path) 262 263 has_follow_flag = ( 264 hasattr(os, "O_NOFOLLOW") and flags & os.O_NOFOLLOW == os.O_NOFOLLOW 265 ) 266 if has_follow_flag and self.filesystem.islink(path): 267 raise OSError(errno.ELOOP, "path is a symlink", path) 268 269 has_tmpfile_flag = ( 270 hasattr(os, "O_TMPFILE") and flags & os.O_TMPFILE == os.O_TMPFILE 271 ) 272 open_modes = _OpenModes( 273 must_exist=not flags & os.O_CREAT and not has_tmpfile_flag, 274 can_read=not flags & os.O_WRONLY, 275 can_write=flags & (os.O_RDWR | os.O_WRONLY) != 0, 276 truncate=flags & os.O_TRUNC != 0, 277 append=flags & os.O_APPEND != 0, 278 must_not_exist=flags & os.O_EXCL != 0, 279 ) 280 if open_modes.must_not_exist and open_modes.must_exist: 281 raise NotImplementedError("O_EXCL without O_CREAT mode is not supported") 282 if has_tmpfile_flag: 283 # this is a workaround for tempfiles that do not have a filename 284 # as we do not support this directly, we just add a unique filename 285 # and set the file to delete on close 286 path = self.filesystem.joinpaths( 287 path, matching_string(path, str(uuid.uuid4())) 288 ) 289 290 if not self.filesystem.is_windows_fs and self.filesystem.exists(path): 291 # handle opening directory - only allowed under Posix 292 # with read-only mode 293 obj = self.filesystem.resolve(path) 294 if isinstance(obj, FakeDirectory): 295 if ( 296 not open_modes.must_exist and not self.filesystem.is_macos 297 ) or open_modes.can_write: 298 self.filesystem.raise_os_error(errno.EISDIR, path) 299 dir_wrapper = FakeDirWrapper(obj, path, self.filesystem) 300 file_des = self.filesystem.add_open_file(dir_wrapper) 301 dir_wrapper.filedes = file_des 302 return file_des 303 304 # low level open is always binary 305 str_flags = "b" 306 delete_on_close = has_tmpfile_flag 307 if hasattr(os, "O_TEMPORARY"): 308 delete_on_close = flags & os.O_TEMPORARY == os.O_TEMPORARY 309 fake_file = FakeFileOpen( 310 self.filesystem, delete_on_close=delete_on_close, raw_io=True 311 )(path, str_flags, open_modes=open_modes) 312 assert not isinstance(fake_file, StandardStreamWrapper) 313 if fake_file.file_object != self.filesystem.dev_null: 314 self.chmod(path, mode) 315 return fake_file.fileno() 316 317 def close(self, fd: int) -> None: 318 """Close a file descriptor. 319 320 Args: 321 fd: An integer file descriptor for the file object requested. 322 323 Raises: 324 OSError: bad file descriptor. 325 TypeError: if file descriptor is not an integer. 326 """ 327 file_handle = self.filesystem.get_open_file(fd) 328 file_handle.close_fd(fd) 329 330 def dup(self, fd: int) -> int: 331 file_handle = self.filesystem.get_open_file(fd) 332 return self.filesystem.add_open_file(file_handle) 333 334 def dup2(self, fd: int, fd2: int, inheritable: bool = True) -> int: 335 if fd == fd2: 336 return fd 337 file_handle = self.filesystem.get_open_file(fd) 338 return self.filesystem.add_open_file(file_handle, fd2) 339 340 def read(self, fd: int, n: int) -> bytes: 341 """Read number of bytes from a file descriptor, returns bytes read. 342 343 Args: 344 fd: An integer file descriptor for the file object requested. 345 n: Number of bytes to read from file. 346 347 Returns: 348 Bytes read from file. 349 350 Raises: 351 OSError: bad file descriptor. 352 TypeError: if file descriptor is not an integer. 353 """ 354 file_handle = self.filesystem.get_open_file(fd) 355 if isinstance(file_handle, FakeFileWrapper): 356 file_handle.raw_io = True 357 if isinstance(file_handle, FakeDirWrapper): 358 self.filesystem.raise_os_error(errno.EBADF, file_handle.file_path) 359 return file_handle.read(n) 360 361 def write(self, fd: int, contents: bytes) -> int: 362 """Write string to file descriptor, returns number of bytes written. 363 364 Args: 365 fd: An integer file descriptor for the file object requested. 366 contents: String of bytes to write to file. 367 368 Returns: 369 Number of bytes written. 370 371 Raises: 372 OSError: bad file descriptor. 373 TypeError: if file descriptor is not an integer. 374 """ 375 file_handle = cast(FakeFileWrapper, self.filesystem.get_open_file(fd)) 376 if isinstance(file_handle, FakeDirWrapper): 377 self.filesystem.raise_os_error(errno.EBADF, file_handle.file_path) 378 379 if isinstance(file_handle, FakePipeWrapper): 380 return file_handle.write(contents) 381 382 file_handle.raw_io = True 383 file_handle._sync_io() 384 file_handle.update_flush_pos() 385 file_handle.write(contents) 386 file_handle.flush() 387 return len(contents) 388 389 def lseek(self, fd: int, pos: int, whence: int): 390 file_handle = self.filesystem.get_open_file(fd) 391 if isinstance(file_handle, FakeFileWrapper): 392 file_handle.seek(pos, whence) 393 else: 394 raise OSError(errno.EBADF, "Bad file descriptor for fseek") 395 396 def pipe(self) -> Tuple[int, int]: 397 read_fd, write_fd = os.pipe() 398 read_wrapper = FakePipeWrapper(self.filesystem, read_fd, False) 399 file_des = self.filesystem.add_open_file(read_wrapper) 400 read_wrapper.filedes = file_des 401 write_wrapper = FakePipeWrapper(self.filesystem, write_fd, True) 402 file_des = self.filesystem.add_open_file(write_wrapper) 403 write_wrapper.filedes = file_des 404 return read_wrapper.filedes, write_wrapper.filedes 405 406 def fstat(self, fd: int) -> FakeStatResult: 407 """Return the os.stat-like tuple for the FakeFile object of file_des. 408 409 Args: 410 fd: The file descriptor of filesystem object to retrieve. 411 412 Returns: 413 The FakeStatResult object corresponding to entry_path. 414 415 Raises: 416 OSError: if the filesystem object doesn't exist. 417 """ 418 # stat should return the tuple representing return value of os.stat 419 file_object = self.filesystem.get_open_file(fd).get_object() 420 assert isinstance(file_object, FakeFile) 421 return file_object.stat_result.copy() 422 423 def umask(self, mask: int) -> int: 424 """Change the current umask. 425 426 Args: 427 mask: (int) The new umask value. 428 429 Returns: 430 The old umask. 431 432 Raises: 433 TypeError: if new_mask is of an invalid type. 434 """ 435 if not is_int_type(mask): 436 raise TypeError("an integer is required") 437 old_umask = self.filesystem.umask 438 self.filesystem.umask = mask 439 return old_umask 440 441 def chdir(self, path: AnyStr) -> None: 442 """Change current working directory to target directory. 443 444 Args: 445 path: The path to new current working directory. 446 447 Raises: 448 OSError: if user lacks permission to enter the argument directory 449 or if the target is not a directory. 450 """ 451 try: 452 path = self.filesystem.resolve_path(path, allow_fd=True) 453 except OSError as exc: 454 if self.filesystem.is_macos and exc.errno == errno.EBADF: 455 raise OSError(errno.ENOTDIR, "Not a directory: " + str(path)) 456 raise 457 self.filesystem.confirmdir(path) 458 directory = self.filesystem.resolve(path) 459 # A full implementation would check permissions all the way 460 # up the tree. 461 if not is_root() and not directory.has_permission(PERM_EXE): 462 self.filesystem.raise_os_error(errno.EACCES, directory.name) 463 self.filesystem.cwd = path # type: ignore[assignment] 464 465 def getcwd(self) -> str: 466 """Return current working directory.""" 467 return to_string(self.filesystem.cwd) 468 469 def getcwdb(self) -> bytes: 470 """Return current working directory as bytes.""" 471 return to_bytes(self.filesystem.cwd) 472 473 def listdir(self, path: AnyStr) -> List[AnyStr]: 474 """Return a list of file names in target_directory. 475 476 Args: 477 path: Path to the target directory within the fake 478 filesystem. 479 480 Returns: 481 A list of file names within the target directory in arbitrary 482 order. 483 484 Raises: 485 OSError: if the target is not a directory. 486 """ 487 return self.filesystem.listdir(path) 488 489 XATTR_CREATE = 1 490 XATTR_REPLACE = 2 491 492 def getxattr( 493 self, path: AnyStr, attribute: AnyString, *, follow_symlinks: bool = True 494 ) -> Optional[bytes]: 495 """Return the value of the given extended filesystem attribute for 496 `path`. 497 498 Args: 499 path: File path, file descriptor or path-like object. 500 attribute: (str or bytes) The attribute name. 501 follow_symlinks: (bool) If True (the default), symlinks in the 502 path are traversed. 503 504 Returns: 505 The contents of the extended attribute as bytes or None if 506 the attribute does not exist. 507 508 Raises: 509 OSError: if the path does not exist. 510 """ 511 if not self.filesystem.is_linux: 512 raise AttributeError("module 'os' has no attribute 'getxattr'") 513 514 if isinstance(attribute, bytes): 515 attribute = attribute.decode(sys.getfilesystemencoding()) 516 file_obj = self.filesystem.resolve(path, follow_symlinks, allow_fd=True) 517 if attribute not in file_obj.xattr: 518 raise OSError(errno.ENODATA, "No data available", path) 519 return file_obj.xattr.get(attribute) 520 521 def listxattr( 522 self, path: Optional[AnyStr] = None, *, follow_symlinks: bool = True 523 ) -> List[str]: 524 """Return a list of the extended filesystem attributes on `path`. 525 526 Args: 527 path: File path, file descriptor or path-like object. 528 If None, the current directory is used. 529 follow_symlinks: (bool) If True (the default), symlinks in the 530 path are traversed. 531 532 Returns: 533 A list of all attribute names for the given path as str. 534 535 Raises: 536 OSError: if the path does not exist. 537 """ 538 if not self.filesystem.is_linux: 539 raise AttributeError("module 'os' has no attribute 'listxattr'") 540 541 path_str = self.filesystem.cwd if path is None else path 542 file_obj = self.filesystem.resolve( 543 cast(AnyStr, path_str), # pytype: disable=invalid-annotation 544 follow_symlinks, 545 allow_fd=True, 546 ) 547 return list(file_obj.xattr.keys()) 548 549 def removexattr( 550 self, path: AnyStr, attribute: AnyString, *, follow_symlinks: bool = True 551 ) -> None: 552 """Removes the extended filesystem attribute from `path`. 553 554 Args: 555 path: File path, file descriptor or path-like object 556 attribute: (str or bytes) The attribute name. 557 follow_symlinks: (bool) If True (the default), symlinks in the 558 path are traversed. 559 560 Raises: 561 OSError: if the path does not exist. 562 """ 563 if not self.filesystem.is_linux: 564 raise AttributeError("module 'os' has no attribute 'removexattr'") 565 566 if isinstance(attribute, bytes): 567 attribute = attribute.decode(sys.getfilesystemencoding()) 568 file_obj = self.filesystem.resolve(path, follow_symlinks, allow_fd=True) 569 if attribute in file_obj.xattr: 570 del file_obj.xattr[attribute] 571 572 def setxattr( 573 self, 574 path: AnyStr, 575 attribute: AnyString, 576 value: bytes, 577 flags: int = 0, 578 *, 579 follow_symlinks: bool = True, 580 ) -> None: 581 """Sets the value of the given extended filesystem attribute for 582 `path`. 583 584 Args: 585 path: File path, file descriptor or path-like object. 586 attribute: The attribute name (str or bytes). 587 value: (byte-like) The value to be set. 588 follow_symlinks: (bool) If True (the default), symlinks in the 589 path are traversed. 590 591 Raises: 592 OSError: if the path does not exist. 593 TypeError: if `value` is not a byte-like object. 594 """ 595 if not self.filesystem.is_linux: 596 raise AttributeError("module 'os' has no attribute 'setxattr'") 597 598 if isinstance(attribute, bytes): 599 attribute = attribute.decode(sys.getfilesystemencoding()) 600 if not is_byte_string(value): 601 raise TypeError("a bytes-like object is required") 602 file_obj = self.filesystem.resolve(path, follow_symlinks, allow_fd=True) 603 exists = attribute in file_obj.xattr 604 if exists and flags == self.XATTR_CREATE: 605 self.filesystem.raise_os_error(errno.ENODATA, file_obj.path) 606 if not exists and flags == self.XATTR_REPLACE: 607 self.filesystem.raise_os_error(errno.EEXIST, file_obj.path) 608 file_obj.xattr[attribute] = value 609 610 def scandir(self, path: str = ".") -> ScanDirIter: 611 """Return an iterator of DirEntry objects corresponding to the 612 entries in the directory given by path. 613 614 Args: 615 path: Path to the target directory within the fake filesystem. 616 617 Returns: 618 An iterator to an unsorted list of os.DirEntry objects for 619 each entry in path. 620 621 Raises: 622 OSError: if the target is not a directory. 623 """ 624 return scandir(self.filesystem, path) 625 626 def walk( 627 self, 628 top: AnyStr, 629 topdown: bool = True, 630 onerror: Optional[bool] = None, 631 followlinks: bool = False, 632 ): 633 """Perform an os.walk operation over the fake filesystem. 634 635 Args: 636 top: The root directory from which to begin walk. 637 topdown: Determines whether to return the tuples with the root as 638 the first entry (`True`) or as the last, after all the child 639 directory tuples (`False`). 640 onerror: If not `None`, function which will be called to handle the 641 `os.error` instance provided when `os.listdir()` fails. 642 followlinks: If `True`, symbolic links are followed. 643 644 Yields: 645 (path, directories, nondirectories) for top and each of its 646 subdirectories. See the documentation for the builtin os module 647 for further details. 648 """ 649 return walk(self.filesystem, top, topdown, onerror, followlinks) 650 651 def readlink(self, path: AnyStr, dir_fd: Optional[int] = None) -> str: 652 """Read the target of a symlink. 653 654 Args: 655 path: Symlink to read the target of. 656 dir_fd: If not `None`, the file descriptor of a directory, 657 with `path` being relative to this directory. 658 659 Returns: 660 the string representing the path to which the symbolic link points. 661 662 Raises: 663 TypeError: if `path` is None 664 OSError: (with errno=ENOENT) if path is not a valid path, or 665 (with errno=EINVAL) if path is valid, but is not a symlink 666 """ 667 path = self._path_with_dir_fd(path, self.readlink, dir_fd) 668 return self.filesystem.readlink(path) 669 670 def stat( 671 self, 672 path: AnyStr, 673 *, 674 dir_fd: Optional[int] = None, 675 follow_symlinks: bool = True, 676 ) -> FakeStatResult: 677 """Return the os.stat-like tuple for the FakeFile object of entry_path. 678 679 Args: 680 path: path to filesystem object to retrieve. 681 dir_fd: (int) If not `None`, the file descriptor of a directory, 682 with `entry_path` being relative to this directory. 683 follow_symlinks: (bool) If `False` and `entry_path` points to a 684 symlink, the link itself is changed instead of the linked 685 object. 686 687 Returns: 688 The FakeStatResult object corresponding to entry_path. 689 690 Raises: 691 OSError: if the filesystem object doesn't exist. 692 """ 693 path = self._path_with_dir_fd(path, self.stat, dir_fd) 694 return self.filesystem.stat(path, follow_symlinks) 695 696 def lstat(self, path: AnyStr, *, dir_fd: Optional[int] = None) -> FakeStatResult: 697 """Return the os.stat-like tuple for entry_path, 698 not following symlinks. 699 700 Args: 701 path: path to filesystem object to retrieve. 702 dir_fd: If not `None`, the file descriptor of a directory, with 703 `path` being relative to this directory. 704 705 Returns: 706 the FakeStatResult object corresponding to `path`. 707 708 Raises: 709 OSError: if the filesystem object doesn't exist. 710 """ 711 # stat should return the tuple representing return value of os.stat 712 path = self._path_with_dir_fd(path, self.lstat, dir_fd, check_supported=False) 713 return self.filesystem.stat(path, follow_symlinks=False) 714 715 def remove(self, path: AnyStr, dir_fd: Optional[int] = None) -> None: 716 """Remove the FakeFile object at the specified file path. 717 718 Args: 719 path: Path to file to be removed. 720 dir_fd: If not `None`, the file descriptor of a directory, 721 with `path` being relative to this directory. 722 723 Raises: 724 OSError: if path points to a directory. 725 OSError: if path does not exist. 726 OSError: if removal failed. 727 """ 728 path = self._path_with_dir_fd(path, self.remove, dir_fd, check_supported=False) 729 self.filesystem.remove(path) 730 731 def unlink(self, path: AnyStr, *, dir_fd: Optional[int] = None) -> None: 732 """Remove the FakeFile object at the specified file path. 733 734 Args: 735 path: Path to file to be removed. 736 dir_fd: If not `None`, the file descriptor of a directory, 737 with `path` being relative to this directory. 738 739 Raises: 740 OSError: if path points to a directory. 741 OSError: if path does not exist. 742 OSError: if removal failed. 743 """ 744 path = self._path_with_dir_fd(path, self.unlink, dir_fd) 745 self.filesystem.remove(path) 746 747 def rename( 748 self, 749 src: AnyStr, 750 dst: AnyStr, 751 *, 752 src_dir_fd: Optional[int] = None, 753 dst_dir_fd: Optional[int] = None, 754 ) -> None: 755 """Rename a FakeFile object at old_file_path to new_file_path, 756 preserving all properties. 757 Also replaces existing new_file_path object, if one existed 758 (Unix only). 759 760 Args: 761 src: Path to filesystem object to rename. 762 dst: Path to where the filesystem object will live 763 after this call. 764 src_dir_fd: If not `None`, the file descriptor of a directory, 765 with `src` being relative to this directory. 766 dst_dir_fd: If not `None`, the file descriptor of a directory, 767 with `dst` being relative to this directory. 768 769 Raises: 770 OSError: if old_file_path does not exist. 771 OSError: if new_file_path is an existing directory. 772 OSError: if new_file_path is an existing file (Windows only) 773 OSError: if new_file_path is an existing file and could not 774 be removed (Unix) 775 OSError: if `dirname(new_file)` does not exist 776 OSError: if the file would be moved to another filesystem 777 (e.g. mount point) 778 """ 779 src = self._path_with_dir_fd(src, self.rename, src_dir_fd) 780 dst = self._path_with_dir_fd(dst, self.rename, dst_dir_fd) 781 self.filesystem.rename(src, dst) 782 783 def renames(self, old: AnyStr, new: AnyStr): 784 """Fakes `os.renames`, documentation taken from there. 785 786 Super-rename; create directories as necessary and delete any left 787 empty. Works like rename, except creation of any intermediate 788 directories needed to make the new pathname good is attempted 789 first. After the rename, directories corresponding to rightmost 790 path segments of the old name will be pruned until either the 791 whole path is consumed or a nonempty directory is found. 792 793 Note: this function can fail with the new directory structure made 794 if you lack permissions needed to unlink the leaf directory or 795 file. 796 797 """ 798 head, tail = self.filesystem.splitpath(new) 799 if head and tail and not self.filesystem.exists(head): 800 self.makedirs(head) 801 self.rename(old, new) 802 head, tail = self.filesystem.splitpath(old) 803 if head and tail: 804 try: 805 self.removedirs(head) 806 except OSError: 807 pass 808 809 def replace( 810 self, 811 src: AnyStr, 812 dst: AnyStr, 813 *, 814 src_dir_fd: Optional[int] = None, 815 dst_dir_fd: Optional[int] = None, 816 ) -> None: 817 """Renames a FakeFile object at old_file_path to new_file_path, 818 preserving all properties. 819 Also replaces existing new_file_path object, if one existed. 820 821 Arg 822 src: Path to filesystem object to rename. 823 dst: Path to where the filesystem object will live 824 after this call. 825 src_dir_fd: If not `None`, the file descriptor of a directory, 826 with `src` being relative to this directory. 827 dst_dir_fd: If not `None`, the file descriptor of a directory, 828 with `dst` being relative to this directory. 829 830 Raises: 831 OSError: if old_file_path does not exist. 832 OSError: if new_file_path is an existing directory. 833 OSError: if new_file_path is an existing file and could 834 not be removed 835 OSError: if `dirname(new_file)` does not exist 836 OSError: if the file would be moved to another filesystem 837 (e.g. mount point) 838 """ 839 src = self._path_with_dir_fd( 840 src, self.rename, src_dir_fd, check_supported=False 841 ) 842 dst = self._path_with_dir_fd( 843 dst, self.rename, dst_dir_fd, check_supported=False 844 ) 845 self.filesystem.rename(src, dst, force_replace=True) 846 847 def rmdir(self, path: AnyStr, *, dir_fd: Optional[int] = None) -> None: 848 """Remove a leaf Fake directory. 849 850 Args: 851 path: (str) Name of directory to remove. 852 dir_fd: If not `None`, the file descriptor of a directory, 853 with `path` being relative to this directory. 854 855 Raises: 856 OSError: if `path` does not exist or is not a directory, 857 or as per FakeFilesystem.remove_object. Cannot remove '.'. 858 """ 859 path = self._path_with_dir_fd(path, self.rmdir, dir_fd) 860 self.filesystem.rmdir(path) 861 862 def removedirs(self, name: AnyStr) -> None: 863 """Remove a leaf fake directory and all empty intermediate ones. 864 865 Args: 866 name: the directory to be removed. 867 868 Raises: 869 OSError: if target_directory does not exist or is not a directory. 870 OSError: if target_directory is not empty. 871 """ 872 name = self.filesystem.absnormpath(name) 873 directory = self.filesystem.confirmdir(name) 874 if directory.entries: 875 self.filesystem.raise_os_error(errno.ENOTEMPTY, self.path.basename(name)) 876 else: 877 self.rmdir(name) 878 head, tail = self.path.split(name) 879 if not tail: 880 head, tail = self.path.split(head) 881 while head and tail: 882 head_dir = self.filesystem.confirmdir(head) 883 if head_dir.entries: 884 break 885 # only the top-level dir may not be a symlink 886 self.filesystem.rmdir(head, allow_symlink=True) 887 head, tail = self.path.split(head) 888 889 def mkdir( 890 self, path: AnyStr, mode: int = PERM_DEF, *, dir_fd: Optional[int] = None 891 ) -> None: 892 """Create a leaf Fake directory. 893 894 Args: 895 path: (str) Name of directory to create. 896 Relative paths are assumed to be relative to '/'. 897 mode: (int) Mode to create directory with. This argument defaults 898 to 0o777. The umask is applied to this mode. 899 dir_fd: If not `None`, the file descriptor of a directory, 900 with `path` being relative to this directory. 901 902 Raises: 903 OSError: if the directory name is invalid or parent directory is 904 read only or as per FakeFilesystem.add_object. 905 """ 906 path = self._path_with_dir_fd(path, self.mkdir, dir_fd) 907 try: 908 self.filesystem.makedir(path, mode) 909 except OSError as e: 910 if e.errno == errno.EACCES: 911 self.filesystem.raise_os_error(e.errno, path) 912 raise 913 914 def makedirs( 915 self, name: AnyStr, mode: int = PERM_DEF, exist_ok: Optional[bool] = None 916 ) -> None: 917 """Create a leaf Fake directory + create any non-existent parent dirs. 918 919 Args: 920 name: (str) Name of directory to create. 921 mode: (int) Mode to create directory (and any necessary parent 922 directories) with. This argument defaults to 0o777. 923 The umask is applied to this mode. 924 exist_ok: (boolean) If exist_ok is False (the default), an OSError 925 is raised if the target directory already exists. 926 927 Raises: 928 OSError: if the directory already exists and exist_ok=False, or as 929 per :py:meth:`FakeFilesystem.create_dir`. 930 """ 931 if exist_ok is None: 932 exist_ok = False 933 934 # copied and adapted from real implementation in os.py (Python 3.12) 935 head, tail = self.filesystem.splitpath(name) 936 if not tail: 937 head, tail = self.filesystem.splitpath(head) 938 if head and tail and not self.filesystem.exists(head): 939 try: 940 self.makedirs(head, exist_ok=exist_ok) 941 except FileExistsError: 942 pass 943 cdir = self.filesystem.cwd 944 if isinstance(tail, bytes): 945 if tail == bytes(cdir, "ASCII"): 946 return 947 elif tail == cdir: 948 return 949 try: 950 self.mkdir(name, mode) 951 except OSError: 952 if not exist_ok or not self.filesystem.isdir(name): 953 raise 954 955 def _path_with_dir_fd( 956 self, 957 path: AnyStr, 958 fct: Callable, 959 dir_fd: Optional[int], 960 check_supported: bool = True, 961 ) -> AnyStr: 962 """Return the path considering dir_fd. Raise on invalid parameters.""" 963 try: 964 path = make_string_path(path) 965 except TypeError: 966 # the error is handled later 967 path = path 968 if dir_fd is not None: 969 # check if fd is supported for the built-in real function 970 if check_supported and (fct not in self.supports_dir_fd): 971 raise NotImplementedError("dir_fd unavailable on this platform") 972 if isinstance(path, int): 973 raise ValueError( 974 "%s: Can't specify dir_fd without matching path_str" % fct.__name__ 975 ) 976 if not self.path.isabs(path): 977 open_file = self.filesystem.get_open_file(dir_fd) 978 return self.path.join( # type: ignore[type-var, return-value] 979 cast(FakeFile, open_file.get_object()).path, path 980 ) 981 return path 982 983 def truncate(self, path: AnyStr, length: int) -> None: 984 """Truncate the file corresponding to path, so that it is 985 length bytes in size. If length is larger than the current size, 986 the file is filled up with zero bytes. 987 988 Args: 989 path: (str or int) Path to the file, or an integer file 990 descriptor for the file object. 991 length: (int) Length of the file after truncating it. 992 993 Raises: 994 OSError: if the file does not exist or the file descriptor is 995 invalid. 996 """ 997 file_object = self.filesystem.resolve(path, allow_fd=True) 998 file_object.size = length 999 1000 def ftruncate(self, fd: int, length: int) -> None: 1001 """Truncate the file corresponding to fd, so that it is 1002 length bytes in size. If length is larger than the current size, 1003 the file is filled up with zero bytes. 1004 1005 Args: 1006 fd: (int) File descriptor for the file object. 1007 length: (int) Maximum length of the file after truncating it. 1008 1009 Raises: 1010 OSError: if the file descriptor is invalid 1011 """ 1012 file_object = self.filesystem.get_open_file(fd).get_object() 1013 if isinstance(file_object, FakeFileWrapper): 1014 file_object.size = length 1015 else: 1016 raise OSError(errno.EBADF, "Invalid file descriptor") 1017 1018 def access( 1019 self, 1020 path: AnyStr, 1021 mode: int, 1022 *, 1023 dir_fd: Optional[int] = None, 1024 effective_ids: bool = False, 1025 follow_symlinks: bool = True, 1026 ) -> bool: 1027 """Check if a file exists and has the specified permissions. 1028 1029 Args: 1030 path: (str) Path to the file. 1031 mode: (int) Permissions represented as a bitwise-OR combination of 1032 os.F_OK, os.R_OK, os.W_OK, and os.X_OK. 1033 dir_fd: If not `None`, the file descriptor of a directory, with 1034 `path` being relative to this directory. 1035 effective_ids: (bool) Unused. Only here to match the signature. 1036 follow_symlinks: (bool) If `False` and `path` points to a symlink, 1037 the link itself is queried instead of the linked object. 1038 1039 Returns: 1040 bool, `True` if file is accessible, `False` otherwise. 1041 """ 1042 if effective_ids and self.filesystem.is_windows_fs: 1043 raise NotImplementedError( 1044 "access: effective_ids unavailable on this platform" 1045 ) 1046 path = self._path_with_dir_fd(path, self.access, dir_fd) 1047 try: 1048 stat_result = self.stat(path, follow_symlinks=follow_symlinks) 1049 except OSError as os_error: 1050 if os_error.errno == errno.ENOENT: 1051 return False 1052 raise 1053 if is_root(): 1054 mode &= ~os.W_OK 1055 return (mode & ((stat_result.st_mode >> 6) & 7)) == mode 1056 1057 def fchmod( 1058 self, 1059 fd: int, 1060 mode: int, 1061 ) -> None: 1062 """Change the permissions of an open file as encoded in integer mode. 1063 1064 Args: 1065 fd: (int) File descriptor. 1066 mode: (int) Permissions. 1067 """ 1068 if self.filesystem.is_windows_fs and sys.version_info < (3, 13): 1069 raise AttributeError( 1070 "module 'os' has no attribute 'fchmod'. Did you mean: 'chmod'?" 1071 ) 1072 self.filesystem.chmod(fd, mode) 1073 1074 def chmod( 1075 self, 1076 path: AnyStr, 1077 mode: int, 1078 *, 1079 dir_fd: Optional[int] = None, 1080 follow_symlinks: bool = True, 1081 ) -> None: 1082 """Change the permissions of a file as encoded in integer mode. 1083 1084 Args: 1085 path: (str) Path to the file. 1086 mode: (int) Permissions. 1087 dir_fd: If not `None`, the file descriptor of a directory, with 1088 `path` being relative to this directory. 1089 follow_symlinks: (bool) If `False` and `path` points to a symlink, 1090 the link itself is queried instead of the linked object. 1091 """ 1092 if not follow_symlinks and ( 1093 self.chmod not in self.supports_follow_symlinks or IS_PYPY 1094 ): 1095 raise NotImplementedError( 1096 "`follow_symlinks` for chmod() is not available on this system" 1097 ) 1098 path = self._path_with_dir_fd(path, self.chmod, dir_fd) 1099 self.filesystem.chmod(path, mode, follow_symlinks) 1100 1101 def lchmod(self, path: AnyStr, mode: int) -> None: 1102 """Change the permissions of a file as encoded in integer mode. 1103 If the file is a link, the permissions of the link are changed. 1104 1105 Args: 1106 path: (str) Path to the file. 1107 mode: (int) Permissions. 1108 """ 1109 if self.filesystem.is_windows_fs: 1110 raise NameError("name 'lchmod' is not defined") 1111 self.filesystem.chmod(path, mode, follow_symlinks=False) 1112 1113 def utime( 1114 self, 1115 path: AnyStr, 1116 times: Optional[Tuple[Union[int, float], Union[int, float]]] = None, 1117 ns: Optional[Tuple[int, int]] = None, 1118 dir_fd: Optional[int] = None, 1119 follow_symlinks: bool = True, 1120 ) -> None: 1121 """Change the access and modified times of a file. 1122 1123 Args: 1124 path: (str) Path to the file. 1125 times: 2-tuple of int or float numbers, of the form (atime, mtime) 1126 which is used to set the access and modified times in seconds. 1127 If None, both times are set to the current time. 1128 ns: 2-tuple of int numbers, of the form (atime, mtime) which is 1129 used to set the access and modified times in nanoseconds. 1130 If None, both times are set to the current time. 1131 dir_fd: If not `None`, the file descriptor of a directory, 1132 with `path` being relative to this directory. 1133 follow_symlinks: (bool) If `False` and `path` points to a symlink, 1134 the link itself is queried instead of the linked object. 1135 1136 Raises: 1137 TypeError: If anything other than the expected types is 1138 specified in the passed `times` or `ns` tuple, 1139 or if the tuple length is not equal to 2. 1140 ValueError: If both times and ns are specified. 1141 """ 1142 path = self._path_with_dir_fd(path, self.utime, dir_fd) 1143 self.filesystem.utime(path, times=times, ns=ns, follow_symlinks=follow_symlinks) 1144 1145 def chown( 1146 self, 1147 path: AnyStr, 1148 uid: int, 1149 gid: int, 1150 *, 1151 dir_fd: Optional[int] = None, 1152 follow_symlinks: bool = True, 1153 ) -> None: 1154 """Set ownership of a faked file. 1155 1156 Args: 1157 path: (str) Path to the file or directory. 1158 uid: (int) Numeric uid to set the file or directory to. 1159 gid: (int) Numeric gid to set the file or directory to. 1160 dir_fd: (int) If not `None`, the file descriptor of a directory, 1161 with `path` being relative to this directory. 1162 follow_symlinks: (bool) If `False` and path points to a symlink, 1163 the link itself is changed instead of the linked object. 1164 1165 Raises: 1166 OSError: if path does not exist. 1167 1168 `None` is also allowed for `uid` and `gid`. This permits `os.rename` 1169 to use `os.chown` even when the source file `uid` and `gid` are 1170 `None` (unset). 1171 """ 1172 path = self._path_with_dir_fd(path, self.chown, dir_fd) 1173 file_object = self.filesystem.resolve(path, follow_symlinks, allow_fd=True) 1174 if not isinstance(uid, int) or not isinstance(gid, int): 1175 raise TypeError("An integer is required") 1176 if uid != -1: 1177 file_object.st_uid = uid 1178 if gid != -1: 1179 file_object.st_gid = gid 1180 1181 def mknod( 1182 self, 1183 path: AnyStr, 1184 mode: Optional[int] = None, 1185 device: int = 0, 1186 *, 1187 dir_fd: Optional[int] = None, 1188 ) -> None: 1189 """Create a filesystem node named 'filename'. 1190 1191 Does not support device special files or named pipes as the real os 1192 module does. 1193 1194 Args: 1195 path: (str) Name of the file to create 1196 mode: (int) Permissions to use and type of file to be created. 1197 Default permissions are 0o666. Only the stat.S_IFREG file type 1198 is supported by the fake implementation. The umask is applied 1199 to this mode. 1200 device: not supported in fake implementation 1201 dir_fd: If not `None`, the file descriptor of a directory, 1202 with `path` being relative to this directory. 1203 1204 Raises: 1205 OSError: if called with unsupported options or the file can not be 1206 created. 1207 """ 1208 if self.filesystem.is_windows_fs: 1209 raise AttributeError("module 'os' has no attribute 'mknode'") 1210 if mode is None: 1211 # note that a default value of 0o600 without a device type is 1212 # documented - this is not how it seems to work 1213 mode = S_IFREG | 0o600 1214 if device or not mode & S_IFREG and not is_root(): 1215 self.filesystem.raise_os_error(errno.EPERM) 1216 1217 path = self._path_with_dir_fd(path, self.mknod, dir_fd) 1218 head, tail = self.path.split(path) 1219 if not tail: 1220 if self.filesystem.exists(head, check_link=True): 1221 self.filesystem.raise_os_error(errno.EEXIST, path) 1222 self.filesystem.raise_os_error(errno.ENOENT, path) 1223 if tail in (matching_string(tail, "."), matching_string(tail, "..")): 1224 self.filesystem.raise_os_error(errno.ENOENT, path) 1225 if self.filesystem.exists(path, check_link=True): 1226 self.filesystem.raise_os_error(errno.EEXIST, path) 1227 self.filesystem.add_object( 1228 head, 1229 FakeFile(tail, mode & ~self.filesystem.umask, filesystem=self.filesystem), 1230 ) 1231 1232 def symlink( 1233 self, 1234 src: AnyStr, 1235 dst: AnyStr, 1236 target_is_directory: bool = False, 1237 *, 1238 dir_fd: Optional[int] = None, 1239 ) -> None: 1240 """Creates the specified symlink, pointed at the specified link target. 1241 1242 Args: 1243 src: The target of the symlink. 1244 dst: Path to the symlink to create. 1245 target_is_directory: Currently ignored. 1246 dir_fd: If not `None`, the file descriptor of a directory, 1247 with `dst` being relative to this directory. 1248 1249 Raises: 1250 OSError: if the file already exists. 1251 """ 1252 dst = self._path_with_dir_fd(dst, self.symlink, dir_fd) 1253 self.filesystem.create_symlink(dst, src, create_missing_dirs=False) 1254 1255 def link( 1256 self, 1257 src: AnyStr, 1258 dst: AnyStr, 1259 *, 1260 src_dir_fd: Optional[int] = None, 1261 dst_dir_fd: Optional[int] = None, 1262 follow_symlinks: Optional[bool] = None, 1263 ) -> None: 1264 """Create a hard link at dst, pointing at src. 1265 1266 Args: 1267 src: An existing path to the target file. 1268 dst: The destination path to create a new link at. 1269 src_dir_fd: If not `None`, the file descriptor of a directory, 1270 with `src` being relative to this directory. 1271 dst_dir_fd: If not `None`, the file descriptor of a directory, 1272 with `dst` being relative to this directory. 1273 follow_symlinks: (bool) If True (the default), symlinks in the 1274 path are traversed. 1275 1276 Raises: 1277 OSError: if something already exists at new_path. 1278 OSError: if the parent directory doesn't exist. 1279 """ 1280 if IS_PYPY and follow_symlinks is not None: 1281 raise OSError(errno.EINVAL, "Invalid argument: follow_symlinks") 1282 if follow_symlinks is None: 1283 follow_symlinks = True 1284 1285 src = self._path_with_dir_fd(src, self.link, src_dir_fd) 1286 dst = self._path_with_dir_fd(dst, self.link, dst_dir_fd) 1287 self.filesystem.link(src, dst, follow_symlinks=follow_symlinks) 1288 1289 def fsync(self, fd: int) -> None: 1290 """Perform fsync for a fake file (in other words, do nothing). 1291 1292 Args: 1293 fd: The file descriptor of the open file. 1294 1295 Raises: 1296 OSError: file_des is an invalid file descriptor. 1297 TypeError: file_des is not an integer. 1298 """ 1299 # Throw an error if file_des isn't valid 1300 if 0 <= fd < NR_STD_STREAMS: 1301 self.filesystem.raise_os_error(errno.EINVAL) 1302 file_object = cast(FakeFileWrapper, self.filesystem.get_open_file(fd)) 1303 if self.filesystem.is_windows_fs: 1304 if not hasattr(file_object, "allow_update") or not file_object.allow_update: 1305 self.filesystem.raise_os_error(errno.EBADF, file_object.file_path) 1306 1307 def fdatasync(self, fd: int) -> None: 1308 """Perform fdatasync for a fake file (in other words, do nothing). 1309 1310 Args: 1311 fd: The file descriptor of the open file. 1312 1313 Raises: 1314 OSError: `fd` is an invalid file descriptor. 1315 TypeError: `fd` is not an integer. 1316 """ 1317 if self.filesystem.is_windows_fs or self.filesystem.is_macos: 1318 raise AttributeError("module 'os' has no attribute 'fdatasync'") 1319 # Throw an error if file_des isn't valid 1320 if 0 <= fd < NR_STD_STREAMS: 1321 self.filesystem.raise_os_error(errno.EINVAL) 1322 self.filesystem.get_open_file(fd) 1323 1324 def sendfile(self, fd_out: int, fd_in: int, offset: int, count: int) -> int: 1325 """Copy count bytes from file descriptor fd_in to file descriptor 1326 fd_out starting at offset. 1327 1328 Args: 1329 fd_out: The file descriptor of the destination file. 1330 fd_in: The file descriptor of the source file. 1331 offset: The offset in bytes where to start the copy in the 1332 source file. If `None` (Linux only), copying is started at 1333 the current position, and the position is updated. 1334 count: The number of bytes to copy. If 0, all remaining bytes 1335 are copied (MacOs only). 1336 1337 Raises: 1338 OSError: If `fd_in` or `fd_out` is an invalid file descriptor. 1339 TypeError: If `fd_in` or `fd_out` is not an integer. 1340 TypeError: If `offset` is None under MacOs. 1341 """ 1342 if self.filesystem.is_windows_fs: 1343 raise AttributeError("module 'os' has no attribute 'sendfile'") 1344 if 0 <= fd_in < NR_STD_STREAMS: 1345 self.filesystem.raise_os_error(errno.EINVAL) 1346 if 0 <= fd_out < NR_STD_STREAMS: 1347 self.filesystem.raise_os_error(errno.EINVAL) 1348 source = cast(FakeFileWrapper, self.filesystem.get_open_file(fd_in)) 1349 dest = cast(FakeFileWrapper, self.filesystem.get_open_file(fd_out)) 1350 if self.filesystem.is_macos: 1351 if dest.get_object().stat_result.st_mode & 0o777000 != S_IFSOCK: 1352 raise OSError("Socket operation on non-socket") 1353 if offset is None: 1354 if self.filesystem.is_macos: 1355 raise TypeError("None is not a valid offset") 1356 contents = source.read(count) 1357 else: 1358 position = source.tell() 1359 source.seek(offset) 1360 if count == 0 and self.filesystem.is_macos: 1361 contents = source.read() 1362 else: 1363 contents = source.read(count) 1364 source.seek(position) 1365 if contents: 1366 written = dest.write(contents) 1367 dest.flush() 1368 return written 1369 return 0 1370 1371 def getuid(self) -> int: 1372 """Returns the user id set in the fake filesystem. 1373 If not changed using ``set_uid``, this is the uid of the real system. 1374 """ 1375 if self.filesystem.is_windows_fs: 1376 raise NameError("name 'getuid' is not defined") 1377 return get_uid() 1378 1379 def getgid(self) -> int: 1380 """Returns the group id set in the fake filesystem. 1381 If not changed using ``set_gid``, this is the gid of the real system. 1382 """ 1383 if self.filesystem.is_windows_fs: 1384 raise NameError("name 'getgid' is not defined") 1385 return get_gid() 1386 1387 def fake_functions(self, original_functions) -> Set: 1388 functions = set() 1389 for fn in original_functions: 1390 if hasattr(self, fn.__name__): 1391 functions.add(getattr(self, fn.__name__)) 1392 else: 1393 functions.add(fn) 1394 return functions 1395 1396 @property 1397 def supports_follow_symlinks(self) -> Set[Callable]: 1398 if self._supports_follow_symlinks is None: 1399 self._supports_follow_symlinks = self.fake_functions( 1400 self.os_module.supports_follow_symlinks 1401 ) 1402 return self._supports_follow_symlinks 1403 1404 @property 1405 def supports_dir_fd(self) -> Set[Callable]: 1406 if self._supports_dir_fd is None: 1407 self._supports_dir_fd = self.fake_functions(self.os_module.supports_dir_fd) 1408 return self._supports_dir_fd 1409 1410 @property 1411 def supports_fd(self) -> Set[Callable]: 1412 if self._supports_fd is None: 1413 self._supports_fd = self.fake_functions(self.os_module.supports_fd) 1414 return self._supports_fd 1415 1416 @property 1417 def supports_effective_ids(self) -> Set[Callable]: 1418 if self._supports_effective_ids is None: 1419 self._supports_effective_ids = self.fake_functions( 1420 self.os_module.supports_effective_ids 1421 ) 1422 return self._supports_effective_ids 1423 1424 def __getattr__(self, name: str) -> Any: 1425 """Forwards any unfaked calls to the standard os module.""" 1426 return getattr(self.os_module, name) 1427 1428 1429def handle_original_call(f: Callable) -> Callable: 1430 """Decorator used for real pathlib Path methods to ensure that 1431 real os functions instead of faked ones are used. 1432 Applied to all non-private methods of `FakeOsModule`.""" 1433 1434 @functools.wraps(f) 1435 def wrapped(*args, **kwargs): 1436 should_use_original = FakeOsModule.use_original 1437 1438 if not should_use_original and args: 1439 self = args[0] 1440 fs: FakeFilesystem = self.filesystem 1441 if self.filesystem.patcher: 1442 skip_names = fs.patcher.skip_names 1443 if is_called_from_skipped_module( 1444 skip_names=skip_names, 1445 case_sensitive=fs.is_case_sensitive, 1446 ): 1447 should_use_original = True 1448 1449 if should_use_original: 1450 # remove the `self` argument for FakeOsModule methods 1451 if args and isinstance(args[0], FakeOsModule): 1452 args = args[1:] 1453 return getattr(os, f.__name__)(*args, **kwargs) 1454 1455 return f(*args, **kwargs) 1456 1457 return wrapped 1458 1459 1460for name, fn in inspect.getmembers(FakeOsModule, inspect.isfunction): 1461 if not fn.__name__.startswith("_"): 1462 setattr(FakeOsModule, name, handle_original_call(fn)) 1463 1464 1465@contextmanager 1466def use_original_os(): 1467 """Temporarily use original os functions instead of faked ones. 1468 Used to ensure that skipped modules do not use faked calls. 1469 """ 1470 try: 1471 FakeOsModule.use_original = True 1472 yield 1473 finally: 1474 FakeOsModule.use_original = False 1475