Skip to content

Add convenience function add_package_metadata #1156

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ The released versions correspond to PyPI releases.

## Unreleased

### Enhancements
* added convenience function `add_package_metadata` to add the metadata of a given
package to the fake filesystem (see [#1155](../../issues/1155))

### Fixes
* fixed handling of dynamic imports from code in the fake filesystem in Python > 3.11
(see [#1121](../../issues/1121))
Expand Down
28 changes: 27 additions & 1 deletion docs/convenience.rst
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ You can do the same using ``pytest`` by using a fixture for test setup:

.. note::
If you are not using the fixture directly in the test, you can use
``@pytest.mark.usefixtures`` instead of passing the fixture as an argument.
`@pytest.mark.usefixtures`_ instead of passing the fixture as an argument.
This avoids warnings about unused arguments from linters.

When using ``pytest`` another option is to load the contents of the real file
Expand All @@ -139,6 +139,27 @@ the ``fs`` fixture.
fs.create_file("fake/path.txt")
assert content != ""

.. _map-metadata:

Map package metadata files into fake filesystem
...............................................
A more specialized function for adding real files to the fake filesystem is
:py:meth:`add_package_metadata() <pyfakefs.fake_filesystem.FakeFilesystem.add_package_metadata>`.
It adds the metadata distribution files for a given package to the fake filesystem,
so that it can be accessed by modules like `importlib.metadata`_. This is needed
for example if using `flask.testing`_ with ``pyfakefs``.

.. code:: python

import pytest


@pytest.fixture(autouse=True)
def add_werkzeug_metadata(fs):
# flask.testing accesses Werkzeug metadata, map it
fs.add_package_metadata("Werkzeug")
yield


Handling mount points
~~~~~~~~~~~~~~~~~~~~~
Expand Down Expand Up @@ -312,3 +333,8 @@ possibility to use the ``force_unix_mode`` argument to ``FakeFilesystem.chmod``:
fs.chmod("/foo", 0o000, force_unix_mode=True)
with pytest.raises(PermissionError):
path.is_file()


.. _`importlib.metadata`: https://docs.python.org/3/library/importlib.metadata.html
.. _`@pytest.mark.usefixtures`: https://docs.pytest.org/en/stable/reference/reference.html#pytest-mark-usefixtures
.. _`flask.testing`: https://flask-testing.readthedocs.io/en/latest/
2 changes: 1 addition & 1 deletion docs/modules.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Fake filesystem classes
.. autoclass:: pyfakefs.fake_filesystem.FakeFilesystem
:members: add_mount_point,
get_disk_usage, set_disk_usage, change_disk_usage,
add_real_directory, add_real_file, add_real_symlink, add_real_paths,
add_real_directory, add_real_file, add_real_symlink, add_real_paths, add_package_metadata,
create_dir, create_file, create_symlink, create_link,
get_object, pause, resume

Expand Down
5 changes: 5 additions & 0 deletions docs/troubleshooting.rst
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,10 @@ Here's an example of how to add these using pytest:
)
return fs

Another example of code accessing the real filesystem is the usage of `importlib.metadata`_,
which accesses the metadata associated with a Python package on disk. To handle this
specific problem, another convenience method can be used - see :ref:`map-metadata`.

.. _os_temporary_directories:

OS temporary directories
Expand Down Expand Up @@ -491,3 +495,4 @@ We will analyze the problem, and if we find a solution we will either get this f
.. _`pandas`: https://pypi.org/project/pandas/
.. _`xlrd`: https://pypi.org/project/xlrd/
.. _`openpyxl`: https://pypi.org/project/openpyxl/
.. _`importlib.metadata`: https://docs.python.org/3/library/importlib.metadata.html
20 changes: 10 additions & 10 deletions pyfakefs/fake_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ def st_mtime(self, val: float) -> None:
self.stat_result.st_mtime = val

def set_large_file_size(self, st_size: int) -> None:
"""Sets the self.st_size attribute and replaces self.content with None.
"""Sets the self.st_size attribute and replaces self.content with `None`.

Provided specifically to simulate very large files without regards
to their content (which wouldn't fit in memory).
Expand All @@ -247,8 +247,8 @@ def set_large_file_size(self, st_size: int) -> None:
st_size: (int) The desired file size

Raises:
OSError: if the st_size is not a non-negative integer,
or if st_size exceeds the available file system space
OSError: if ``st_size`` is not a non-negative integer,
or if ``st_size`` exceeds the available file system space
"""
self._check_positive_int(st_size)
if self.st_size:
Expand Down Expand Up @@ -286,11 +286,11 @@ def set_initial_contents(self, contents: AnyStr) -> bool:
contents: string, new content of file.

Returns:
True if the contents have been changed.
`True` if the contents have been changed.

Raises:
OSError: if the st_size is not a non-negative integer,
or if st_size exceeds the available file system space
OSError: if the `st_size` is not a non-negative integer,
or if `st_size` exceeds the available file system space
"""
byte_contents = self._encode_contents(contents)
changed = self._byte_contents != byte_contents
Expand All @@ -312,11 +312,11 @@ def set_contents(self, contents: AnyStr, encoding: Optional[str] = None) -> bool
Args:
contents: (str, bytes) new content of file.
encoding: (str) the encoding to be used for writing the contents
if they are a unicode string.
if they are a Unicode string.
If not given, the locale preferred encoding is used.

Returns:
True if the contents have been changed.
`True` if the contents have been changed.

Raises:
OSError: if `st_size` is not a non-negative integer,
Expand Down Expand Up @@ -408,7 +408,7 @@ def has_permission(self, permission_bits: int) -> bool:
permission_bits: The permission bits as set for the user.

Returns:
True if the permissions are set in the correct class (user/group/other).
`True` if the permissions are set in the correct class (user/group/other).
"""
if helpers.get_uid() == self.stat_result.st_uid:
return self.st_mode & permission_bits == permission_bits
Expand Down Expand Up @@ -579,7 +579,7 @@ def remove_entry(self, pathname_name: str, recursive: bool = True) -> None:

Args:
pathname_name: Basename of the child object to remove.
recursive: If True (default), the entries in contained directories
recursive: If `True` (default), the entries in contained directories
are deleted first. Used to propagate removal errors
(e.g. permission problems) from contained entries.

Expand Down
Loading
Loading