summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTakeshi KOMIYA <i.tkomiya@gmail.com>2020-02-22 00:28:35 +0900
committerTakeshi KOMIYA <i.tkomiya@gmail.com>2020-02-22 00:49:45 +0900
commit2fec37219fc8d99f96026308116ef859c67d7588 (patch)
treea9359eccc69155a47a6198cc5ebb7dedc6df29a3
parent130a0a7f3877e55fca5ead70edd880fc2f23ff46 (diff)
downloadsphinx-git-2fec37219fc8d99f96026308116ef859c67d7588.tar.gz
Fix #7189: autodoc: classmethod coroutines are not detected
-rw-r--r--CHANGES1
-rw-r--r--sphinx/util/inspect.py21
-rw-r--r--tests/roots/test-ext-autodoc/target/coroutine.py10
-rw-r--r--tests/test_autodoc.py18
-rw-r--r--tests/test_util_inspect.py10
5 files changed, 55 insertions, 5 deletions
diff --git a/CHANGES b/CHANGES
index a34909ae9..10762b617 100644
--- a/CHANGES
+++ b/CHANGES
@@ -18,6 +18,7 @@ Bugs fixed
* #7184: autodoc: ``*args`` and ``**kwarg`` in type comments are not handled
properly
+* #7189: autodoc: classmethod coroutines are not detected
Testing
--------
diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py
index 619512fd7..2bcccdd60 100644
--- a/sphinx/util/inspect.py
+++ b/sphinx/util/inspect.py
@@ -111,6 +111,19 @@ def getargspec(func):
kwonlyargs, kwdefaults, annotations)
+def unwrap(obj: Any) -> Any:
+ """Get an original object from wrapped object."""
+ while True:
+ if ispartial(obj):
+ obj = unpartial(obj)
+ elif isclassmethod(obj):
+ obj = obj.__func__
+ elif isstaticmethod(obj):
+ obj = obj.__func__
+ else:
+ return obj
+
+
def isenumclass(x: Any) -> bool:
"""Check if the object is subclass of enum."""
return inspect.isclass(x) and issubclass(x, enum.Enum)
@@ -141,7 +154,7 @@ def isclassmethod(obj: Any) -> bool:
"""Check if the object is classmethod."""
if isinstance(obj, classmethod):
return True
- elif inspect.ismethod(obj) and obj.__self__ is not None:
+ elif inspect.ismethod(obj) and obj.__self__ is not None and isclass(obj.__self__):
return True
return False
@@ -208,17 +221,17 @@ def isattributedescriptor(obj: Any) -> bool:
def isfunction(obj: Any) -> bool:
"""Check if the object is function."""
- return inspect.isfunction(unpartial(obj))
+ return inspect.isfunction(unwrap(obj))
def isbuiltin(obj: Any) -> bool:
"""Check if the object is builtin."""
- return inspect.isbuiltin(unpartial(obj))
+ return inspect.isbuiltin(unwrap(obj))
def iscoroutinefunction(obj: Any) -> bool:
"""Check if the object is coroutine-function."""
- obj = unpartial(obj)
+ obj = unwrap(obj)
if hasattr(obj, '__code__') and inspect.iscoroutinefunction(obj):
# check obj.__code__ because iscoroutinefunction() crashes for custom method-like
# objects (see https://github.com/sphinx-doc/sphinx/issues/6605)
diff --git a/tests/roots/test-ext-autodoc/target/coroutine.py b/tests/roots/test-ext-autodoc/target/coroutine.py
index b3223a820..69602325d 100644
--- a/tests/roots/test-ext-autodoc/target/coroutine.py
+++ b/tests/roots/test-ext-autodoc/target/coroutine.py
@@ -3,6 +3,16 @@ class AsyncClass:
"""A documented coroutine function"""
attr_coro_result = await _other_coro_func() # NOQA
+ @classmethod
+ async def do_coroutine2(cls):
+ """A documented coroutine classmethod"""
+ pass
+
+ @staticmethod
+ async def do_coroutine3():
+ """A documented coroutine staticmethod"""
+ pass
+
async def _other_coro_func():
return "run"
diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py
index 2e8ff0414..a86211f18 100644
--- a/tests/test_autodoc.py
+++ b/tests/test_autodoc.py
@@ -1319,7 +1319,23 @@ def test_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_util_inspect.py b/tests/test_util_inspect.py
index c6b2c9149..a8019a9c3 100644
--- a/tests/test_util_inspect.py
+++ b/tests/test_util_inspect.py
@@ -420,6 +420,16 @@ def test_dict_customtype():
@pytest.mark.sphinx(testroot='ext-autodoc')
+def test_isclassmethod(app):
+ from target.methods import Base, Inherited
+
+ assert inspect.isclassmethod(Base.classmeth) is True
+ assert inspect.isclassmethod(Base.meth) is False
+ assert inspect.isclassmethod(Inherited.classmeth) is True
+ assert inspect.isclassmethod(Inherited.meth) is False
+
+
+@pytest.mark.sphinx(testroot='ext-autodoc')
def test_isstaticmethod(app):
from target.methods import Base, Inherited