4

I'm starting to document my first asyncio-based project with Sphinx. I noticed some projects have this "coroutine" prefix before some methods and I'd like to do the same in my project's documentation, but I can't figure out how.

For example, aiohttp's HTTP client reference shows this:

class aiohttp.ClientSession(...):

coroutine request(...)

That project seems to use a coroutinemethod directive to achieve this, but I document all my functions and classes inline using docstrings and this directive only works if you write the docs in a reStructuredText document.

Does anybody know how to achieve this result with autodoc?

Edit: I will also accept answers that explain how to make a Sphinx extension to do this if Sphinx doesn't support it. Bonus points if someone can point my to some kind of way that automatically detects whether the method is a coroutine or not using inspect.iscoroutinefunction().

Edit: I'm looking at the "pyspecific" Sphinx extension in the CPython project for inspiration. However, I need to change autodoc's behavior, not add new directives. After a bit of research, it looks like autodoc has an autodoc-process-signature event which can be used to customize the function signature, but it doesn't seem to have the object used by the "pyspecific" extension.

bad_coder
  • 11,289
  • 20
  • 44
  • 72
André Caron
  • 44,541
  • 12
  • 67
  • 125

1 Answers1

3

This functionality is not already built-in to Sphinx. However, pull request #1826 adds support for generators, coroutine functions, coroutine methods with built-in detection of coroutines in autodoc's autofunction and automethod directives.

Here is a patch that I applied locally to enable this in Sphinx 1.4 (requires disabling sphinx-build's -W "turn warnings into errors" option):

# Recipe stolen from open PR (https://github.com/sphinx-doc/sphinx/pull/1826).


from inspect import iscoroutinefunction

from sphinx import addnodes
from sphinx.domains.python import (
    PyClassmember,
    PyModulelevel,
)
from sphinx.ext.autodoc import FunctionDocumenter as _FunctionDocumenter
from sphinx.ext.autodoc import MethodDocumenter as _MethodDocumenter


class PyCoroutineMixin(object):
    """Helper for coroutine-related Sphinx custom directives."""

    def handle_signature(self, sig, signode):
        ret = super(PyCoroutineMixin, self).handle_signature(sig, signode)
        signode.insert(0, addnodes.desc_annotation('coroutine ', 'coroutine '))
        return ret


class PyCoroutineFunction(PyCoroutineMixin, PyModulelevel):
    """Sphinx directive for coroutine functions."""

    def run(self):
        self.name = 'py:function'
        return PyModulelevel.run(self)


class PyCoroutineMethod(PyCoroutineMixin, PyClassmember):
    """Sphinx directive for coroutine methods."""

    def run(self):
        self.name = 'py:method'
        return PyClassmember.run(self)


class FunctionDocumenter(_FunctionDocumenter):
    """Automatically detect coroutine functions."""

    def import_object(self):
        ret = _FunctionDocumenter.import_object(self)
        if not ret:
            return ret

        obj = self.parent.__dict__.get(self.object_name)
        if iscoroutinefunction(obj):
            self.directivetype = 'coroutine'
            self.member_order = _FunctionDocumenter.member_order + 2
        return ret


class MethodDocumenter(_MethodDocumenter):
    """Automatically detect coroutine methods."""

    def import_object(self):
        ret = _MethodDocumenter.import_object(self)
        if not ret:
            return ret

        obj = self.parent.__dict__.get(self.object_name)
        if iscoroutinefunction(obj):
            self.directivetype = 'coroutinemethod'
            self.member_order = _MethodDocumenter.member_order + 2
        return ret


def setup(app):
    """Sphinx extension entry point."""

    # Add new directives.
    app.add_directive_to_domain('py', 'coroutine', PyCoroutineFunction)
    app.add_directive_to_domain('py', 'coroutinemethod', PyCoroutineMethod)

    # Customize annotations for anything that looks like a coroutine.
    app.add_autodocumenter(FunctionDocumenter)
    app.add_autodocumenter(MethodDocumenter)

    # Return extension meta data.
    return {
        'version': '1.0',
        'parallel_read_safe': True,
    }
André Caron
  • 44,541
  • 12
  • 67
  • 125