From ccfca458eac84342d8f90f8d6c93b4711a849614 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Fri, 17 Sep 2021 02:19:34 +0900 Subject: [PATCH] Close #9639: autodoc: Support asynchronous generator functions --- CHANGES | 2 + sphinx/ext/autodoc/__init__.py | 4 +- sphinx/util/inspect.py | 3 +- .../test-ext-autodoc/target/coroutine.py | 4 ++ .../test-ext-autodoc/target/functions.py | 4 ++ tests/test_ext_autodoc.py | 53 ------------------- tests/test_ext_autodoc_autoclass.py | 42 +++++++++++++++ tests/test_ext_autodoc_autofunction.py | 35 ++++++++++++ 8 files changed, 91 insertions(+), 56 deletions(-) diff --git a/CHANGES b/CHANGES index 9e8bc1151e..4129b148b4 100644 --- a/CHANGES +++ b/CHANGES @@ -13,6 +13,8 @@ Deprecated Features added -------------- +* #9639: autodoc: Support asynchronous generator functions + Bugs fixed ---------- diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index 956ec9726a..4de1236b00 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -1318,7 +1318,7 @@ def add_directive_header(self, sig: str) -> None: sourcename = self.get_sourcename() super().add_directive_header(sig) - if inspect.iscoroutinefunction(self.object): + if inspect.iscoroutinefunction(self.object) or inspect.isasyncgenfunction(self.object): self.add_line(' :async:', sourcename) def format_signature(self, **kwargs: Any) -> str: @@ -2137,7 +2137,7 @@ def add_directive_header(self, sig: str) -> None: obj = self.parent.__dict__.get(self.object_name, self.object) if inspect.isabstractmethod(obj): self.add_line(' :abstractmethod:', sourcename) - if inspect.iscoroutinefunction(obj): + if inspect.iscoroutinefunction(obj) or inspect.isasyncgenfunction(obj): self.add_line(' :async:', sourcename) if inspect.isclassmethod(obj): self.add_line(' :classmethod:', sourcename) diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index d55ebceecb..b767b8adca 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -19,7 +19,8 @@ import warnings from functools import partial, partialmethod from importlib import import_module -from inspect import Parameter, isclass, ismethod, ismethoddescriptor, ismodule # NOQA +from inspect import (Parameter, isasyncgenfunction, isclass, ismethod, # NOQA + ismethoddescriptor, ismodule) from io import StringIO from types import ModuleType from typing import Any, Callable, Dict, Mapping, Optional, Sequence, Tuple, Type, cast diff --git a/tests/roots/test-ext-autodoc/target/coroutine.py b/tests/roots/test-ext-autodoc/target/coroutine.py index 692dd48833..156d7f9614 100644 --- a/tests/roots/test-ext-autodoc/target/coroutine.py +++ b/tests/roots/test-ext-autodoc/target/coroutine.py @@ -17,6 +17,10 @@ async def do_coroutine3(): """A documented coroutine staticmethod""" pass + async def do_asyncgen(self): + """A documented async generator""" + yield + async def _other_coro_func(): return "run" diff --git a/tests/roots/test-ext-autodoc/target/functions.py b/tests/roots/test-ext-autodoc/target/functions.py index 8ff00f7344..b62aa70d22 100644 --- a/tests/roots/test-ext-autodoc/target/functions.py +++ b/tests/roots/test-ext-autodoc/target/functions.py @@ -8,6 +8,10 @@ def func(): async def coroutinefunc(): pass + +async def asyncgenerator(): + yield + partial_func = partial(func) partial_coroutinefunc = partial(coroutinefunc) diff --git a/tests/test_ext_autodoc.py b/tests/test_ext_autodoc.py index 299c1c6817..30676f7d16 100644 --- a/tests/test_ext_autodoc.py +++ b/tests/test_ext_autodoc.py @@ -1619,59 +1619,6 @@ def test_bound_method(app): ] -@pytest.mark.sphinx('html', testroot='ext-autodoc') -def test_coroutine(app): - actual = do_autodoc(app, 'function', 'target.functions.coroutinefunc') - assert list(actual) == [ - '', - '.. py:function:: coroutinefunc()', - ' :module: target.functions', - ' :async:', - '', - ] - - options = {"members": None} - actual = do_autodoc(app, 'class', 'target.coroutine.AsyncClass', options) - assert list(actual) == [ - '', - '.. py:class:: AsyncClass()', - ' :module: target.coroutine', - '', - '', - ' .. py:method:: AsyncClass.do_coroutine()', - ' :module: target.coroutine', - ' :async:', - '', - ' A documented coroutine function', - '', - '', - ' .. py:method:: AsyncClass.do_coroutine2()', - ' :module: target.coroutine', - ' :async:', - ' :classmethod:', - '', - ' A documented coroutine classmethod', - '', - '', - ' .. py:method:: AsyncClass.do_coroutine3()', - ' :module: target.coroutine', - ' :async:', - ' :staticmethod:', - '', - ' A documented coroutine staticmethod', - '', - ] - - # force-synchronized wrapper - actual = do_autodoc(app, 'function', 'target.coroutine.sync_func') - assert list(actual) == [ - '', - '.. py:function:: sync_func()', - ' :module: target.coroutine', - '', - ] - - @pytest.mark.sphinx('html', testroot='ext-autodoc') def test_partialmethod(app): expected = [ diff --git a/tests/test_ext_autodoc_autoclass.py b/tests/test_ext_autodoc_autoclass.py index 24617bf0a5..7f1cfe64e6 100644 --- a/tests/test_ext_autodoc_autoclass.py +++ b/tests/test_ext_autodoc_autoclass.py @@ -389,3 +389,45 @@ def test_class_alias_having_doccomment(app): ' docstring', '', ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_coroutine(app): + options = {"members": None} + actual = do_autodoc(app, 'class', 'target.coroutine.AsyncClass', options) + assert list(actual) == [ + '', + '.. py:class:: AsyncClass()', + ' :module: target.coroutine', + '', + '', + ' .. py:method:: AsyncClass.do_asyncgen()', + ' :module: target.coroutine', + ' :async:', + '', + ' A documented async generator', + '', + '', + ' .. py:method:: AsyncClass.do_coroutine()', + ' :module: target.coroutine', + ' :async:', + '', + ' A documented coroutine function', + '', + '', + ' .. py:method:: AsyncClass.do_coroutine2()', + ' :module: target.coroutine', + ' :async:', + ' :classmethod:', + '', + ' A documented coroutine classmethod', + '', + '', + ' .. py:method:: AsyncClass.do_coroutine3()', + ' :module: target.coroutine', + ' :async:', + ' :staticmethod:', + '', + ' A documented coroutine staticmethod', + '', + ] diff --git a/tests/test_ext_autodoc_autofunction.py b/tests/test_ext_autodoc_autofunction.py index ca2429b5e8..52af51abbf 100644 --- a/tests/test_ext_autodoc_autofunction.py +++ b/tests/test_ext_autodoc_autofunction.py @@ -168,3 +168,38 @@ def test_wrapped_function_contextmanager(app): " You'll feel better in this context!", '', ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_coroutine(app): + actual = do_autodoc(app, 'function', 'target.functions.coroutinefunc') + assert list(actual) == [ + '', + '.. py:function:: coroutinefunc()', + ' :module: target.functions', + ' :async:', + '', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_synchronized_coroutine(app): + actual = do_autodoc(app, 'function', 'target.coroutine.sync_func') + assert list(actual) == [ + '', + '.. py:function:: sync_func()', + ' :module: target.coroutine', + '', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_async_generator(app): + actual = do_autodoc(app, 'function', 'target.functions.asyncgenerator') + assert list(actual) == [ + '', + '.. py:function:: asyncgenerator()', + ' :module: target.functions', + ' :async:', + '', + ]