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
|
||||
* #6289: autodoc: :confval:`autodoc_default_options` now supports
|
||||
``imported-members`` option
|
||||
* #4777: autodoc: Support coroutine
|
||||
* #6212 autosummary: Add :confval:`autosummary_imported_members` to display
|
||||
imported members on autosummary
|
||||
* #6271: ``make clean`` is catastrophically broken if building into '.'
|
||||
* Add ``:classmethod:`` and ``:staticmethod:`` options to :rst:dir:`py:method`
|
||||
directive
|
||||
* #4777: py domain: Add ``:async:`` option to :rst:dir:`py:function` directive
|
||||
* py domain: Add ``:async:``, ``:classmethod:`` and ``:staticmethod:`` options
|
||||
to :rst:dir:`py:method` directive
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
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
|
||||
: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
|
||||
no value) to indicate the method is a class method (or a static method).
|
||||
|
||||
.. versionchanged:: 2.1
|
||||
|
||||
``:classmethod:`` and ``:staticmethod:`` options added.
|
||||
``:async:``, ``:classmethod:`` and ``:staticmethod:`` options added.
|
||||
|
||||
.. rst:directive:: .. py:staticmethod:: name(parameters)
|
||||
|
||||
|
@ -438,6 +438,18 @@ class PyModulelevel(PyObject):
|
||||
class PyFunction(PyObject):
|
||||
"""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):
|
||||
# type: () -> bool
|
||||
return True
|
||||
@ -573,6 +585,7 @@ class PyMethod(PyObject):
|
||||
|
||||
option_spec = PyObject.option_spec.copy()
|
||||
option_spec.update({
|
||||
'async': directives.flag,
|
||||
'classmethod': directives.flag,
|
||||
'staticmethod': directives.flag,
|
||||
})
|
||||
@ -583,10 +596,16 @@ class PyMethod(PyObject):
|
||||
|
||||
def get_signature_prefix(self, sig):
|
||||
# type: (str) -> str
|
||||
prefix = []
|
||||
if 'async' in self.options:
|
||||
prefix.append('async')
|
||||
if 'staticmethod' in self.options:
|
||||
return 'static '
|
||||
elif 'classmethod' in self.options:
|
||||
return 'classmethod '
|
||||
prefix.append('static')
|
||||
if 'classmethod' in self.options:
|
||||
prefix.append('classmethod')
|
||||
|
||||
if prefix:
|
||||
return ' '.join(prefix) + ' '
|
||||
else:
|
||||
return ''
|
||||
|
||||
|
@ -1034,6 +1034,14 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ
|
||||
# type: (bool) -> None
|
||||
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):
|
||||
"""
|
||||
@ -1318,9 +1326,11 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
|
||||
|
||||
sourcename = self.get_sourcename()
|
||||
obj = self.parent.__dict__.get(self.object_name, self.object)
|
||||
if inspect.iscoroutinefunction(obj):
|
||||
self.add_line(' :async:', sourcename)
|
||||
if inspect.isclassmethod(obj):
|
||||
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)
|
||||
|
||||
def document_members(self, all_members=False):
|
||||
|
@ -381,6 +381,10 @@ class VariableCommentPicker(ast.NodeVisitor):
|
||||
self.context.pop()
|
||||
self.current_function = None
|
||||
|
||||
def visit_AsyncFunctionDef(self, node):
|
||||
# type: (ast.AsyncFunctionDef) -> None
|
||||
self.visit_FunctionDef(node) # type: ignore
|
||||
|
||||
|
||||
class DefinitionFinder(TokenProcessor):
|
||||
def __init__(self, lines):
|
||||
|
@ -15,7 +15,7 @@ import re
|
||||
import sys
|
||||
import typing
|
||||
import warnings
|
||||
from functools import partial
|
||||
from functools import partial, partialmethod
|
||||
from inspect import ( # NOQA
|
||||
isclass, ismethod, ismethoddescriptor, isroutine
|
||||
)
|
||||
@ -129,7 +129,7 @@ def isenumattribute(x):
|
||||
def ispartial(obj):
|
||||
# type: (Any) -> bool
|
||||
"""Check if the object is partial."""
|
||||
return isinstance(obj, partial)
|
||||
return isinstance(obj, (partial, partialmethod))
|
||||
|
||||
|
||||
def isclassmethod(obj):
|
||||
@ -212,6 +212,18 @@ def isbuiltin(obj):
|
||||
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):
|
||||
# type: (Any, str, str) -> object
|
||||
"""A getattr() that turns all exceptions into AttributeErrors."""
|
||||
|
@ -5,7 +5,11 @@ def func():
|
||||
pass
|
||||
|
||||
|
||||
async def coroutinefunc():
|
||||
pass
|
||||
|
||||
partial_func = partial(func)
|
||||
partial_coroutinefunc = partial(coroutinefunc)
|
||||
|
||||
builtin_func = print
|
||||
partial_builtin_func = partial(print)
|
||||
|
@ -19,6 +19,11 @@ class Base():
|
||||
|
||||
partialmeth = partialmethod(meth)
|
||||
|
||||
async def coroutinemeth(self):
|
||||
pass
|
||||
|
||||
partial_coroutinemeth = partialmethod(coroutinemeth)
|
||||
|
||||
|
||||
class Inherited(Base):
|
||||
pass
|
||||
|
@ -1523,6 +1523,15 @@ def test_bound_method():
|
||||
|
||||
@pytest.mark.usefixtures('setup_test')
|
||||
def test_coroutine():
|
||||
actual = do_autodoc(app, 'function', 'target.functions.coroutinefunc')
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:function:: coroutinefunc()',
|
||||
' :module: target.functions',
|
||||
' :async:',
|
||||
'',
|
||||
]
|
||||
|
||||
options = {"members": None}
|
||||
actual = do_autodoc(app, 'class', 'target.coroutine.AsyncClass', options)
|
||||
assert list(actual) == [
|
||||
@ -1533,6 +1542,7 @@ def test_coroutine():
|
||||
' ',
|
||||
' .. py:method:: AsyncClass.do_coroutine()',
|
||||
' :module: target.coroutine',
|
||||
' :async:',
|
||||
' ',
|
||||
' A documented coroutine function',
|
||||
' '
|
||||
|
@ -304,15 +304,24 @@ def test_pydata(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')
|
||||
doctree = restructuredtext.parse(app, text)
|
||||
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_content, ()])]))
|
||||
assert 'func' in domain.objects
|
||||
assert domain.objects['func'] == ('index', 'function')
|
||||
assert 'func1' in domain.objects
|
||||
assert domain.objects['func1'] == ('index', 'function')
|
||||
assert 'func2' in domain.objects
|
||||
assert domain.objects['func2'] == ('index', 'function')
|
||||
|
||||
|
||||
def test_pymethod_options(app):
|
||||
@ -322,7 +331,9 @@ def test_pymethod_options(app):
|
||||
" .. py:method:: meth2\n"
|
||||
" :classmethod:\n"
|
||||
" .. py:method:: meth3\n"
|
||||
" :staticmethod:\n")
|
||||
" :staticmethod:\n"
|
||||
" .. py:method:: meth4\n"
|
||||
" :async:\n")
|
||||
domain = app.env.get_domain('py')
|
||||
doctree = restructuredtext.parse(app, text)
|
||||
assert_node(doctree, (addnodes.index,
|
||||
@ -333,6 +344,8 @@ def test_pymethod_options(app):
|
||||
addnodes.index,
|
||||
desc,
|
||||
addnodes.index,
|
||||
desc,
|
||||
addnodes.index,
|
||||
desc)])]))
|
||||
|
||||
# method
|
||||
@ -364,6 +377,16 @@ def test_pymethod_options(app):
|
||||
assert 'Class.meth3' in domain.objects
|
||||
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):
|
||||
text = (".. py:class:: Class\n"
|
||||
|
@ -314,6 +314,21 @@ def test_decorators():
|
||||
'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():
|
||||
source = ('class Foo:\n'
|
||||
'\f\n'
|
||||
|
@ -397,6 +397,22 @@ def test_isstaticmethod(app):
|
||||
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')
|
||||
def test_isfunction(app):
|
||||
from target.functions import builtin_func, partial_builtin_func
|
||||
|
Loading…
Reference in New Issue
Block a user