From 15daf84f1ad9b952bbb608bf8ee7012996049645 Mon Sep 17 00:00:00 2001 From: Alex Sergeev Date: Fri, 12 Apr 2019 20:12:55 -0700 Subject: [PATCH 1/2] Add support for bound methods posing as functions in the module --- sphinx/ext/autodoc/__init__.py | 3 ++- tests/test_autodoc.py | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index 4c1032db5..37bdbc09c 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -993,7 +993,8 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ @classmethod def can_document_member(cls, member, membername, isattr, parent): # type: (Any, str, bool, Any) -> bool - return inspect.isfunction(member) or inspect.isbuiltin(member) + return (inspect.isfunction(member) or inspect.isbuiltin(member) or + (inspect.isroutine(member) and isinstance(parent, ModuleDocumenter))) def format_args(self): # type: () -> str diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py index 27412a9da..3faf5d9db 100644 --- a/tests/test_autodoc.py +++ b/tests/test_autodoc.py @@ -259,6 +259,11 @@ def test_format_signature(): assert formatsig('method', 'H.foo', H.foo2, None, None) == '(*c)' assert formatsig('method', 'H.foo', H.foo3, None, None) == r"(d='\\n')" + # test bound methods interpreted as functions + assert formatsig('function', 'foo', H().foo1, None, None) == '(b, *c)' + assert formatsig('function', 'foo', H().foo2, None, None) == '(*c)' + assert formatsig('function', 'foo', H().foo3, None, None) == r"(d='\\n')" + # test exception handling (exception is caught and args is '') directive.env.config.autodoc_docstring_signature = False assert formatsig('function', 'int', int, None, None) == '' @@ -282,6 +287,7 @@ def test_format_signature(): '(b, c=42, *d, **e)' + @pytest.mark.usefixtures('setup_test') def test_get_doc(): def getdocl(objtype, obj): @@ -451,6 +457,14 @@ def test_get_doc(): directive.env.config.autoclass_content = 'both' assert getdocl('class', I) == ['Class docstring', '', 'New docstring'] + # verify that method docstrings get extracted in both normal case + # and in case of bound method posing as a function + class J: # NOQA + def foo(self): + """Method docstring""" + assert getdocl('method', J.foo) == ['Method docstring'] + assert getdocl('function', J().foo) == ['Method docstring'] + from target import Base, Derived # NOTE: inspect.getdoc seems not to work with locally defined classes From b7f6657dd11b9131ff301cdc0066f14055fa83c0 Mon Sep 17 00:00:00 2001 From: Alex Sergeev Date: Sat, 13 Apr 2019 22:08:18 -1000 Subject: [PATCH 2/2] Address feedback --- sphinx/ext/autodoc/__init__.py | 1 + .../test-ext-autodoc/target/bound_method.py | 7 +++++++ tests/test_autodoc.py | 18 +++++++++++++++++- 3 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 tests/roots/test-ext-autodoc/target/bound_method.py diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index 37bdbc09c..392c4e3b4 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -993,6 +993,7 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ @classmethod def can_document_member(cls, member, membername, isattr, parent): # type: (Any, str, bool, Any) -> bool + # supports functions, builtins and bound methods exported at the module level return (inspect.isfunction(member) or inspect.isbuiltin(member) or (inspect.isroutine(member) and isinstance(parent, ModuleDocumenter))) diff --git a/tests/roots/test-ext-autodoc/target/bound_method.py b/tests/roots/test-ext-autodoc/target/bound_method.py new file mode 100644 index 000000000..d48b9ee1c --- /dev/null +++ b/tests/roots/test-ext-autodoc/target/bound_method.py @@ -0,0 +1,7 @@ +class Cls: + def method(self): + """Method docstring""" + pass + + +bound_method = Cls().method diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py index 3faf5d9db..07e82b54a 100644 --- a/tests/test_autodoc.py +++ b/tests/test_autodoc.py @@ -287,7 +287,6 @@ def test_format_signature(): '(b, c=42, *d, **e)' - @pytest.mark.usefixtures('setup_test') def test_get_doc(): def getdocl(objtype, obj): @@ -1477,6 +1476,23 @@ def test_partialfunction(): ] +@pytest.mark.usefixtures('setup_test') +def test_bound_method(): + options = {"members": None} + actual = do_autodoc(app, 'module', 'target.bound_method', options) + assert list(actual) == [ + '', + '.. py:module:: target.bound_method', + '', + '', + '.. py:function:: bound_method()', + ' :module: target.bound_method', + '', + ' Method docstring', + ' ', + ] + + @pytest.mark.usefixtures('setup_test') def test_coroutine(): options = {"members": None}