Skip to content
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

Patch 'Path' if imported from pathlib or pathlib2 #442

Merged
merged 2 commits into from
Oct 16, 2018
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
2 changes: 2 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ This version of pyfakefs does not support Python 3.3. Python 3.3 users shall
keep using pyfakefs 3.4.3, or upgrade to a newer Python version.

#### New Features
* automatically patch `Path` if imported like `from pathlib import Path`
([#440](../../issues/440))
* added side_effect option to fake files ([#433](../../pull/433))
* parameter `patch_path` has been removed from `UnitTest` and `Patcher`,
the correct patching of `path` imports is now done automatically
Expand Down
54 changes: 23 additions & 31 deletions docs/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ Both ``fake_filesystem_unittest.Patcher`` and ``fake_filesystem_unittest.TestCas
provide a few additional arguments for fine-tuning. These are only needed if
patching does not work for some module.
In case of ``fake_filesystem_unittest.TestCase``, these arguments can either
be set in the TestCase instance initialization, or in the call of
be set in the TestCase instance initialization, or in the call of
``setUpPyfakefs()``.

.. note:: If you need these arguments in ``PyTest``, you have to
Expand All @@ -116,16 +116,16 @@ Pyfakefs automatically patches modules only if they are imported directly, e.g:
import os
import pathlib.Path

The following imports of ``os`` and ``pathlib.Path`` will not be patched by
``pyfakefs``, however:
The following import of ``os`` will not be patched by ``pyfakefs``, however:

.. code:: python

import os as my_os
from pathlib import Path

.. note:: There is one exception to that: importing ``os.path`` like
.. note:: There are two exceptions to that: importing ``os.path`` like
``from os import path`` will work, because it is handled by ``pyfakefs``.
The same is true for ``pathlib.Path`` if imported like ``from pathlib import
Path``.

If adding the module containing these imports to ``modules_to_reload``, they
will be correctly patched.
Expand All @@ -135,42 +135,34 @@ modules_to_patch
This also allows patching modules that are not patched out of the box, in
this case by adding a fake module implementation for a module name. The
argument is a dictionary of fake modules mapped to the names to be faked.
This can be used to fake modules imported as another name directly. For the
``os`` import above you could also use:
This can be used to fake out modules that use OS-specific file system calls
as in the following example:

.. code:: python

with Patcher(modules_to_patch={'my_os': fake_filesystem.FakeOsModule}):
test_something()
class FakeLocks(object):
"""django.core.files.locks uses low level OS functions, fake it."""
_locks_module = django.core.files.locks

For the second example (``from pathlib import Path``) the syntax is slightly
different:
def __init__(self, fs):
"""Each fake module expects the fake file system as an __init__ parameter."""
pass

.. code:: python

with Patcher(modules_to_patch={'pathlib.Path': MyFakePath}):
test_something()
@staticmethod
def lock(f, flags):
return True

This will fake the class ``Path`` inside the module ``pathlib``, if imported
as ``Path``.
Here is an example of how to implement ``MyFakePath``:

.. code:: python
@staticmethod
def unlock(f):
return True

class MyFakePath():
"""Patches `pathlib.Path` by passing all calls to FakePathlibModule."""
fake_pathlib = None
def __getattr__(self, name):
return getattr(self._locks_module, name)

def __init__(self, filesystem):
if self.fake_pathlib is None:
from pyfakefs.fake_pathlib import FakePathlibModule
self.__class__.fake_pathlib = FakePathlibModule(filesystem)

def __call__(self, *args, **kwargs):
return self.fake_pathlib.Path(*args, **kwargs)
with Patcher(modules_to_patch={'locks': FakeLocks}):
test_django_stuff()

def __getattr__(self, name):
return getattr(self.fake_pathlib.Path, name)

additional_skip_names
~~~~~~~~~~~~~~~~~~~~~
Expand Down
8 changes: 6 additions & 2 deletions pyfakefs/fake_filesystem_unittest.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
from pyfakefs import fake_filesystem
from pyfakefs import fake_filesystem_shutil
from pyfakefs import mox3_stubout
from pyfakefs.extra_packages import pathlib, use_scandir
from pyfakefs.extra_packages import pathlib, pathlib2, use_scandir

if pathlib:
from pyfakefs import fake_pathlib
Expand Down Expand Up @@ -313,14 +313,18 @@ def __init__(self, additional_skip_names=None,
'shutil': fake_filesystem_shutil.FakeShutilModule,
'io': fake_filesystem.FakeIoModule,
}
self._class_modules = {}
if pathlib:
self._fake_module_classes[
'pathlib'] = fake_pathlib.FakePathlibModule
self._fake_module_classes[
'Path'] = fake_pathlib.FakePathlibPathModule
mod_name = 'pathlib2' if pathlib2 is not None else 'pathlib'
self._class_modules['Path'] = mod_name
if use_scandir:
self._fake_module_classes[
'scandir'] = fake_scandir.FakeScanDirModule

self._class_modules = {}
if modules_to_patch is not None:
for name, fake_module in modules_to_patch.items():
if '.' in name:
Expand Down
15 changes: 15 additions & 0 deletions pyfakefs/fake_pathlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -670,3 +670,18 @@ class PosixPath(FakePath, PurePosixPath):
def __getattr__(self, name):
"""Forwards any unfaked calls to the standard pathlib module."""
return getattr(self._pathlib_module, name)


class FakePathlibPathModule(object):
"""Patches `pathlib.Path` by passing all calls to FakePathlibModule."""
fake_pathlib = None

def __init__(self, filesystem):
if self.fake_pathlib is None:
self.__class__.fake_pathlib = FakePathlibModule(filesystem)

def __call__(self, *args, **kwargs):
return self.fake_pathlib.Path(*args, **kwargs)

def __getattr__(self, name):
return getattr(self.fake_pathlib.Path, name)
30 changes: 7 additions & 23 deletions pyfakefs/tests/fake_filesystem_unittest_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,13 @@ def test_fakepathlib(self):


class TestImportAsOtherNameInit(fake_filesystem_unittest.TestCase):
def __init__(self, methodName='RunTest'):
modules_to_load = [pyfakefs.tests.import_as_example]
super(TestImportAsOtherNameInit, self).__init__(
methodName, modules_to_reload=modules_to_load)

def setUp(self):
self.setUpPyfakefs(
modules_to_reload=[pyfakefs.tests.import_as_example])
self.setUpPyfakefs()

def test_file_exists(self):
file_path = '/foo/bar/baz'
Expand Down Expand Up @@ -194,28 +198,8 @@ def test_own_path_module(self):


if pathlib:
class FakePathlibPathModule(object):
"""Patches `pathlib.Path` by passing all calls to FakePathlibModule."""
fake_pathlib = None

def __init__(self, filesystem):
if self.fake_pathlib is None:
from pyfakefs.fake_pathlib import FakePathlibModule
self.__class__.fake_pathlib = FakePathlibModule(filesystem)

def __call__(self, *args, **kwargs):
return self.fake_pathlib.Path(*args, **kwargs)

def __getattr__(self, name):
return getattr(self.fake_pathlib.Path, name)

class PatchPathlibPathTest(TestPyfakefsUnittestBase):
"""Shows how to patch a class inside a module."""
def __init__(self, methodName='RunTest'):
modules_to_patch = {'pathlib.Path': FakePathlibPathModule}
super(PatchPathlibPathTest, self).__init__(
methodName, modules_to_patch=modules_to_patch)

"""Shows that pathlib.Path is correctly patched."""
def test_path_exists(self):
file_path = '/foo/bar'
self.fs.create_dir(file_path)
Expand Down
10 changes: 5 additions & 5 deletions pyfakefs/tests/import_as_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,18 @@
import os as my_os

try:
import pathlib
from pathlib import Path
except ImportError:
try:
import pathlib2 as pathlib
from pathlib2 import Path
except ImportError:
pathlib = None
Path = None


def check_if_exists(filepath):
return my_os.path.exists(filepath)


if pathlib:
if Path:
def check_if_path_exists(filepath):
return pathlib.Path(filepath).exists()
return Path(filepath).exists()