4

I'm trying to autogenerate documentation for Readthedocs. I mock some dependencies as they suggest in FAQ, but from type annotations of functions I get some parts of documentation to look like

Return type: <MagicMock id=‘140517266915680’>

Which is of course unacceptable. So I rewritten mock like this:

from unittest.mock import Mock

class ModuleMock(Mock):
    def __init__(self, path='', *args, **kwargs):
        super().__init__(*args, *kwargs)
        self.path = path

    def __getattr__(self, name):
        return ModuleMock(path=self.path + '.' + name)

    def __repr__(self):
        return self.path

So I could do

>>> x = ModuleMock('x')
>>> x
x
>>> x.y.z
x.y.z

But with this I get exception

  ...
  File "<frozen importlib._bootstrap>", line 906, in _find_spec
  File "<frozen importlib._bootstrap_external>", line 1280, in find_spec
  File "<frozen importlib._bootstrap_external>", line 1246, in _get_spec
TypeError: 'ModuleMock' object is not iterable

When I instead try to inherit from MagicMock, I get RecursionError.

What should I do to properly isolate dependencies for documentation generation, and make that documentation readable?

Bunyk
  • 7,635
  • 8
  • 47
  • 79
  • In this context, a mock is an object that pretends to be a module, so that it can be imported, but it doesn’t actually do anything, and cannot be documented like 'real' modules. Note also that you are best off adding the mocks to conf.py, see also http://blog.rtwilson.com/how-to-make-your-sphinx-documentation-compile-with-readthedocs-when-youre-using-numpy-and-scipy/ – ElToro1966 Jan 03 '19 at 16:00
  • @ElToro1966 I don't want it to be documented, I want references to it to not look like `` I found unfinished PR to Sphinx that fixes this https://github.com/sphinx-doc/sphinx/pull/5394, but not yet able to figure out how it works. – Bunyk Jan 03 '19 at 16:44

1 Answers1

3

It because MagicMock uses _mock_methods and _mock_unsafe attributes, but Mock doesn't (seems). I use Python 2.7

Correct implementation:

from mock import MagicMock

class ModuleMock(MagicMock):
    def __init__(self, path='', *args, **kwargs):
        super(ModuleMock, self).__init__(*args, **kwargs)
        self.path = path

    def __repr__(self):
        return self.path

    def __getattr__(self, name):
        #print(name)
        if name in ('_mock_methods', '_mock_unsafe'):
            return super(ModuleMock, self).__getattr__(name)

        return ModuleMock(self.path + "." + name)


if __name__ == '__main__':
    x = ModuleMock('x')
    print(x)
    print(x.y.z)

So if you print attribute name inside __getattr__, you can see MagicMock has several calls.

Result:

_mock_methods
_mock_methods
x
y
_mock_methods
z
_mock_methods
_mock_methods
x.y.z
Valijon
  • 12,667
  • 4
  • 34
  • 67
  • Yes! After I also added `__mro_entries__` to list of exceptions for `__getattr__` redefinition it started to work! (Sphinx wanted to get some data from there, and wanted it to be a tuple) – Bunyk Jan 03 '19 at 17:24