diff --git a/CHANGES b/CHANGES index 0aa82cf49..1ff1fd3e2 100644 --- a/CHANGES +++ b/CHANGES @@ -17,6 +17,7 @@ Bugs fixed ---------- * #4019: inheritance_diagram AttributeError stoping make process +* #4539: autodoc emits warnings for partialmethods Testing -------- diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index 3c3361b99..6fd5c789c 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -307,9 +307,17 @@ class Signature(object): raise TypeError("can't compute signature for built-in type {}".format(subject)) self.subject = subject + self.partialmethod_with_noargs = False if PY3: - self.signature = inspect.signature(subject) + try: + self.signature = inspect.signature(subject) + except IndexError: + if hasattr(subject, '_partialmethod'): # partialmethod with no argument + self.signature = None + self.partialmethod_with_noargs = True + else: + raise else: self.argspec = getargspec(subject) @@ -339,7 +347,10 @@ class Signature(object): def parameters(self): # type: () -> Dict if PY3: - return self.signature.parameters + if self.partialmethod_with_noargs: + return {} + else: + return self.signature.parameters else: params = OrderedDict() # type: Dict positionals = len(self.argspec.args) - len(self.argspec.defaults) @@ -360,7 +371,7 @@ class Signature(object): @property def return_annotation(self): # type: () -> Any - if PY3: + if PY3 and self.signature: return self.signature.return_annotation else: return None diff --git a/tests/roots/test-ext-autodoc/target/partialmethod.py b/tests/roots/test-ext-autodoc/target/partialmethod.py new file mode 100644 index 000000000..01cf4e798 --- /dev/null +++ b/tests/roots/test-ext-autodoc/target/partialmethod.py @@ -0,0 +1,18 @@ +# for py34 or above +from functools import partialmethod + + +class Cell(object): + """An example for partialmethod. + + refs: https://docs.python.jp/3/library/functools.html#functools.partialmethod + """ + + def set_state(self, state): + """Update state of cell to *state*.""" + + #: Make a cell alive. + set_alive = partialmethod(set_state, True) + + set_dead = partialmethod(set_state, False) + """Make a cell dead.""" diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py index 55c4b8847..7de5a2910 100644 --- a/tests/test_autodoc.py +++ b/tests/test_autodoc.py @@ -907,3 +907,50 @@ def test_generate(): options.members = ['decoratedFunction'] assert_result_contains('.. py:function:: decoratedFunction()', 'module', 'autodoc_missing_imports') + + +@pytest.mark.skipif(sys.version_info < (3, 4), + reason='functools.partialmethod is available on py34 or above') +@pytest.mark.usefixtures('setup_test') +def test_partialmethod(): + def call_autodoc(objtype, name): + inst = app.registry.documenters[objtype](directive, name) + inst.generate() + result = list(directive.result) + del directive.result[:] + return result + + options.inherited_members = True + options.undoc_members = True + expected = [ + '', + '.. py:class:: Cell', + ' :module: target.partialmethod', + '', + ' An example for partialmethod.', + ' ', + ' refs: https://docs.python.jp/3/library/functools.html#functools.partialmethod', + ' ', + ' ', + ' .. py:method:: Cell.set_alive() -> None', + ' :module: target.partialmethod', + ' ', + ' Make a cell alive.', + ' ', + ' ', + ' .. py:method:: Cell.set_dead() -> None', + ' :module: target.partialmethod', + ' ', + ' Make a cell dead.', + ' ', + ' ', + ' .. py:method:: Cell.set_state(state)', + ' :module: target.partialmethod', + ' ', + ' Update state of cell to *state*.', + ' ', + ] + if sys.version_info < (3, 5): + expected = '\n'.join(expected).replace(' -> None', '').split('\n') + + assert call_autodoc('class', 'target.partialmethod.Cell') == expected diff --git a/tests/test_util_inspect.py b/tests/test_util_inspect.py index b5d50ed71..3e4c82d5b 100644 --- a/tests/test_util_inspect.py +++ b/tests/test_util_inspect.py @@ -200,6 +200,33 @@ def test_Signature_methods(): assert sig == '(arg1, **kwargs)' +@pytest.mark.skipif(sys.version_info < (3, 4), + reason='functools.partialmethod is available on py34 or above') +def test_Signature_partialmethod(): + from functools import partialmethod + + class Foo(object): + def meth1(self, arg1, arg2, arg3=None, arg4=None): + pass + + def meth2(self, arg1, arg2): + pass + + foo = partialmethod(meth1, 1, 2) + bar = partialmethod(meth1, 1, arg3=3) + baz = partialmethod(meth2, 1, 2) + + subject = Foo() + sig = inspect.Signature(subject.foo).format_args() + assert sig == '(arg3=None, arg4=None)' + + sig = inspect.Signature(subject.bar).format_args() + assert sig == '(arg2, *, arg3=3, arg4=None)' + + sig = inspect.Signature(subject.baz).format_args() + assert sig == '()' + + @pytest.mark.skipif(sys.version_info < (3, 5), reason='type annotation test is available on py35 or above') def test_Signature_annotations():