Repository: numpy/numpydoc

Issue:
support for coroutine
I have a lot of coroutine to comment but i have not seen any suppport for this python feature.

The yield allows to comment what goes out of the coroutine but it would be nice to be able to specify what goes in in a specific manner.


Discussion:
jnothman: Ping @Dreem-Devices, @tacaswell, @stefanv who commented on the original issue.
tacaswell: I'm going to ping my co worker @danielballan who is much better at naming things than I am.
rgommers: LGTM. Needs a rebase.
rgommers: @jnothman want to rebase this? I think it can go into 0.9.0

Patch:
diff --git a/doc/format.rst b/doc/format.rst
index 87f5ff7..cdeec0b 100644
--- a/doc/format.rst
+++ b/doc/format.rst
@@ -252,13 +252,21 @@ The sections of a function's docstring are:
    Support for the **Yields** section was added in `numpydoc
    <https://github.com/numpy/numpydoc>`_ version 0.6.
 
-7. **Other Parameters**
+7. **Receives**
+
+   Explanation of parameters passed to a generator's ``.send()`` method,
+   formatted as for Parameters, above.  Since, like for Yields and Returns, a
+   single object is always passed to the method, this may describe either the
+   single parameter, or positional arguments passed as a tuple.  If a docstring
+   includes Receives it must also include Yields.
+
+8. **Other Parameters**
 
    An optional section used to describe infrequently used parameters.
    It should only be used if a function has a large number of keyword
    parameters, to prevent cluttering the **Parameters** section.
 
-8. **Raises**
+9. **Raises**
 
    An optional section detailing which errors get raised and under
    what conditions::
@@ -271,16 +279,16 @@ The sections of a function's docstring are:
    This section should be used judiciously, i.e., only for errors
    that are non-obvious or have a large chance of getting raised.
 
-9. **Warns**
+10. **Warns**
 
    An optional section detailing which warnings get raised and
    under what conditions, formatted similarly to Raises.
 
-10. **Warnings**
+11. **Warnings**
 
     An optional section with cautions to the user in free text/reST.
 
-11. **See Also**
+12. **See Also**
 
     An optional section used to refer to related code.  This section
     can be very useful, but should be used judiciously.  The goal is to
@@ -319,7 +327,7 @@ The sections of a function's docstring are:
       func_b, func_c_, func_d
       func_e
 
-12. **Notes**
+13. **Notes**
 
     An optional section that provides additional information about the
     code, possibly including a discussion of the algorithm. This
@@ -364,7 +372,7 @@ The sections of a function's docstring are:
     where filename is a path relative to the reference guide source
     directory.
 
-13. **References**
+14. **References**
 
     References cited in the **notes** section may be listed here,
     e.g. if you cited the article below using the text ``[1]_``,
@@ -397,7 +405,7 @@ The sections of a function's docstring are:
 
 .. highlight:: pycon
 
-14. **Examples**
+15. **Examples**
 
     An optional section for examples, using the `doctest
     <http://docs.python.org/library/doctest.html>`_ format.
diff --git a/numpydoc/docscrape.py b/numpydoc/docscrape.py
index f3453c6..4e3fc4c 100644
--- a/numpydoc/docscrape.py
+++ b/numpydoc/docscrape.py
@@ -127,6 +127,7 @@ class NumpyDocString(Mapping):
         'Parameters': [],
         'Returns': [],
         'Yields': [],
+        'Receives': [],
         'Raises': [],
         'Warns': [],
         'Other Parameters': [],
@@ -350,6 +351,9 @@ class NumpyDocString(Mapping):
         if has_returns and has_yields:
             msg = 'Docstring contains both a Returns and Yields section.'
             raise ValueError(msg)
+        if not has_yields and 'Receives' in section_names:
+            msg = 'Docstring contains a Receives section but not Yields.'
+            raise ValueError(msg)
 
         for (section, content) in sections:
             if not section.startswith('..'):
@@ -359,8 +363,8 @@ class NumpyDocString(Mapping):
                     self._error_location("The section %s appears twice"
                                          % section)
 
-            if section in ('Parameters', 'Returns', 'Yields', 'Raises',
-                           'Warns', 'Other Parameters', 'Attributes',
+            if section in ('Parameters', 'Returns', 'Yields', 'Receives',
+                           'Raises', 'Warns', 'Other Parameters', 'Attributes',
                            'Methods'):
                 self[section] = self._parse_param_list(content)
             elif section.startswith('.. index::'):
@@ -484,7 +488,7 @@ class NumpyDocString(Mapping):
         out += self._str_signature()
         out += self._str_summary()
         out += self._str_extended_summary()
-        for param_list in ('Parameters', 'Returns', 'Yields',
+        for param_list in ('Parameters', 'Returns', 'Yields', 'Receives',
                            'Other Parameters', 'Raises', 'Warns'):
             out += self._str_param_list(param_list)
         out += self._str_section('Warnings')
diff --git a/numpydoc/docscrape_sphinx.py b/numpydoc/docscrape_sphinx.py
index 4cc95d8..9b23235 100644
--- a/numpydoc/docscrape_sphinx.py
+++ b/numpydoc/docscrape_sphinx.py
@@ -374,6 +374,7 @@ class SphinxDocString(NumpyDocString):
             'parameters': self._str_param_list('Parameters'),
             'returns': self._str_returns('Returns'),
             'yields': self._str_returns('Yields'),
+            'receives': self._str_returns('Receives'),
             'other_parameters': self._str_param_list('Other Parameters'),
             'raises': self._str_param_list('Raises'),
             'warns': self._str_param_list('Warns'),
diff --git a/numpydoc/numpydoc.py b/numpydoc/numpydoc.py
index c8e676f..2544d0f 100644
--- a/numpydoc/numpydoc.py
+++ b/numpydoc/numpydoc.py
@@ -27,8 +27,9 @@ try:
 except ImportError:
     from collections import Callable
 import hashlib
+import itertools
 
-from docutils.nodes import citation, Text, reference
+from docutils.nodes import citation, Text, section, comment, reference
 import sphinx
 from sphinx.addnodes import pending_xref, desc_content, only
 
@@ -73,18 +74,39 @@ def rename_references(app, what, name, obj, options, lines):
                                             sixu('.. [%s]') % new_r)
 
 
-def _ascend(node, cls):
-    while node and not isinstance(node, cls):
-        node = node.parent
-    return node
+def _is_cite_in_numpydoc_docstring(citation_node):
+    # Find DEDUPLICATION_TAG in comment as last node of sibling section
+
+    # XXX: I failed to use citation_node.traverse to do this:
+    section_node = citation_node.parent
+
+    def is_docstring_section(node):
+        return isinstance(node, (section, desc_content))
+
+    while not is_docstring_section(section_node):
+        section_node = section_node.parent
+        if section_node is None:
+            return False
+
+    sibling_sections = itertools.chain(section_node.traverse(is_docstring_section,
+                                                             include_self=True,
+                                                             descend=False,
+                                                             siblings=True))
+    for sibling_section in sibling_sections:
+        if not sibling_section.children:
+            continue
+        last_child = sibling_section.children[-1]
+        if not isinstance(last_child, comment):
+            continue
+        if last_child.rawsource.strip() == DEDUPLICATION_TAG.strip():
+            return True
+    return False
 
 
 def relabel_references(app, doc):
     # Change 'hash-ref' to 'ref' in label text
     for citation_node in doc.traverse(citation):
-        if _ascend(citation_node, desc_content) is None:
-            # no desc node in ancestry -> not in a docstring
-            # XXX: should we also somehow check it's in a References section?
+        if not _is_cite_in_numpydoc_docstring(citation_node):
             continue
         label_node = citation_node[0]
         prefix, _, new_label = label_node[0].astext().partition('-')
diff --git a/numpydoc/templates/numpydoc_docstring.rst b/numpydoc/templates/numpydoc_docstring.rst
index 1900db5..79ab1f8 100644
--- a/numpydoc/templates/numpydoc_docstring.rst
+++ b/numpydoc/templates/numpydoc_docstring.rst
@@ -4,6 +4,7 @@
 {{parameters}}
 {{returns}}
 {{yields}}
+{{receives}}
 {{other_parameters}}
 {{raises}}
 {{warns}}


Test patch:
diff --git a/numpydoc/tests/test_docscrape.py b/numpydoc/tests/test_docscrape.py
index 2085948..a0fb19c 100644
--- a/numpydoc/tests/test_docscrape.py
+++ b/numpydoc/tests/test_docscrape.py
@@ -150,6 +150,25 @@ int
 doc_yields = NumpyDocString(doc_yields_txt)
 
 
+doc_sent_txt = """
+Test generator
+
+Yields
+------
+a : int
+    The number of apples.
+
+Receives
+--------
+b : int
+    The number of bananas.
+c : int
+    The number of oranges.
+
+"""
+doc_sent = NumpyDocString(doc_sent_txt)
+
+
 def test_signature():
     assert doc['Signature'].startswith('numpy.multivariate_normal(')
     assert doc['Signature'].endswith('spam=None)')
@@ -216,6 +235,38 @@ def test_yields():
         assert desc[0].endswith(end)
 
 
+def test_sent():
+    section = doc_sent['Receives']
+    assert len(section) == 2
+    truth = [('b', 'int', 'bananas.'),
+             ('c', 'int', 'oranges.')]
+    for (arg, arg_type, desc), (arg_, arg_type_, end) in zip(section, truth):
+        assert arg == arg_
+        assert arg_type == arg_type_
+        assert desc[0].startswith('The number of')
+        assert desc[0].endswith(end)
+
+
+def test_returnyield():
+    doc_text = """
+Test having returns and yields.
+
+Returns
+-------
+int
+    The number of apples.
+
+Yields
+------
+a : int
+    The number of apples.
+b : int
+    The number of bananas.
+
+"""
+    assert_raises(ValueError, NumpyDocString, doc_text)
+
+
 def test_returnyield():
     doc_text = """
 Test having returns and yields.
@@ -468,6 +519,25 @@ int
 .. index:: """)
 
 
+def test_receives_str():
+    line_by_line_compare(str(doc_sent),
+"""Test generator
+
+Yields
+------
+a : int
+    The number of apples.
+
+Receives
+--------
+b : int
+    The number of bananas.
+c : int
+    The number of oranges.
+
+.. index:: """)
+
+
 def test_no_index_in_str():
     assert "index" not in str(NumpyDocString("""Test idx