From 02f2c4b07258605c0c4f18141ea9c6b4ab3c58cc Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Fri, 7 Nov 2014 15:01:10 +0100 Subject: Fix :confval:`autodoc_docstring_signature` not working with signatures in class docstrings. --- CHANGES | 4 ++- sphinx/ext/autodoc.py | 88 +++++++++++++++++++---------------------------- sphinx/util/docstrings.py | 3 +- tests/test_autodoc.py | 28 ++++++++++----- 4 files changed, 61 insertions(+), 62 deletions(-) diff --git a/CHANGES b/CHANGES index a3d8a079..110a8d18 100644 --- a/CHANGES +++ b/CHANGES @@ -9,7 +9,9 @@ Features added Bugs fixed ---------- -* Now sphinx.ext.autodoc work with python-2.5 again. +* Now sphinx.ext.autodoc works with python-2.5 again. +* Fix :confval:`autodoc_docstring_signature` not working with signatures + in class docstrings. * #1563: :meth:`~sphinx.application.Sphinx.add_search_language` raises AssertionError for correct type of argument. Thanks to rikoman. * #1568: fix a crash when a "centered" directive contains a reference. diff --git a/sphinx/ext/autodoc.py b/sphinx/ext/autodoc.py index b4ecf32f..8c4f1593 100644 --- a/sphinx/ext/autodoc.py +++ b/sphinx/ext/autodoc.py @@ -901,36 +901,37 @@ class DocstringSignatureMixin(object): """ def _find_signature(self, encoding=None): - docstrings = Documenter.get_doc(self, encoding) - if len(docstrings) != 1: - return - doclines = docstrings[0] - setattr(self, '__new_doclines', doclines) - if not doclines: - return - # match first line of docstring against signature RE - match = py_ext_sig_re.match(doclines[0]) - if not match: - return - exmod, path, base, args, retann = match.groups() - # the base name must match ours - if not self.objpath or base != self.objpath[-1]: - return - # re-prepare docstring to ignore indentation after signature - docstrings = Documenter.get_doc(self, encoding, 2) - doclines = docstrings[0] - # ok, now jump over remaining empty lines and set the remaining - # lines as the new doclines - i = 1 - while i < len(doclines) and not doclines[i].strip(): - i += 1 - setattr(self, '__new_doclines', doclines[i:]) - return args, retann + docstrings = self.get_doc(encoding) + self._new_docstrings = docstrings[:] + result = None + for i, doclines in enumerate(docstrings): + # no lines in docstring, no match + if not doclines: + continue + # match first line of docstring against signature RE + match = py_ext_sig_re.match(doclines[0]) + if not match: + continue + exmod, path, base, args, retann = match.groups() + # the base name must match ours + valid_names = [self.objpath[-1]] + if isinstance(self, ClassDocumenter): + valid_names.append('__init__') + if hasattr(self.object, '__mro__'): + valid_names.extend(cls.__name__ for cls in self.object.__mro__) + if base not in valid_names: + continue + # re-prepare docstring to ignore more leading indentation + self._new_docstrings[i] = prepare_docstring('\n'.join(doclines[1:])) + result = args, retann + # don't look any further + break + return result def get_doc(self, encoding=None, ignore=1): - lines = getattr(self, '__new_doclines', None) + lines = getattr(self, '_new_docstrings', None) if lines is not None: - return [lines] + return lines return Documenter.get_doc(self, encoding, ignore) def format_signature(self): @@ -984,7 +985,7 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): pass -class ClassDocumenter(ModuleLevelDocumenter): +class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): """ Specialized Documenter subclass for classes. """ @@ -1036,18 +1037,7 @@ class ClassDocumenter(ModuleLevelDocumenter): if self.doc_as_attr: return '' - # get __init__ method signature from __init__.__doc__ - if self.env.config.autodoc_docstring_signature: - # only act if the feature is enabled - init_doc = MethodDocumenter(self.directive, '__init__') - init_doc.object = self.get_attr(self.object, '__init__', None) - init_doc.objpath = ['__init__'] - result = init_doc._find_signature() - if result is not None: - # use args only for Class signature - return '(%s)' % result[0] - - return ModuleLevelDocumenter.format_signature(self) + return DocstringSignatureMixin.format_signature(self) def add_directive_header(self, sig): if self.doc_as_attr: @@ -1066,6 +1056,10 @@ class ClassDocumenter(ModuleLevelDocumenter): '') def get_doc(self, encoding=None, ignore=1): + lines = getattr(self, '_new_docstrings', None) + if lines is not None: + return lines + content = self.env.config.autoclass_content docstrings = [] @@ -1076,18 +1070,8 @@ class ClassDocumenter(ModuleLevelDocumenter): # for classes, what the "docstring" is can be controlled via a # config value; the default is only the class docstring if content in ('both', 'init'): - # get __init__ method document from __init__.__doc__ - if self.env.config.autodoc_docstring_signature: - # only act if the feature is enabled - init_doc = MethodDocumenter(self.directive, '__init__') - init_doc.object = self.get_attr(self.object, '__init__', None) - init_doc.objpath = ['__init__'] - init_doc._find_signature() # this effects to get_doc() result - initdocstring = '\n'.join( - ['\n'.join(l) for l in init_doc.get_doc(encoding)]) - else: - initdocstring = self.get_attr( - self.get_attr(self.object, '__init__', None), '__doc__') + initdocstring = self.get_attr( + self.get_attr(self.object, '__init__', None), '__doc__') # for new-style classes, no __init__ means default __init__ if (initdocstring is not None and (initdocstring == object.__init__.__doc__ or # for pypy diff --git a/sphinx/util/docstrings.py b/sphinx/util/docstrings.py index 71381305..c5397766 100644 --- a/sphinx/util/docstrings.py +++ b/sphinx/util/docstrings.py @@ -34,7 +34,8 @@ def prepare_docstring(s, ignore=1): if i < len(lines): lines[i] = lines[i].lstrip() if margin < sys.maxint: - for i in range(ignore, len(lines)): lines[i] = lines[i][margin:] + for i in range(ignore, len(lines)): + lines[i] = lines[i][margin:] # Remove any leading blank lines. while lines and not lines[0]: lines.pop(0) diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py index a7b2cee0..ed906d1c 100644 --- a/tests/test_autodoc.py +++ b/tests/test_autodoc.py @@ -153,9 +153,12 @@ def test_format_signature(): inst.fullname = name inst.doc_as_attr = False # for class objtype inst.object = obj + inst.objpath = [name] inst.args = args inst.retann = retann - return inst.format_signature() + res = inst.format_signature() + print res + return res # no signatures for modules assert formatsig('module', 'test', None, None, None) == '' @@ -188,7 +191,8 @@ def test_format_signature(): assert formatsig('class', 'C', C, None, None) == '(a, b=None)' assert formatsig('class', 'C', D, 'a, b', 'X') == '(a, b) -> X' - #__init__ have signature at first line of docstring + # __init__ have signature at first line of docstring + directive.env.config.autoclass_content = 'both' class F2: '''some docstring for F2.''' def __init__(self, *args, **kw): @@ -199,9 +203,11 @@ def test_format_signature(): ''' class G2(F2, object): pass - for C in (F2, G2): - assert formatsig('class', 'C', C, None, None) == \ - '(a1, a2, kw1=True, kw2=False)' + + assert formatsig('class', 'F2', F2, None, None) == \ + '(a1, a2, kw1=True, kw2=False)' + assert formatsig('class', 'G2', G2, None, None) == \ + '(a1, a2, kw1=True, kw2=False)' # test for methods class H: @@ -217,6 +223,7 @@ def test_format_signature(): assert formatsig('method', 'H.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) == '' del _warnings[:] @@ -244,9 +251,14 @@ def test_get_doc(): def getdocl(objtype, obj, encoding=None): inst = AutoDirective._registry[objtype](directive, 'tmp') inst.object = obj + inst.objpath = [obj.__name__] + inst.doc_as_attr = False + inst.format_signature() # handle docstring signatures! ds = inst.get_doc(encoding) # for testing purposes, concat them and strip the empty line at the end - return sum(ds, [])[:-1] + res = sum(ds, [])[:-1] + print res + return res # objects without docstring def f(): @@ -307,7 +319,7 @@ def test_get_doc(): assert getdocl('class', D) == ['Class docstring', '', 'Init docstring', '', 'Other', ' lines'] - #__init__ have signature at first line of docstring + # __init__ have signature at first line of docstring class E: """Class docstring""" def __init__(self, *args, **kw): @@ -332,7 +344,7 @@ def test_get_doc(): # signature line in the docstring will be removed when # autodoc_docstring_signature == True - directive.env.config.autodoc_docstring_signature = True #default + directive.env.config.autodoc_docstring_signature = True # default directive.env.config.autoclass_content = 'class' assert getdocl('class', E) == ['Class docstring'] directive.env.config.autoclass_content = 'init' -- cgit v1.2.1