diff --git a/CHANGES b/CHANGES index c79f7c6fe..d4df88f30 100644 --- a/CHANGES +++ b/CHANGES @@ -132,6 +132,7 @@ Bugs fixed * #7267: autodoc: error message for invalid directive options has wrong location * #7329: autodoc: info-field-list is wrongly generated from type hints into the class description even if ``autoclass_content='class'`` set +* #7331: autodoc: a cython-function is not recognized as a function * #5637: inheritance_diagram: Incorrect handling of nested class names * #7139: ``code-block:: guess`` does not work * #7325: html: source_suffix containing dot leads to wrong source link diff --git a/setup.py b/setup.py index 088d5b8e7..a427d5493 100644 --- a/setup.py +++ b/setup.py @@ -52,6 +52,7 @@ extras_require = { 'pytest-cov', 'html5lib', 'typed_ast', # for py35-37 + 'cython', ], } diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index a81842c4a..7b6b3e4aa 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -1011,7 +1011,8 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ if self.env.config.autodoc_typehints in ('none', 'description'): kwargs.setdefault('show_annotation', False) - if inspect.isbuiltin(self.object) or inspect.ismethoddescriptor(self.object): + if ((inspect.isbuiltin(self.object) or inspect.ismethoddescriptor(self.object)) and + not inspect.is_cython_function_or_method(self.object)): # cannot introspect arguments of a C function or method return None try: @@ -1430,7 +1431,8 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type: if self.env.config.autodoc_typehints == 'none': kwargs.setdefault('show_annotation', False) - if inspect.isbuiltin(self.object) or inspect.ismethoddescriptor(self.object): + if ((inspect.isbuiltin(self.object) or inspect.ismethoddescriptor(self.object)) and + not inspect.is_cython_function_or_method(self.object)): # can never get arguments of a C function or method return None if inspect.isstaticmethod(self.object, cls=self.parent, name=self.object_name): diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index d22df7656..855b11d83 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -197,6 +197,14 @@ def isabstractmethod(obj: Any) -> bool: return safe_getattr(obj, '__isabstractmethod__', False) is True +def is_cython_function_or_method(obj: Any) -> bool: + """Check if the object is a function or method in cython.""" + try: + return obj.__class__.__name__ == 'cython_function_or_method' + except AttributeError: + return False + + def isattributedescriptor(obj: Any) -> bool: """Check if the object is an attribute like descriptor.""" if inspect.isdatadescriptor(object): @@ -207,6 +215,9 @@ def isattributedescriptor(obj: Any) -> bool: if isfunction(obj) or isbuiltin(obj) or inspect.ismethod(obj): # attribute must not be either function, builtin and method return False + elif is_cython_function_or_method(obj): + # attribute must not be either function and method (for cython) + return False elif inspect.isclass(obj): # attribute must not be a class return False diff --git a/tests/roots/test-ext-autodoc/target/cython.pyx b/tests/roots/test-ext-autodoc/target/cython.pyx new file mode 100644 index 000000000..1457db3c9 --- /dev/null +++ b/tests/roots/test-ext-autodoc/target/cython.pyx @@ -0,0 +1,12 @@ +# cython: binding=True + +def foo(*args, **kwargs): + """Docstring.""" + + +class Class: + """Docstring.""" + + def meth(self, name: str, age: int = 0) -> None: + """Docstring.""" + pass diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py index 012292999..741c4bb60 100644 --- a/tests/test_autodoc.py +++ b/tests/test_autodoc.py @@ -22,6 +22,13 @@ from sphinx.testing.util import SphinxTestApp, Struct # NOQA from sphinx.util import logging from sphinx.util.docutils import LoggingReporter +try: + # Enable pyximport to test cython module + import pyximport + pyximport.install() +except ImportError: + pyximport = None + app = None @@ -1609,3 +1616,34 @@ def test_singledispatchmethod(): ' A method for general use.', '', ] + + +@pytest.mark.usefixtures('setup_test') +@pytest.mark.skipif(pyximport is None, reason='cython is not installed') +def test_cython(): + options = {"members": None, + "undoc-members": None} + actual = do_autodoc(app, 'module', 'target.cython', options) + assert list(actual) == [ + '', + '.. py:module:: target.cython', + '', + '', + '.. py:class:: Class', + ' :module: target.cython', + '', + ' Docstring.', + '', + '', + ' .. py:method:: Class.meth(name: str, age: int = 0) -> None', + ' :module: target.cython', + '', + ' Docstring.', + '', + '', + '.. py:function:: foo(*args, **kwargs)', + ' :module: target.cython', + '', + ' Docstring.', + '', + ]