From e7ccd8cfe220a70476d5a2e9eccee43cde809419 Mon Sep 17 00:00:00 2001 From: mrbean-bremen Date: Fri, 25 Aug 2023 20:31:49 +0200 Subject: [PATCH] Add some API links to the usage documentation - mention the return value of create_x methods more prominently - add a note about global pathlib.Path objects to the troubleshooting section --- docs/modules.rst | 4 +- docs/troubleshooting.rst | 24 +++++++++ docs/usage.rst | 18 ++++--- pyfakefs/fake_filesystem.py | 104 +++++++++++++++++++++--------------- 4 files changed, 97 insertions(+), 53 deletions(-) diff --git a/docs/modules.rst b/docs/modules.rst index 75c96bfb..f6d7942f 100644 --- a/docs/modules.rst +++ b/docs/modules.rst @@ -13,9 +13,9 @@ Fake filesystem classes ----------------------- .. autoclass:: pyfakefs.fake_filesystem.FakeFilesystem :members: add_mount_point, - get_disk_usage, set_disk_usage, + get_disk_usage, set_disk_usage, change_disk_usage, add_real_directory, add_real_file, add_real_symlink, add_real_paths, - create_dir, create_file, create_symlink, + create_dir, create_file, create_symlink, create_link, get_object, pause, resume .. autoclass:: pyfakefs.fake_file.FakeFile diff --git a/docs/troubleshooting.rst b/docs/troubleshooting.rst index ba2b9774..323baafd 100644 --- a/docs/troubleshooting.rst +++ b/docs/troubleshooting.rst @@ -233,6 +233,30 @@ passed before the ``mocker`` fixture to ensure this: # works correctly mocker.patch("builtins.open", mocker.mock_open(read_data="content")) +Pathlib.Path objects created outside of tests +--------------------------------------------- +An pattern which is more often seen with the increased usage of `pathlib` is the +creation of global `pathlib.Path` objects (instead of string paths) that are used +in the tests. As these objects are created in the real filesystem, they are not of the same +type as faked `pathlib.Path` objects, and both will always compare as not equal, +regardless of the path they point to: + +.. code:: python + + import pathlib + + FOLDER = pathlib.Path(__file__).parent + FILE = FOLDER / "file.csv" + + + def test_one(fs): + fake_file = pathlib.Path(fs.create_file(FILE).path) + assert FILE == fake_file # fails, compares different objects + assert str(FILE) == str(fake_file) # succeeds, compares the actual paths + +Generally, mixing objects in the real filesystem and the fake filesystem +is problematic and better avoided. + .. _`multiprocessing`: https://docs.python.org/3/library/multiprocessing.html .. _`subprocess`: https://docs.python.org/3/library/subprocess.html diff --git a/docs/usage.rst b/docs/usage.rst index f90377ff..76dc92dc 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -675,8 +675,10 @@ for the ``patchfs`` decorator. File creation helpers ~~~~~~~~~~~~~~~~~~~~~ To create files, directories or symlinks together with all the directories -in the path, you may use ``create_file()``, ``create_dir()``, -``create_symlink()`` and ``create_link()``, respectively. +in the path, you may use :py:meth:`create_file()`, +:py:meth:`create_dir()`, +:py:meth:`create_symlink()` and +:py:meth:`create_link()`, respectively. ``create_file()`` also allows you to set the file mode and the file contents together with the encoding if needed. Alternatively, you can define a file @@ -713,8 +715,10 @@ automatically. Access to files in the real file system ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If you want to have read access to real files or directories, you can map -them into the fake file system using ``add_real_file()``, -``add_real_directory()``, ``add_real_symlink()`` and ``add_real_paths()``. +them into the fake file system using :py:meth:`add_real_file()`, +:py:meth:`add_real_directory()`, +:py:meth:`add_real_symlink()` and +:py:meth:`add_real_paths()`. They take a file path, a directory path, a symlink path, or a list of paths, respectively, and make them accessible from the fake file system. By default, the contents of the mapped files and directories are read only on @@ -800,7 +804,7 @@ Handling mount points ~~~~~~~~~~~~~~~~~~~~~ Under Linux and macOS, the root path (``/``) is the only mount point created in the fake file system. If you need support for more mount points, you can add -them using ``add_mount_point()``. +them using :py:meth:`add_mount_point()`. Under Windows, drives and UNC paths are internally handled as mount points. Adding a file or directory on another drive or UNC path automatically @@ -818,7 +822,7 @@ Setting the file system size ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If you need to know the file system size in your tests (for example for testing cleanup scripts), you can set the fake file system size using -``set_disk_usage()``. By default, this sets the total size in bytes of the +:py:meth:`set_disk_usage()`. By default, this sets the total size in bytes of the root partition; if you add a path as parameter, the size will be related to the mount point (see above) the path is related to. @@ -843,7 +847,7 @@ and you may fail to create new files if the fake file system is full. f.write("a" * 200) f.flush() -To get the file system size, you may use ``get_disk_usage()``, which is +To get the file system size, you may use :py:meth:`get_disk_usage()`, which is modeled after ``shutil.disk_usage()``. Suspending patching diff --git a/pyfakefs/fake_filesystem.py b/pyfakefs/fake_filesystem.py index 9f170d17..edf5776f 100644 --- a/pyfakefs/fake_filesystem.py +++ b/pyfakefs/fake_filesystem.py @@ -183,7 +183,8 @@ class FakeFilesystem: is_windows_fs: `True` in a real or faked Windows file system. is_macos: `True` under MacOS, or if we are faking it. is_case_sensitive: `True` if a case-sensitive file system is assumed. - root: The root :py:class:`FakeDirectory` entry of the file system. + root: The root :py:class:`FakeDirectory` entry + of the file system. umask: The umask used for newly created files, see `os.umask`. patcher: Holds the Patcher object if created from it. Allows access to the patcher object if using the pytest fs fixture. @@ -364,15 +365,15 @@ def _add_root_mount_point(self, total_size): self.add_mount_point(mount_point, total_size) def pause(self) -> None: - """Pause the patching of the file system modules until `resume` is + """Pause the patching of the file system modules until :py:meth:`resume` is called. After that call, all file system calls are executed in the real file system. - Calling pause() twice is silently ignored. + Calling `pause()` twice is silently ignored. Only allowed if the file system object was created by a - Patcher object. This is also the case for the pytest `fs` fixture. + `Patcher` object. This is also the case for the pytest `fs` fixture. Raises: - RuntimeError: if the file system was not created by a Patcher. + RuntimeError: if the file system was not created by a `Patcher`. """ if self.patcher is None: raise RuntimeError( @@ -382,7 +383,7 @@ def pause(self) -> None: self.patcher.pause() def resume(self) -> None: - """Resume the patching of the file system modules if `pause` has + """Resume the patching of the file system modules if :py:meth:`pause` has been called before. After that call, all file system calls are executed in the fake file system. Does nothing if patching is not paused. @@ -456,8 +457,8 @@ def add_mount_point( total_size: The new total size of the added filesystem device in bytes. Defaults to infinite size. - can_exist: If True, no error is raised if the mount point - already exists + can_exist: If `True`, no error is raised if the mount point + already exists. Returns: The newly created mount point dict. @@ -581,7 +582,7 @@ def get_disk_usage(self, path: Optional[AnyStr] = None) -> Tuple[int, int, int]: """Return the total, used and free disk space in bytes as named tuple, or placeholder values simulating unlimited space if not set. - .. note:: This matches the return value of shutil.disk_usage(). + .. note:: This matches the return value of ``shutil.disk_usage()``. Args: path: The disk space is returned for the file system device where @@ -642,7 +643,7 @@ def change_disk_usage( st_dev: The device ID for the respective file system. Raises: - OSError: if usage_change exceeds the free file system space + OSError: if `usage_change` exceeds the free file system space """ mount_point = self._mount_point_for_device(st_dev) if mount_point: @@ -1656,12 +1657,14 @@ def get_object(self, file_path: AnyPath, check_read_perm: bool = True) -> FakeFi filesystem. Args: - file_path: Specifies the target FakeFile object to retrieve. + file_path: Specifies the target + :py:class:`FakeFile` object to retrieve. check_read_perm: If True, raises OSError if a parent directory does not have read permission Returns: - The FakeFile object corresponding to `file_path`. + The :py:class:`FakeFile` object corresponding + to `file_path`. Raises: OSError: if the object is not found. @@ -2034,16 +2037,18 @@ def make_string_path(self, path: AnyPath) -> AnyStr: def create_dir( self, directory_path: AnyPath, perm_bits: int = helpers.PERM_DEF ) -> FakeDirectory: - """Create `directory_path`, and all the parent directories. + """Create `directory_path` and all the parent directories, and return + the created :py:class:`FakeDirectory` object. Helper method to set up your test faster. Args: directory_path: The full directory path to create. - perm_bits: The permission bits as set by `chmod`. + perm_bits: The permission bits as set by ``chmod``. Returns: - The newly created FakeDirectory object. + The newly created + :py:class:`FakeDirectory` object. Raises: OSError: if the directory already exists. @@ -2095,29 +2100,30 @@ def create_file( side_effect: Optional[Callable] = None, ) -> FakeFile: """Create `file_path`, including all the parent directories along - the way. + the way, and return the created + :py:class:`FakeFile` object. This helper method can be used to set up tests more easily. Args: file_path: The path to the file to create. - st_mode: The stat constant representing the file type. - contents: the contents of the file. If not given and st_size is - None, an empty file is assumed. + st_mode: The `stat` constant representing the file type. + contents: the contents of the file. If not given and `st_size` is + `None`, an empty file is assumed. st_size: file size; only valid if contents not given. If given, the file is considered to be in "large file mode" and trying to read from or write to the file will result in an exception. create_missing_dirs: If `True`, auto create missing directories. apply_umask: `True` if the current umask must be applied on `st_mode`. - encoding: If `contents` is a unicode string, the encoding used + encoding: If `contents` is of type `str`, the encoding used for serialization. errors: The error mode used for encoding/decoding errors. - side_effect: function handle that is executed when file is written, + side_effect: function handle that is executed when the file is written, must accept the file object as an argument. Returns: - The newly created FakeFile object. + The newly created :py:class:`FakeFile` object. Raises: OSError: if the file already exists. @@ -2142,8 +2148,9 @@ def add_real_file( target_path: Optional[AnyPath] = None, ) -> FakeFile: """Create `file_path`, including all the parent directories along the - way, for an existing real file. The contents of the real file are read - only on demand. + way, for an existing real file, and return the created + :py:class:`FakeFile` object. + The contents of the real file are read only on demand. Args: source_path: Path to an existing file in the real file system @@ -2154,7 +2161,7 @@ def add_real_file( otherwise it is equal to `source_path`. Returns: - the newly created FakeFile object. + the newly created :py:class:`FakeFile` object. Raises: OSError: if the file does not exist in the real file system. @@ -2181,9 +2188,10 @@ def add_real_file( def add_real_symlink( self, source_path: AnyPath, target_path: Optional[AnyPath] = None ) -> FakeFile: - """Create a symlink at source_path (or target_path, if given). It will - point to the same path as the symlink on the real filesystem. Relative - symlinks will point relative to their new location. Absolute symlinks + """Create a symlink at `source_path` (or `target_path`, if given) and return + the created :py:class:`FakeFile` object. + It will point to the same path as the symlink on the real filesystem. + Relative symlinks will point relative to their new location. Absolute symlinks will point to the same, absolute path as on the real filesystem. Args: @@ -2192,7 +2200,7 @@ def add_real_symlink( filesystem, otherwise, the same as `source_path`. Returns: - the newly created FakeFile object. + the newly created :py:class:`FakeFile` object. Raises: OSError: if the directory does not exist in the real file system. @@ -2220,7 +2228,9 @@ def add_real_directory( target_path: Optional[AnyPath] = None, ) -> FakeDirectory: """Create a fake directory corresponding to the real directory at the - specified path. Add entries in the fake directory corresponding to + specified path, and return the created + :py:class:`FakeDirectory` object. + Add entries in the fake directory corresponding to the entries in the real directory. Symlinks are supported. Args: @@ -2240,7 +2250,8 @@ def add_real_directory( the target directory is the same as `source_path`. Returns: - the newly created FakeDirectory object. + the newly created + :py:class:`FakeDirectory` object. Raises: OSError: if the directory does not exist in the real file system. @@ -2295,18 +2306,18 @@ def add_real_paths( lazy_dir_read: bool = True, ) -> None: """This convenience method adds multiple files and/or directories from - the real file system to the fake file system. See `add_real_file()` and - `add_real_directory()`. + the real file system to the fake file system. See :py:meth:`add_real_file` and + :py:meth:`add_real_directory`. Args: path_list: List of file and directory paths in the real file system. - read_only: If set, all files and files under under the directories + read_only: If set, all files and files under the directories are treated as read-only, e.g. a write access raises an exception; otherwise, writing to the files changes the fake files only as usually. lazy_dir_read: Uses lazy reading of directory contents if set - (see `add_real_directory`) + (see :py:meth:`add_real_directory`) Raises: OSError: if any of the files and directories in the list @@ -2415,16 +2426,18 @@ def create_symlink( link_target: AnyPath, create_missing_dirs: bool = True, ) -> FakeFile: - """Create the specified symlink, pointed at the specified link target. + """Create the specified symlink, pointed at the specified link target, + and return the created :py:class:`FakeFile` object + representing the link. Args: file_path: path to the symlink to create link_target: the target of the symlink create_missing_dirs: If `True`, any missing parent directories of - file_path will be created + `file_path` will be created Returns: - The newly created FakeFile object. + The newly created :py:class:`FakeFile` object. Raises: OSError: if the symlink could not be created @@ -2473,22 +2486,25 @@ def create_link( follow_symlinks: bool = True, create_missing_dirs: bool = True, ) -> FakeFile: - """Create a hard link at new_path, pointing at old_path. + """Create a hard link at `new_path`, pointing at `old_path`, + and return the created :py:class:`FakeFile` object + representing the link. Args: old_path: An existing link to the target file. new_path: The destination path to create a new link at. - follow_symlinks: If False and old_path is a symlink, link the + follow_symlinks: If `False` and `old_path` is a symlink, link the symlink instead of the object it points to. create_missing_dirs: If `True`, any missing parent directories of - file_path will be created + `file_path` will be created Returns: - The FakeFile object referred to by old_path. + The :py:class:`FakeFile` object referred to + by `old_path`. Raises: - OSError: if something already exists at new_path. - OSError: if old_path is a directory. + OSError: if something already exists at `new_path`. + OSError: if `old_path` is a directory. OSError: if the parent directory doesn't exist. """ old_path_str = make_string_path(old_path)