Search code examples
pythonpython-sphinxpython-mockread-the-docs

How to redefine MagicMock __str__ method?


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?


Solution

  • 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