Browse Source

Teach autodoc to document tasks if undoc-members is not set (#4588)

Celery tasks created with the @task decorator have the property
that `obj.__doc__` and `obj.__class__.__doc__` are equal, which
trips up the logic in `sphinx.ext.autodoc` that is supposed to
suppress repetition of class documentation in an instance of the
class. We have to override that behavior so that autodoc picks
up tasks whether or not autodoc's `undoc-members` flag is set.

Because autodoc really works now, we also have to correctly
check that the task was defined in the current module!

Fixes #4587.
Leo Singer 7 years ago
parent
commit
a6b3e2278e

+ 21 - 3
celery/contrib/sphinx.py

@@ -32,6 +32,7 @@ Use ``.. autotask::`` to alternatively manually document a task.
 from __future__ import absolute_import, unicode_literals
 
 from celery.app.task import BaseTask
+from celery.local import PromiseProxy
 from sphinx.domains.python import PyModulelevel
 from sphinx.ext.autodoc import FunctionDocumenter
 
@@ -68,11 +69,13 @@ class TaskDocumenter(FunctionDocumenter):
     def check_module(self):
         # Normally checks if *self.object* is really defined in the module
         # given by *self.modname*. But since functions decorated with the @task
-        # decorator are instances living in the celery.local module we're
-        # checking for that and simply agree to document those then.
+        # decorator are instances living in the celery.local, we have to check
+        # the wrapped function instead.
         modname = self.get_attr(self.object, '__module__', None)
         if modname and modname == 'celery.local':
-            return True
+            wrapped = getattr(self.object, '__wrapped__', None)
+            if wrapped and getattr(wrapped, '__module__') == self.modname:
+                return True
         return super(TaskDocumenter, self).check_module()
 
 
@@ -83,11 +86,26 @@ class TaskDirective(PyModulelevel):
         return self.env.config.celery_task_prefix
 
 
+def autodoc_skip_member_handler(app, what, name, obj, skip, options):
+    """Handler for autodoc-skip-member event."""
+    # Celery tasks created with the @task decorator have the property
+    # that *obj.__doc__* and *obj.__class__.__doc__* are equal, which
+    # trips up the logic in sphinx.ext.autodoc that is supposed to
+    # suppress repetition of class documentation in an instance of the
+    # class. This overrides that behavior.
+    if isinstance(obj, BaseTask) and getattr(obj, '__wrapped__'):
+        if skip and isinstance(obj, PromiseProxy):
+            return False
+    return None
+
+
 def setup(app):
     """Setup Sphinx extension."""
+    app.setup_extension('sphinx.ext.autodoc')
     app.add_autodocumenter(TaskDocumenter)
     app.add_directive_to_domain('py', 'task', TaskDirective)
     app.add_config_value('celery_task_prefix', '(task)', True)
+    app.connect('autodoc-skip-member', autodoc_skip_member_handler)
 
     return {
         'parallel_read_safe': True

+ 2 - 3
t/unit/contrib/proj/conf.py

@@ -3,8 +3,7 @@ from __future__ import absolute_import, unicode_literals
 import os
 import sys
 
-extensions = ['celery.contrib.sphinx', 'sphinx.ext.autodoc']
-autodoc_default_flags = ['members', 'undoc-members']
-autosummary_generate = True
+extensions = ['celery.contrib.sphinx']
+autodoc_default_flags = ['members']
 
 sys.path.insert(0, os.path.abspath('.'))

+ 1 - 0
t/unit/contrib/proj/foo.py

@@ -1,6 +1,7 @@
 from __future__ import absolute_import, unicode_literals
 
 from celery import Celery
+from xyzzy import plugh  # noqa
 
 app = Celery()
 

+ 10 - 0
t/unit/contrib/proj/xyzzy.py

@@ -0,0 +1,10 @@
+from __future__ import absolute_import, unicode_literals
+
+from celery import Celery
+
+app = Celery()
+
+
+@app.task
+def plugh():
+    """This task is in a different module!"""

+ 1 - 0
t/unit/contrib/test_sphinx.py

@@ -17,3 +17,4 @@ def test_sphinx(tmpdir):
     with open(tmpdir / 'contents.html', 'r') as f:
         contents = f.read()
     assert 'This task has a docstring!' in contents
+    assert 'This task is in a different module!' not in contents