mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Merge pull request #6295 from tk0miya/refactor_py_domain4
autodoc: Support coroutine (refs: #4777)
This commit is contained in:
commit
b49ef12e6a
6
CHANGES
6
CHANGES
@ -75,11 +75,13 @@ Features added
|
|||||||
functions
|
functions
|
||||||
* #6289: autodoc: :confval:`autodoc_default_options` now supports
|
* #6289: autodoc: :confval:`autodoc_default_options` now supports
|
||||||
``imported-members`` option
|
``imported-members`` option
|
||||||
|
* #4777: autodoc: Support coroutine
|
||||||
* #6212 autosummary: Add :confval:`autosummary_imported_members` to display
|
* #6212 autosummary: Add :confval:`autosummary_imported_members` to display
|
||||||
imported members on autosummary
|
imported members on autosummary
|
||||||
* #6271: ``make clean`` is catastrophically broken if building into '.'
|
* #6271: ``make clean`` is catastrophically broken if building into '.'
|
||||||
* Add ``:classmethod:`` and ``:staticmethod:`` options to :rst:dir:`py:method`
|
* #4777: py domain: Add ``:async:`` option to :rst:dir:`py:function` directive
|
||||||
directive
|
* py domain: Add ``:async:``, ``:classmethod:`` and ``:staticmethod:`` options
|
||||||
|
to :rst:dir:`py:method` directive
|
||||||
|
|
||||||
Bugs fixed
|
Bugs fixed
|
||||||
----------
|
----------
|
||||||
|
@ -169,6 +169,13 @@ The following directives are provided for module and class contents:
|
|||||||
This information can (in any ``py`` directive) optionally be given in a
|
This information can (in any ``py`` directive) optionally be given in a
|
||||||
structured form, see :ref:`info-field-lists`.
|
structured form, see :ref:`info-field-lists`.
|
||||||
|
|
||||||
|
The ``async`` option can be given (with no value) to indicate the function is
|
||||||
|
an async method.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.1
|
||||||
|
|
||||||
|
``:async:`` option added.
|
||||||
|
|
||||||
.. rst:directive:: .. py:data:: name
|
.. rst:directive:: .. py:data:: name
|
||||||
|
|
||||||
Describes global data in a module, including both variables and values used
|
Describes global data in a module, including both variables and values used
|
||||||
@ -216,12 +223,15 @@ The following directives are provided for module and class contents:
|
|||||||
described for ``function``. See also :ref:`signatures` and
|
described for ``function``. See also :ref:`signatures` and
|
||||||
:ref:`info-field-lists`.
|
:ref:`info-field-lists`.
|
||||||
|
|
||||||
|
The ``async`` option can be given (with no value) to indicate the method is
|
||||||
|
an async method.
|
||||||
|
|
||||||
The ``classmethod`` option and ``staticmethod`` option can be given (with
|
The ``classmethod`` option and ``staticmethod`` option can be given (with
|
||||||
no value) to indicate the method is a class method (or a static method).
|
no value) to indicate the method is a class method (or a static method).
|
||||||
|
|
||||||
.. versionchanged:: 2.1
|
.. versionchanged:: 2.1
|
||||||
|
|
||||||
``:classmethod:`` and ``:staticmethod:`` options added.
|
``:async:``, ``:classmethod:`` and ``:staticmethod:`` options added.
|
||||||
|
|
||||||
.. rst:directive:: .. py:staticmethod:: name(parameters)
|
.. rst:directive:: .. py:staticmethod:: name(parameters)
|
||||||
|
|
||||||
|
@ -438,6 +438,18 @@ class PyModulelevel(PyObject):
|
|||||||
class PyFunction(PyObject):
|
class PyFunction(PyObject):
|
||||||
"""Description of a function."""
|
"""Description of a function."""
|
||||||
|
|
||||||
|
option_spec = PyObject.option_spec.copy()
|
||||||
|
option_spec.update({
|
||||||
|
'async': directives.flag,
|
||||||
|
})
|
||||||
|
|
||||||
|
def get_signature_prefix(self, sig):
|
||||||
|
# type: (str) -> str
|
||||||
|
if 'async' in self.options:
|
||||||
|
return 'async '
|
||||||
|
else:
|
||||||
|
return ''
|
||||||
|
|
||||||
def needs_arglist(self):
|
def needs_arglist(self):
|
||||||
# type: () -> bool
|
# type: () -> bool
|
||||||
return True
|
return True
|
||||||
@ -573,6 +585,7 @@ class PyMethod(PyObject):
|
|||||||
|
|
||||||
option_spec = PyObject.option_spec.copy()
|
option_spec = PyObject.option_spec.copy()
|
||||||
option_spec.update({
|
option_spec.update({
|
||||||
|
'async': directives.flag,
|
||||||
'classmethod': directives.flag,
|
'classmethod': directives.flag,
|
||||||
'staticmethod': directives.flag,
|
'staticmethod': directives.flag,
|
||||||
})
|
})
|
||||||
@ -583,10 +596,16 @@ class PyMethod(PyObject):
|
|||||||
|
|
||||||
def get_signature_prefix(self, sig):
|
def get_signature_prefix(self, sig):
|
||||||
# type: (str) -> str
|
# type: (str) -> str
|
||||||
|
prefix = []
|
||||||
|
if 'async' in self.options:
|
||||||
|
prefix.append('async')
|
||||||
if 'staticmethod' in self.options:
|
if 'staticmethod' in self.options:
|
||||||
return 'static '
|
prefix.append('static')
|
||||||
elif 'classmethod' in self.options:
|
if 'classmethod' in self.options:
|
||||||
return 'classmethod '
|
prefix.append('classmethod')
|
||||||
|
|
||||||
|
if prefix:
|
||||||
|
return ' '.join(prefix) + ' '
|
||||||
else:
|
else:
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
|
@ -1034,6 +1034,14 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ
|
|||||||
# type: (bool) -> None
|
# type: (bool) -> None
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def add_directive_header(self, sig):
|
||||||
|
# type: (str) -> None
|
||||||
|
sourcename = self.get_sourcename()
|
||||||
|
super().add_directive_header(sig)
|
||||||
|
|
||||||
|
if inspect.iscoroutinefunction(self.object):
|
||||||
|
self.add_line(' :async:', sourcename)
|
||||||
|
|
||||||
|
|
||||||
class DecoratorDocumenter(FunctionDocumenter):
|
class DecoratorDocumenter(FunctionDocumenter):
|
||||||
"""
|
"""
|
||||||
@ -1318,9 +1326,11 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
|
|||||||
|
|
||||||
sourcename = self.get_sourcename()
|
sourcename = self.get_sourcename()
|
||||||
obj = self.parent.__dict__.get(self.object_name, self.object)
|
obj = self.parent.__dict__.get(self.object_name, self.object)
|
||||||
|
if inspect.iscoroutinefunction(obj):
|
||||||
|
self.add_line(' :async:', sourcename)
|
||||||
if inspect.isclassmethod(obj):
|
if inspect.isclassmethod(obj):
|
||||||
self.add_line(' :classmethod:', sourcename)
|
self.add_line(' :classmethod:', sourcename)
|
||||||
elif inspect.isstaticmethod(obj, cls=self.parent, name=self.object_name):
|
if inspect.isstaticmethod(obj, cls=self.parent, name=self.object_name):
|
||||||
self.add_line(' :staticmethod:', sourcename)
|
self.add_line(' :staticmethod:', sourcename)
|
||||||
|
|
||||||
def document_members(self, all_members=False):
|
def document_members(self, all_members=False):
|
||||||
|
@ -381,6 +381,10 @@ class VariableCommentPicker(ast.NodeVisitor):
|
|||||||
self.context.pop()
|
self.context.pop()
|
||||||
self.current_function = None
|
self.current_function = None
|
||||||
|
|
||||||
|
def visit_AsyncFunctionDef(self, node):
|
||||||
|
# type: (ast.AsyncFunctionDef) -> None
|
||||||
|
self.visit_FunctionDef(node) # type: ignore
|
||||||
|
|
||||||
|
|
||||||
class DefinitionFinder(TokenProcessor):
|
class DefinitionFinder(TokenProcessor):
|
||||||
def __init__(self, lines):
|
def __init__(self, lines):
|
||||||
|
@ -15,7 +15,7 @@ import re
|
|||||||
import sys
|
import sys
|
||||||
import typing
|
import typing
|
||||||
import warnings
|
import warnings
|
||||||
from functools import partial
|
from functools import partial, partialmethod
|
||||||
from inspect import ( # NOQA
|
from inspect import ( # NOQA
|
||||||
isclass, ismethod, ismethoddescriptor, isroutine
|
isclass, ismethod, ismethoddescriptor, isroutine
|
||||||
)
|
)
|
||||||
@ -129,7 +129,7 @@ def isenumattribute(x):
|
|||||||
def ispartial(obj):
|
def ispartial(obj):
|
||||||
# type: (Any) -> bool
|
# type: (Any) -> bool
|
||||||
"""Check if the object is partial."""
|
"""Check if the object is partial."""
|
||||||
return isinstance(obj, partial)
|
return isinstance(obj, (partial, partialmethod))
|
||||||
|
|
||||||
|
|
||||||
def isclassmethod(obj):
|
def isclassmethod(obj):
|
||||||
@ -212,6 +212,18 @@ def isbuiltin(obj):
|
|||||||
return inspect.isbuiltin(obj) or ispartial(obj) and inspect.isbuiltin(obj.func)
|
return inspect.isbuiltin(obj) or ispartial(obj) and inspect.isbuiltin(obj.func)
|
||||||
|
|
||||||
|
|
||||||
|
def iscoroutinefunction(obj):
|
||||||
|
# type: (Any) -> bool
|
||||||
|
"""Check if the object is coroutine-function."""
|
||||||
|
if inspect.iscoroutinefunction(obj):
|
||||||
|
return True
|
||||||
|
elif ispartial(obj) and inspect.iscoroutinefunction(obj.func):
|
||||||
|
# partialed
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def safe_getattr(obj, name, *defargs):
|
def safe_getattr(obj, name, *defargs):
|
||||||
# type: (Any, str, str) -> object
|
# type: (Any, str, str) -> object
|
||||||
"""A getattr() that turns all exceptions into AttributeErrors."""
|
"""A getattr() that turns all exceptions into AttributeErrors."""
|
||||||
|
@ -5,7 +5,11 @@ def func():
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
async def coroutinefunc():
|
||||||
|
pass
|
||||||
|
|
||||||
partial_func = partial(func)
|
partial_func = partial(func)
|
||||||
|
partial_coroutinefunc = partial(coroutinefunc)
|
||||||
|
|
||||||
builtin_func = print
|
builtin_func = print
|
||||||
partial_builtin_func = partial(print)
|
partial_builtin_func = partial(print)
|
||||||
|
@ -19,6 +19,11 @@ class Base():
|
|||||||
|
|
||||||
partialmeth = partialmethod(meth)
|
partialmeth = partialmethod(meth)
|
||||||
|
|
||||||
|
async def coroutinemeth(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
partial_coroutinemeth = partialmethod(coroutinemeth)
|
||||||
|
|
||||||
|
|
||||||
class Inherited(Base):
|
class Inherited(Base):
|
||||||
pass
|
pass
|
||||||
|
@ -1523,6 +1523,15 @@ def test_bound_method():
|
|||||||
|
|
||||||
@pytest.mark.usefixtures('setup_test')
|
@pytest.mark.usefixtures('setup_test')
|
||||||
def test_coroutine():
|
def test_coroutine():
|
||||||
|
actual = do_autodoc(app, 'function', 'target.functions.coroutinefunc')
|
||||||
|
assert list(actual) == [
|
||||||
|
'',
|
||||||
|
'.. py:function:: coroutinefunc()',
|
||||||
|
' :module: target.functions',
|
||||||
|
' :async:',
|
||||||
|
'',
|
||||||
|
]
|
||||||
|
|
||||||
options = {"members": None}
|
options = {"members": None}
|
||||||
actual = do_autodoc(app, 'class', 'target.coroutine.AsyncClass', options)
|
actual = do_autodoc(app, 'class', 'target.coroutine.AsyncClass', options)
|
||||||
assert list(actual) == [
|
assert list(actual) == [
|
||||||
@ -1533,6 +1542,7 @@ def test_coroutine():
|
|||||||
' ',
|
' ',
|
||||||
' .. py:method:: AsyncClass.do_coroutine()',
|
' .. py:method:: AsyncClass.do_coroutine()',
|
||||||
' :module: target.coroutine',
|
' :module: target.coroutine',
|
||||||
|
' :async:',
|
||||||
' ',
|
' ',
|
||||||
' A documented coroutine function',
|
' A documented coroutine function',
|
||||||
' '
|
' '
|
||||||
|
@ -304,15 +304,24 @@ def test_pydata(app):
|
|||||||
|
|
||||||
|
|
||||||
def test_pyfunction(app):
|
def test_pyfunction(app):
|
||||||
text = ".. py:function:: func\n"
|
text = (".. py:function:: func1\n"
|
||||||
|
".. py:function:: func2\n"
|
||||||
|
" :async:\n")
|
||||||
domain = app.env.get_domain('py')
|
domain = app.env.get_domain('py')
|
||||||
doctree = restructuredtext.parse(app, text)
|
doctree = restructuredtext.parse(app, text)
|
||||||
assert_node(doctree, (addnodes.index,
|
assert_node(doctree, (addnodes.index,
|
||||||
[desc, ([desc_signature, ([desc_name, "func"],
|
[desc, ([desc_signature, ([desc_name, "func1"],
|
||||||
|
[desc_parameterlist, ()])],
|
||||||
|
[desc_content, ()])],
|
||||||
|
addnodes.index,
|
||||||
|
[desc, ([desc_signature, ([desc_annotation, "async "],
|
||||||
|
[desc_name, "func2"],
|
||||||
[desc_parameterlist, ()])],
|
[desc_parameterlist, ()])],
|
||||||
[desc_content, ()])]))
|
[desc_content, ()])]))
|
||||||
assert 'func' in domain.objects
|
assert 'func1' in domain.objects
|
||||||
assert domain.objects['func'] == ('index', 'function')
|
assert domain.objects['func1'] == ('index', 'function')
|
||||||
|
assert 'func2' in domain.objects
|
||||||
|
assert domain.objects['func2'] == ('index', 'function')
|
||||||
|
|
||||||
|
|
||||||
def test_pymethod_options(app):
|
def test_pymethod_options(app):
|
||||||
@ -322,7 +331,9 @@ def test_pymethod_options(app):
|
|||||||
" .. py:method:: meth2\n"
|
" .. py:method:: meth2\n"
|
||||||
" :classmethod:\n"
|
" :classmethod:\n"
|
||||||
" .. py:method:: meth3\n"
|
" .. py:method:: meth3\n"
|
||||||
" :staticmethod:\n")
|
" :staticmethod:\n"
|
||||||
|
" .. py:method:: meth4\n"
|
||||||
|
" :async:\n")
|
||||||
domain = app.env.get_domain('py')
|
domain = app.env.get_domain('py')
|
||||||
doctree = restructuredtext.parse(app, text)
|
doctree = restructuredtext.parse(app, text)
|
||||||
assert_node(doctree, (addnodes.index,
|
assert_node(doctree, (addnodes.index,
|
||||||
@ -333,6 +344,8 @@ def test_pymethod_options(app):
|
|||||||
addnodes.index,
|
addnodes.index,
|
||||||
desc,
|
desc,
|
||||||
addnodes.index,
|
addnodes.index,
|
||||||
|
desc,
|
||||||
|
addnodes.index,
|
||||||
desc)])]))
|
desc)])]))
|
||||||
|
|
||||||
# method
|
# method
|
||||||
@ -364,6 +377,16 @@ def test_pymethod_options(app):
|
|||||||
assert 'Class.meth3' in domain.objects
|
assert 'Class.meth3' in domain.objects
|
||||||
assert domain.objects['Class.meth3'] == ('index', 'method')
|
assert domain.objects['Class.meth3'] == ('index', 'method')
|
||||||
|
|
||||||
|
# :async:
|
||||||
|
assert_node(doctree[1][1][6], addnodes.index,
|
||||||
|
entries=[('single', 'meth4() (Class method)', 'Class.meth4', '', None)])
|
||||||
|
assert_node(doctree[1][1][7], ([desc_signature, ([desc_annotation, "async "],
|
||||||
|
[desc_name, "meth4"],
|
||||||
|
[desc_parameterlist, ()])],
|
||||||
|
[desc_content, ()]))
|
||||||
|
assert 'Class.meth4' in domain.objects
|
||||||
|
assert domain.objects['Class.meth4'] == ('index', 'method')
|
||||||
|
|
||||||
|
|
||||||
def test_pyclassmethod(app):
|
def test_pyclassmethod(app):
|
||||||
text = (".. py:class:: Class\n"
|
text = (".. py:class:: Class\n"
|
||||||
|
@ -314,6 +314,21 @@ def test_decorators():
|
|||||||
'Foo.method': ('def', 13, 15)}
|
'Foo.method': ('def', 13, 15)}
|
||||||
|
|
||||||
|
|
||||||
|
def test_async_function_and_method():
|
||||||
|
source = ('async def some_function():\n'
|
||||||
|
' """docstring"""\n'
|
||||||
|
' a = 1 + 1 #: comment1\n'
|
||||||
|
'\n'
|
||||||
|
'class Foo:\n'
|
||||||
|
' async def method(self):\n'
|
||||||
|
' pass\n')
|
||||||
|
parser = Parser(source)
|
||||||
|
parser.parse()
|
||||||
|
assert parser.definitions == {'some_function': ('def', 1, 3),
|
||||||
|
'Foo': ('class', 5, 7),
|
||||||
|
'Foo.method': ('def', 6, 7)}
|
||||||
|
|
||||||
|
|
||||||
def test_formfeed_char():
|
def test_formfeed_char():
|
||||||
source = ('class Foo:\n'
|
source = ('class Foo:\n'
|
||||||
'\f\n'
|
'\f\n'
|
||||||
|
@ -397,6 +397,22 @@ def test_isstaticmethod(app):
|
|||||||
assert inspect.isstaticmethod(Inherited.meth, Inherited, 'meth') is False
|
assert inspect.isstaticmethod(Inherited.meth, Inherited, 'meth') is False
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.sphinx(testroot='ext-autodoc')
|
||||||
|
def test_iscoroutinefunction(app):
|
||||||
|
from target.functions import coroutinefunc, func, partial_coroutinefunc
|
||||||
|
from target.methods import Base
|
||||||
|
|
||||||
|
assert inspect.iscoroutinefunction(func) is False # function
|
||||||
|
assert inspect.iscoroutinefunction(coroutinefunc) is True # coroutine
|
||||||
|
assert inspect.iscoroutinefunction(partial_coroutinefunc) is True # partial-ed coroutine
|
||||||
|
assert inspect.iscoroutinefunction(Base.meth) is False # method
|
||||||
|
assert inspect.iscoroutinefunction(Base.coroutinemeth) is True # coroutine-method
|
||||||
|
|
||||||
|
# partial-ed coroutine-method
|
||||||
|
partial_coroutinemeth = Base.__dict__['partial_coroutinemeth']
|
||||||
|
assert inspect.iscoroutinefunction(partial_coroutinemeth) is True
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.sphinx(testroot='ext-autodoc')
|
@pytest.mark.sphinx(testroot='ext-autodoc')
|
||||||
def test_isfunction(app):
|
def test_isfunction(app):
|
||||||
from target.functions import builtin_func, partial_builtin_func
|
from target.functions import builtin_func, partial_builtin_func
|
||||||
|
Loading…
Reference in New Issue
Block a user