diff --git a/CHANGES b/CHANGES index 31f18b850..b67466739 100644 --- a/CHANGES +++ b/CHANGES @@ -51,6 +51,7 @@ Bugs fixed * #6559: Wrong node-ids are generated in glossary directive * #6986: apidoc: misdetects module name for .so file inside module * #6999: napoleon: fails to parse tilde in :exc: role +* #7023: autodoc: nested partial functions are not listed Testing -------- diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index 8d3392500..ab3038b05 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -121,6 +121,17 @@ def isenumattribute(x: Any) -> bool: return isinstance(x, enum.Enum) +def unpartial(obj: Any) -> Any: + """Get an original object from partial object. + + This returns given object itself if not partial. + """ + while ispartial(obj): + obj = obj.func + + return obj + + def ispartial(obj: Any) -> bool: """Check if the object is partial.""" return isinstance(obj, (partial, partialmethod)) @@ -197,24 +208,21 @@ def isattributedescriptor(obj: Any) -> bool: def isfunction(obj: Any) -> bool: """Check if the object is function.""" - return inspect.isfunction(obj) or ispartial(obj) and inspect.isfunction(obj.func) + return inspect.isfunction(unpartial(obj)) def isbuiltin(obj: Any) -> bool: """Check if the object is builtin.""" - return inspect.isbuiltin(obj) or ispartial(obj) and inspect.isbuiltin(obj.func) + return inspect.isbuiltin(unpartial(obj)) def iscoroutinefunction(obj: Any) -> bool: """Check if the object is coroutine-function.""" + obj = unpartial(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) return True - elif (ispartial(obj) and hasattr(obj.func, '__code__') and - inspect.iscoroutinefunction(obj.func)): - # partialed - return True else: return False diff --git a/tests/roots/test-ext-autodoc/target/partialfunction.py b/tests/roots/test-ext-autodoc/target/partialfunction.py index 727a62680..3be63eeee 100644 --- a/tests/roots/test-ext-autodoc/target/partialfunction.py +++ b/tests/roots/test-ext-autodoc/target/partialfunction.py @@ -1,11 +1,12 @@ from functools import partial -def func1(): +def func1(a, b, c): """docstring of func1""" pass -func2 = partial(func1) -func3 = partial(func1) +func2 = partial(func1, 1) +func3 = partial(func2, 2) func3.__doc__ = "docstring of func3" +func4 = partial(func3, 3) diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py index bd13cf6c2..b1161deac 100644 --- a/tests/test_autodoc.py +++ b/tests/test_autodoc.py @@ -1241,19 +1241,25 @@ def test_partialfunction(): '.. py:module:: target.partialfunction', '', '', - '.. py:function:: func1()', + '.. py:function:: func1(a, b, c)', ' :module: target.partialfunction', '', ' docstring of func1', ' ', '', - '.. py:function:: func2()', + '.. py:function:: func2(b, c)', ' :module: target.partialfunction', '', ' docstring of func1', ' ', '', - '.. py:function:: func3()', + '.. py:function:: func3(c)', + ' :module: target.partialfunction', + '', + ' docstring of func3', + ' ', + '', + '.. py:function:: func4()', ' :module: target.partialfunction', '', ' docstring of func3', diff --git a/tests/test_util_inspect.py b/tests/test_util_inspect.py index 5e035c6a9..34844c9bf 100644 --- a/tests/test_util_inspect.py +++ b/tests/test_util_inspect.py @@ -511,3 +511,15 @@ def test_isproperty(app): assert inspect.isproperty(Base.meth) is False # method of class assert inspect.isproperty(Base().meth) is False # method of instance assert inspect.isproperty(func) is False # function + + +def test_unpartial(): + def func1(a, b, c): + pass + + func2 = functools.partial(func1, 1) + func2.__doc__ = "func2" + func3 = functools.partial(func2, 2) # nested partial object + + assert inspect.unpartial(func2) is func1 + assert inspect.unpartial(func3) is func1