Repository: numpy/numpydoc

Issue:
"Handler <function mangle_docstrings at 0x7f64b5ba57b8> for event 'autodoc-process-docstring' threw an exception"
We're starting to see this with the upgrade from 0.9.2 to 1.0

For example: https://github.com/yeatmanlab/pyAFQ/pull/270/checks?check_run_id=800934528#step:6:61

See also: https://github.com/yeatmanlab/pyAFQ/pull/271

Do we need to change something on our end?

Comments:

Patch:
diff --git a/doc/release_notes.rst b/doc/release_notes.rst
index 25c6b63..a324bd1 100644
--- a/doc/release_notes.rst
+++ b/doc/release_notes.rst
@@ -71,3 +71,21 @@ Additional notes
 
    -  https://github.com/numpy/numpydoc/issues/215#issuecomment-568261611
    -  https://github.com/readthedocs/sphinx_rtd_theme/pull/838
+
+1.1.0
+-----
+
+Fixed bugs
+~~~~~~~~~~
+
+-  BUG: Defer to autodoc for signatures `#221 <https://github.com/numpy/numpydoc/pull/221>`__ (`thequackdaddy <https://github.com/thequackdaddy>`__)
+
+Closed issues
+~~~~~~~~~~~~~
+
+-  self included in list of params for method `#220 <https://github.com/numpy/numpydoc/issues/220>`__
+
+Additional notes
+~~~~~~~~~~~~~~~~
+
+-  Due to merging of `#221 <https://github.com/numpy/numpydoc/pull/221>`__, self and cls no longer will appear in method signatures.
diff --git a/numpydoc/docscrape.py b/numpydoc/docscrape.py
index ad0d99c..807e9bc 100644
--- a/numpydoc/docscrape.py
+++ b/numpydoc/docscrape.py
@@ -566,23 +566,6 @@ class FunctionDoc(NumpyDocString):
             doc = inspect.getdoc(func) or ''
         NumpyDocString.__init__(self, doc, config)
 
-        if not self['Signature'] and func is not None:
-            func, func_name = self.get_func()
-            try:
-                try:
-                    signature = str(inspect.signature(func))
-                except (AttributeError, ValueError):
-                    # try to read signature, backward compat for older Python
-                    if sys.version_info[0] >= 3:
-                        argspec = inspect.getfullargspec(func)
-                    else:
-                        argspec = inspect.getargspec(func)
-                    signature = inspect.formatargspec(*argspec)
-                signature = '%s%s' % (func_name, signature)
-            except TypeError:
-                signature = '%s()' % func_name
-            self['Signature'] = signature
-
     def get_func(self):
         func_name = getattr(self._f, '__name__', self.__class__.__name__)
         if inspect.isclass(self._f):
diff --git a/numpydoc/numpydoc.py b/numpydoc/numpydoc.py
index 652eedc..93cd975 100644
--- a/numpydoc/numpydoc.py
+++ b/numpydoc/numpydoc.py
@@ -28,6 +28,7 @@ from docutils.nodes import citation, Text, section, comment, reference
 import sphinx
 from sphinx.addnodes import pending_xref, desc_content
 from sphinx.util import logging
+from sphinx.errors import ExtensionError
 
 if sphinx.__version__ < '1.6.5':
     raise RuntimeError("Sphinx 1.6.5 or newer is required")
@@ -203,12 +204,25 @@ def mangle_signature(app, what, name, obj, options, sig, retann):
     if not hasattr(obj, '__doc__'):
         return
     doc = get_doc_object(obj, config={'show_class_members': False})
-    sig = doc['Signature'] or getattr(obj, '__text_signature__', None)
+    sig = (doc['Signature']
+           or _clean_text_signature(getattr(obj, '__text_signature__', None)))
     if sig:
         sig = re.sub("^[^(]*", "", sig)
         return sig, ''
 
 
+def _clean_text_signature(sig):
+    if sig is None:
+        return None
+    start_pattern = re.compile(r"^[^(]*\(")
+    start, end = start_pattern.search(sig).span()
+    start_sig = sig[start:end]
+    sig = sig[end:-1]
+    sig = re.sub(r'^\$(self|module|type)(,\s|$)','' , sig, count=1)
+    sig = re.sub(r'(^|(?<=,\s))/,\s\*', '*', sig, count=1)
+    return start_sig + sig + ')'
+
+
 def setup(app, get_doc_object_=get_doc_object):
     if not hasattr(app, 'add_config_value'):
         return  # probably called by nose, better bail out
@@ -218,7 +232,13 @@ def setup(app, get_doc_object_=get_doc_object):
 
     app.setup_extension('sphinx.ext.autosummary')
 
-    app.connect('builder-inited', update_config)
+    # Once we bump our Sphinx requirement higher (1.7 or 1.8?)
+    # we can just connect to config-inited
+    try:
+        app.connect('config-inited', update_config)
+    except ExtensionError:
+        app.connect('builder-inited', update_config)
+
     app.connect('autodoc-process-docstring', mangle_docstrings)
     app.connect('autodoc-process-signature', mangle_signature)
     app.connect('doctree-read', relabel_references)
@@ -244,17 +264,19 @@ def setup(app, get_doc_object_=get_doc_object):
     return metadata
 
 
-def update_config(app):
+def update_config(app, config=None):
     """Update the configuration with default values."""
+    if config is None:  # needed for testing and old Sphinx
+        config = app.config
     # Do not simply overwrite the `app.config.numpydoc_xref_aliases`
     # otherwise the next sphinx-build will compare the incoming values (without
     # our additions) to the old values (with our additions) and trigger
     # a full rebuild!
-    numpydoc_xref_aliases_complete = deepcopy(app.config.numpydoc_xref_aliases)
+    numpydoc_xref_aliases_complete = deepcopy(config.numpydoc_xref_aliases)
     for key, value in DEFAULT_LINKS.items():
         if key not in numpydoc_xref_aliases_complete:
             numpydoc_xref_aliases_complete[key] = value
-    app.config.numpydoc_xref_aliases_complete = numpydoc_xref_aliases_complete
+    config.numpydoc_xref_aliases_complete = numpydoc_xref_aliases_complete
 
 
 # ------------------------------------------------------------------------------


Test patch:
diff --git a/numpydoc/tests/test_docscrape.py b/numpydoc/tests/test_docscrape.py
index 1780ec4..2fb1fbd 100644
--- a/numpydoc/tests/test_docscrape.py
+++ b/numpydoc/tests/test_docscrape.py
@@ -699,7 +699,7 @@ def test_escape_stars():
         pass
 
     fdoc = FunctionDoc(func=my_func)
-    assert fdoc['Signature'] == 'my_func(a, b, **kwargs)'
+    assert fdoc['Signature'] == ''
 
 
 doc4 = NumpyDocString(
diff --git a/numpydoc/tests/test_full.py b/numpydoc/tests/test_full.py
index b727cde..775036a 100644
--- a/numpydoc/tests/test_full.py
+++ b/numpydoc/tests/test_full.py
@@ -53,12 +53,13 @@ def test_MyClass(sphinx_app):
         html = fid.read()
     # ensure that no autodoc weirdness ($) occurs
     assert '$self' not in html
+    assert '/,' not in html
     assert '__init__' in html  # inherited
     # escaped * chars should no longer be preceded by \'s,
     # if we see a \* in the output we know it's incorrect:
     assert r'\*' not in html
     # "self" should not be in the parameter list for the class:
-    assert 'self,' in html   # XXX should be "not in", bug!
+    assert 'self,' not in html
     # check xref was embedded properly (dict should link using xref):
     assert 'stdtypes.html#dict' in html
 
diff --git a/numpydoc/tests/test_numpydoc.py b/numpydoc/tests/test_numpydoc.py
index 383ca18..77e7540 100644
--- a/numpydoc/tests/test_numpydoc.py
+++ b/numpydoc/tests/test_numpydoc.py
@@ -1,6 +1,6 @@
 # -*- encoding:utf-8 -*-
 from copy import deepcopy
-from numpydoc.numpydoc import mangle_docstrings
+from numpydoc.numpydoc import mangle_docstrings, _clean_text_signature
 from numpydoc.xref import DEFAULT_LINKS
 from sphinx.ext.autodoc import ALL
 
@@ -36,7 +36,7 @@ app.builder.app = app
 
 
 def test_mangle_docstrings():
-    s ='''
+    s = '''
 A top section before
 
 .. autoclass:: str
@@ -64,6 +64,34 @@ A top section before
     assert 'upper' not in [x.strip() for x in lines]
 
 
+def test_clean_text_signature():
+    assert _clean_text_signature(None) is None
+    assert _clean_text_signature('func($self)') == 'func()'
+    assert (_clean_text_signature('func($self, *args, **kwargs)')
+            == 'func(*args, **kwargs)')
+    assert _clean_text_signature('($self)') == '()'
+    assert _clean_text_signature('()') == '()'
+    assert _clean_text_signature('func()') == 'func()'
+    assert (_clean_text_signature('func($self, /, *args, **kwargs)')
+            == 'func(*args, **kwargs)')
+    assert (_clean_text_signature('func($self, other, /, *args, **kwargs)')
+            == 'func(other, *args, **kwargs)')
+    assert _clean_text_signature('($module)') == '()'
+    assert _clean_text_signature('func($type)') == 'func()'
+    assert (_clean_text_signature('func($self, foo="hello world")')
+            == 'func(foo="hello world")')
+    assert (_clean_text_signature("func($self, foo='hello world')")
+            == "func(foo='hello world')")
+    assert (_clean_text_signature('func(foo="hello world")')
+            == 'func(foo="hello world")')
+    assert (_clean_text_signature('func(foo="$self")')
+            == 'func(foo="$self")')
+    assert (_clean_text_signature('func($self, foo="$self")')
+            == 'func(foo="$self")')
+    assert _clean_text_signature('func(self, other)') == 'func(self, other)'
+    assert _clean_text_signature('func($self, *args)') == 'func(*args)'
+
+
 if __name__ == "__main__":
     import pytest
     pytest.main()