Skip to content

Commit 118e6b3

Browse files
committed
Add convenience function add_package_metadata
- adds the metadata of a package to the fake filesystem - see #1155
1 parent eda7cc8 commit 118e6b3

15 files changed

+204
-123
lines changed

CHANGES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ The released versions correspond to PyPI releases.
1414

1515
## Unreleased
1616

17+
### Enhancements
18+
* added convenience function `add_package_metadata` to add the metadata of a given
19+
package to the fake filesystem (see [#1155](../../issues/1155))
20+
1721
### Fixes
1822
* fixed handling of dynamic imports from code in the fake filesystem in Python > 3.11
1923
(see [#1121](../../issues/1121))

docs/convenience.rst

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ You can do the same using ``pytest`` by using a fixture for test setup:
114114
115115
.. note::
116116
If you are not using the fixture directly in the test, you can use
117-
``@pytest.mark.usefixtures`` instead of passing the fixture as an argument.
117+
`@pytest.mark.usefixtures`_ instead of passing the fixture as an argument.
118118
This avoids warnings about unused arguments from linters.
119119

120120
When using ``pytest`` another option is to load the contents of the real file
@@ -139,6 +139,27 @@ the ``fs`` fixture.
139139
fs.create_file("fake/path.txt")
140140
assert content != ""
141141
142+
.. _map-metadata:
143+
144+
Map package metadata files into fake filesystem
145+
...............................................
146+
A more specialized function for adding real files to the fake filesystem is
147+
:py:meth:`add_package_metadata() <pyfakefs.fake_filesystem.FakeFilesystem.add_package_metadata>`.
148+
It adds the metadata distribution files for a given package to the fake filesystem,
149+
so that it can be accessed by modules like `importlib.metadata`_. This is needed
150+
for example if using `flask.testing`_ with ``pyfakefs``.
151+
152+
.. code:: python
153+
154+
import pytest
155+
156+
157+
@pytest.fixture(autouse=True)
158+
def add_werkzeug_metadata(fs):
159+
# flask.testing accesses Werkzeug metadata, map it
160+
fs.add_package_metadata("Werkzeug")
161+
yield
162+
142163
143164
Handling mount points
144165
~~~~~~~~~~~~~~~~~~~~~
@@ -312,3 +333,8 @@ possibility to use the ``force_unix_mode`` argument to ``FakeFilesystem.chmod``:
312333
fs.chmod("/foo", 0o000, force_unix_mode=True)
313334
with pytest.raises(PermissionError):
314335
path.is_file()
336+
337+
338+
.. _`importlib.metadata`: https://docs.python.org/3/library/importlib.metadata.html
339+
.. _`@pytest.mark.usefixtures`: https://docs.pytest.org/en/stable/reference/reference.html#pytest-mark-usefixtures
340+
.. _`flask.testing`: https://flask-testing.readthedocs.io/en/latest/

docs/modules.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ Fake filesystem classes
1414
.. autoclass:: pyfakefs.fake_filesystem.FakeFilesystem
1515
:members: add_mount_point,
1616
get_disk_usage, set_disk_usage, change_disk_usage,
17-
add_real_directory, add_real_file, add_real_symlink, add_real_paths,
17+
add_real_directory, add_real_file, add_real_symlink, add_real_paths, add_package_metadata,
1818
create_dir, create_file, create_symlink, create_link,
1919
get_object, pause, resume
2020

docs/troubleshooting.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,10 @@ Here's an example of how to add these using pytest:
182182
)
183183
return fs
184184
185+
Another example of code accessing the real filesystem is the usage of `importlib.metadata`_,
186+
which accesses the metadata associated with a Python package on disk. To handle this
187+
specific problem, another convenience method can be used - see :ref:`map-metadata`.
188+
185189
.. _os_temporary_directories:
186190

187191
OS temporary directories
@@ -491,3 +495,4 @@ We will analyze the problem, and if we find a solution we will either get this f
491495
.. _`pandas`: https://pypi.org/project/pandas/
492496
.. _`xlrd`: https://pypi.org/project/xlrd/
493497
.. _`openpyxl`: https://pypi.org/project/openpyxl/
498+
.. _`importlib.metadata`: https://docs.python.org/3/library/importlib.metadata.html

pyfakefs/fake_file.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ def st_mtime(self, val: float) -> None:
236236
self.stat_result.st_mtime = val
237237

238238
def set_large_file_size(self, st_size: int) -> None:
239-
"""Sets the self.st_size attribute and replaces self.content with None.
239+
"""Sets the self.st_size attribute and replaces self.content with `None`.
240240
241241
Provided specifically to simulate very large files without regards
242242
to their content (which wouldn't fit in memory).
@@ -247,8 +247,8 @@ def set_large_file_size(self, st_size: int) -> None:
247247
st_size: (int) The desired file size
248248
249249
Raises:
250-
OSError: if the st_size is not a non-negative integer,
251-
or if st_size exceeds the available file system space
250+
OSError: if ``st_size`` is not a non-negative integer,
251+
or if ``st_size`` exceeds the available file system space
252252
"""
253253
self._check_positive_int(st_size)
254254
if self.st_size:
@@ -286,11 +286,11 @@ def set_initial_contents(self, contents: AnyStr) -> bool:
286286
contents: string, new content of file.
287287
288288
Returns:
289-
True if the contents have been changed.
289+
`True` if the contents have been changed.
290290
291291
Raises:
292-
OSError: if the st_size is not a non-negative integer,
293-
or if st_size exceeds the available file system space
292+
OSError: if the `st_size` is not a non-negative integer,
293+
or if `st_size` exceeds the available file system space
294294
"""
295295
byte_contents = self._encode_contents(contents)
296296
changed = self._byte_contents != byte_contents
@@ -312,11 +312,11 @@ def set_contents(self, contents: AnyStr, encoding: Optional[str] = None) -> bool
312312
Args:
313313
contents: (str, bytes) new content of file.
314314
encoding: (str) the encoding to be used for writing the contents
315-
if they are a unicode string.
315+
if they are a Unicode string.
316316
If not given, the locale preferred encoding is used.
317317
318318
Returns:
319-
True if the contents have been changed.
319+
`True` if the contents have been changed.
320320
321321
Raises:
322322
OSError: if `st_size` is not a non-negative integer,
@@ -408,7 +408,7 @@ def has_permission(self, permission_bits: int) -> bool:
408408
permission_bits: The permission bits as set for the user.
409409
410410
Returns:
411-
True if the permissions are set in the correct class (user/group/other).
411+
`True` if the permissions are set in the correct class (user/group/other).
412412
"""
413413
if helpers.get_uid() == self.stat_result.st_uid:
414414
return self.st_mode & permission_bits == permission_bits
@@ -579,7 +579,7 @@ def remove_entry(self, pathname_name: str, recursive: bool = True) -> None:
579579
580580
Args:
581581
pathname_name: Basename of the child object to remove.
582-
recursive: If True (default), the entries in contained directories
582+
recursive: If `True` (default), the entries in contained directories
583583
are deleted first. Used to propagate removal errors
584584
(e.g. permission problems) from contained entries.
585585

0 commit comments

Comments
 (0)