Merge pull request #6052 from tk0miya/5394_meaningful_annotations_for_mock

Display meaningful names in type annotations for mocked objects
This commit is contained in:
Takeshi KOMIYA
2019-02-11 12:32:45 +09:00
committed by GitHub
5 changed files with 58 additions and 11 deletions

View File

@@ -155,6 +155,7 @@ Features added
* #4182: autodoc: Support :confval:`suppress_warnings`
* #5533: autodoc: :confval:`autodoc_default_options` supports ``member-order``
* #5394: autodoc: Display readable names in type annotations for mocked objects
* #4018: htmlhelp: Add :confval:`htmlhelp_file_suffix` and
:confval:`htmlhelp_link_suffix`
* #5559: text: Support complex tables (colspan and rowspan)

View File

@@ -32,13 +32,18 @@ logger = logging.getLogger(__name__)
class _MockObject:
"""Used by autodoc_mock_imports."""
__display_name__ = '_MockObject'
def __new__(cls, *args, **kwargs):
# type: (Any, Any) -> Any
if len(args) == 3 and isinstance(args[1], tuple) and args[1][-1].__class__ is cls:
# subclassing MockObject
return type(args[0], (_MockObject,), args[2], **kwargs) # type: ignore
else:
return super(_MockObject, cls).__new__(cls)
if len(args) == 3 and isinstance(args[1], tuple):
superclass = args[1][-1].__class__
if superclass is cls:
# subclassing MockObject
return _make_subclass(args[0], superclass.__display_name__,
superclass=superclass, attributes=args[2])
return super(_MockObject, cls).__new__(cls)
def __init__(self, *args, **kwargs):
# type: (Any, Any) -> None
@@ -62,11 +67,11 @@ class _MockObject:
def __getitem__(self, key):
# type: (str) -> _MockObject
return self
return _make_subclass(key, self.__display_name__, self.__class__)()
def __getattr__(self, key):
# type: (str) -> _MockObject
return self
return _make_subclass(key, self.__display_name__, self.__class__)()
def __call__(self, *args, **kw):
# type: (Any, Any) -> Any
@@ -75,6 +80,18 @@ class _MockObject:
return args[0]
return self
def __repr__(self):
# type: () -> str
return self.__display_name__
def _make_subclass(name, module, superclass=_MockObject, attributes=None):
# type: (str, str, Any, dict) -> Any
attrs = {'__module__': module, '__display_name__': module + '.' + name}
attrs.update(attributes or {})
return type(name, (superclass,), attrs)
class _MockModule(ModuleType):
"""Used by autodoc_mock_imports."""
@@ -92,9 +109,11 @@ class _MockModule(ModuleType):
def __getattr__(self, name):
# type: (str) -> _MockObject
o = _MockObject()
o.__module__ = self.__name__
return o
return _make_subclass(name, self.__name__)()
def __repr__(self):
# type: () -> str
return self.__name__
class _MockImporter(MetaPathFinder):

View File

@@ -15,6 +15,11 @@ def decoratedFunction():
return None
def func(arg: missing_module.Class):
"""a function takes mocked object as an argument"""
pass
class TestAutodoc(object):
"""TestAutodoc docstring."""
@missing_name

View File

@@ -1345,7 +1345,7 @@ def test_autofunction_for_callable(app):
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_mocked_module_imports(app, warning):
# no autodoc_mock_imports
options = {"members": 'TestAutodoc,decoratedFunction'}
options = {"members": 'TestAutodoc,decoratedFunction,func'}
actual = do_autodoc(app, 'module', 'target.need_mocks', options)
assert list(actual) == []
assert "autodoc: failed to import module 'need_mocks'" in warning.getvalue()
@@ -1382,6 +1382,12 @@ def test_mocked_module_imports(app, warning):
' :module: target.need_mocks',
'',
' decoratedFunction docstring',
' ',
'',
'.. py:function:: func(arg: missing_module.Class)',
' :module: target.need_mocks',
'',
' a function takes mocked object as an argument',
' '
]
assert warning.getvalue() == ''

View File

@@ -16,6 +16,21 @@ import pytest
from sphinx.ext.autodoc.importer import _MockModule, _MockObject, mock
def test_MockModule():
mock = _MockModule('mocked_module', None)
assert isinstance(mock.some_attr, _MockObject)
assert isinstance(mock.some_method, _MockObject)
assert isinstance(mock.attr1.attr2, _MockObject)
assert isinstance(mock.attr1.attr2.meth(), _MockObject)
assert repr(mock.some_attr) == 'mocked_module.some_attr'
assert repr(mock.some_method) == 'mocked_module.some_method'
assert repr(mock.attr1.attr2) == 'mocked_module.attr1.attr2'
assert repr(mock.attr1.attr2.meth) == 'mocked_module.attr1.attr2.meth'
assert repr(mock) == 'mocked_module'
def test_MockObject():
mock = _MockObject()
assert isinstance(mock.some_attr, _MockObject)
@@ -25,6 +40,7 @@ def test_MockObject():
class SubClass(mock.SomeClass):
"""docstring of SubClass"""
def method(self):
return "string"