mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Merge pull request #6365 from tk0miya/refactor_py_domain
Add :abstractmethod: option to py:method directive and support abstractmethod by autodoc
This commit is contained in:
commit
557d8d140e
2
CHANGES
2
CHANGES
@ -82,6 +82,7 @@ Features added
|
|||||||
* #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
|
* #4777: autodoc: Support coroutine
|
||||||
|
* #744: autodoc: Support abstractmethod
|
||||||
* #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 '.'
|
||||||
@ -89,6 +90,7 @@ Features added
|
|||||||
* #4777: py domain: Add ``:async:`` option to :rst:dir:`py:function` directive
|
* #4777: py domain: Add ``:async:`` option to :rst:dir:`py:function` directive
|
||||||
* py domain: Add new options to :rst:dir:`py:method` directive
|
* py domain: Add new options to :rst:dir:`py:method` directive
|
||||||
|
|
||||||
|
- ``:abstractmethod:``
|
||||||
- ``:async:``
|
- ``:async:``
|
||||||
- ``:classmethod:``
|
- ``:classmethod:``
|
||||||
- ``:property:``
|
- ``:property:``
|
||||||
|
@ -226,16 +226,17 @@ The following directives are provided for module and class contents:
|
|||||||
The ``async`` option can be given (with no value) to indicate the method is
|
The ``async`` option can be given (with no value) to indicate the method is
|
||||||
an async method.
|
an async method.
|
||||||
|
|
||||||
The ``classmethod`` option and ``staticmethod`` option can be given (with
|
The ``abstractmethod``, ``classmethod`` option and ``staticmethod`` option
|
||||||
no value) to indicate the method is a class method (or a static method).
|
can be given (with no value) to indicate the method is an abstract method,
|
||||||
|
a class method or a static method.
|
||||||
|
|
||||||
The ``property`` option can be given (with no value) to indicate the method
|
The ``property`` option can be given (with no value) to indicate the method
|
||||||
is a property.
|
is a property.
|
||||||
|
|
||||||
.. versionchanged:: 2.1
|
.. versionchanged:: 2.1
|
||||||
|
|
||||||
``:async:``, ``:classmethod:``, ``:property:`` and ``:staticmethod:``
|
``:abstractmethod:``, ``:async:``, ``:classmethod:``, ``:property:`` and
|
||||||
options added.
|
``:staticmethod:`` options added.
|
||||||
|
|
||||||
.. rst:directive:: .. py:staticmethod:: name(parameters)
|
.. rst:directive:: .. py:staticmethod:: name(parameters)
|
||||||
|
|
||||||
|
@ -585,6 +585,7 @@ class PyMethod(PyObject):
|
|||||||
|
|
||||||
option_spec = PyObject.option_spec.copy()
|
option_spec = PyObject.option_spec.copy()
|
||||||
option_spec.update({
|
option_spec.update({
|
||||||
|
'abstractmethod': directives.flag,
|
||||||
'async': directives.flag,
|
'async': directives.flag,
|
||||||
'classmethod': directives.flag,
|
'classmethod': directives.flag,
|
||||||
'property': directives.flag,
|
'property': directives.flag,
|
||||||
@ -601,6 +602,8 @@ class PyMethod(PyObject):
|
|||||||
def get_signature_prefix(self, sig):
|
def get_signature_prefix(self, sig):
|
||||||
# type: (str) -> str
|
# type: (str) -> str
|
||||||
prefix = []
|
prefix = []
|
||||||
|
if 'abstractmethod' in self.options:
|
||||||
|
prefix.append('abstract')
|
||||||
if 'async' in self.options:
|
if 'async' in self.options:
|
||||||
prefix.append('async')
|
prefix.append('async')
|
||||||
if 'classmethod' in self.options:
|
if 'classmethod' in self.options:
|
||||||
|
@ -1336,6 +1336,8 @@ 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.isabstractmethod(obj):
|
||||||
|
self.add_line(' :abstractmethod:', sourcename)
|
||||||
if inspect.iscoroutinefunction(obj):
|
if inspect.iscoroutinefunction(obj):
|
||||||
self.add_line(' :async:', sourcename)
|
self.add_line(' :async:', sourcename)
|
||||||
if inspect.isclassmethod(obj):
|
if inspect.isclassmethod(obj):
|
||||||
@ -1453,7 +1455,10 @@ class PropertyDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter): #
|
|||||||
def add_directive_header(self, sig):
|
def add_directive_header(self, sig):
|
||||||
# type: (str) -> None
|
# type: (str) -> None
|
||||||
super().add_directive_header(sig)
|
super().add_directive_header(sig)
|
||||||
self.add_line(' :property:', self.get_sourcename())
|
sourcename = self.get_sourcename()
|
||||||
|
if inspect.isabstractmethod(self.object):
|
||||||
|
self.add_line(' :abstractmethod:', sourcename)
|
||||||
|
self.add_line(' :property:', sourcename)
|
||||||
|
|
||||||
|
|
||||||
class InstanceAttributeDocumenter(AttributeDocumenter):
|
class InstanceAttributeDocumenter(AttributeDocumenter):
|
||||||
|
@ -172,6 +172,12 @@ def isdescriptor(x):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def isabstractmethod(obj):
|
||||||
|
# type: (Any) -> bool
|
||||||
|
"""Check if the object is an abstractmethod."""
|
||||||
|
return safe_getattr(obj, '__isabstractmethod__', False) is True
|
||||||
|
|
||||||
|
|
||||||
def isattributedescriptor(obj):
|
def isattributedescriptor(obj):
|
||||||
# type: (Any) -> bool
|
# type: (Any) -> bool
|
||||||
"""Check if the object is an attribute like descriptor."""
|
"""Check if the object is an attribute like descriptor."""
|
||||||
@ -231,7 +237,7 @@ def isproperty(obj):
|
|||||||
|
|
||||||
|
|
||||||
def safe_getattr(obj, name, *defargs):
|
def safe_getattr(obj, name, *defargs):
|
||||||
# type: (Any, str, str) -> object
|
# type: (Any, str, Any) -> Any
|
||||||
"""A getattr() that turns all exceptions into AttributeErrors."""
|
"""A getattr() that turns all exceptions into AttributeErrors."""
|
||||||
try:
|
try:
|
||||||
return getattr(obj, name, *defargs)
|
return getattr(obj, name, *defargs)
|
||||||
@ -319,9 +325,9 @@ def is_builtin_class_method(obj, attr_name):
|
|||||||
classes = [c for c in inspect.getmro(obj) if attr_name in c.__dict__]
|
classes = [c for c in inspect.getmro(obj) if attr_name in c.__dict__]
|
||||||
cls = classes[0] if classes else object
|
cls = classes[0] if classes else object
|
||||||
|
|
||||||
if not hasattr(builtins, safe_getattr(cls, '__name__', '')): # type: ignore
|
if not hasattr(builtins, safe_getattr(cls, '__name__', '')):
|
||||||
return False
|
return False
|
||||||
return getattr(builtins, safe_getattr(cls, '__name__', '')) is cls # type: ignore
|
return getattr(builtins, safe_getattr(cls, '__name__', '')) is cls
|
||||||
|
|
||||||
|
|
||||||
class Parameter:
|
class Parameter:
|
||||||
|
29
tests/roots/test-ext-autodoc/target/abstractmethods.py
Normal file
29
tests/roots/test-ext-autodoc/target/abstractmethods.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
from abc import abstractmethod
|
||||||
|
|
||||||
|
|
||||||
|
class Base():
|
||||||
|
def meth(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def abstractmeth(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
@abstractmethod
|
||||||
|
def staticmeth():
|
||||||
|
pass
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
@abstractmethod
|
||||||
|
def classmeth(cls):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@property
|
||||||
|
@abstractmethod
|
||||||
|
def prop(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
async def coroutinemeth(self):
|
||||||
|
pass
|
@ -1485,6 +1485,55 @@ def test_mocked_module_imports(app, warning):
|
|||||||
assert warning.getvalue() == ''
|
assert warning.getvalue() == ''
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures('setup_test')
|
||||||
|
def test_abstractmethods():
|
||||||
|
options = {"members": None,
|
||||||
|
"undoc-members": None}
|
||||||
|
actual = do_autodoc(app, 'module', 'target.abstractmethods', options)
|
||||||
|
assert list(actual) == [
|
||||||
|
'',
|
||||||
|
'.. py:module:: target.abstractmethods',
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
'.. py:class:: Base',
|
||||||
|
' :module: target.abstractmethods',
|
||||||
|
'',
|
||||||
|
' ',
|
||||||
|
' .. py:method:: Base.abstractmeth()',
|
||||||
|
' :module: target.abstractmethods',
|
||||||
|
' :abstractmethod:',
|
||||||
|
' ',
|
||||||
|
' ',
|
||||||
|
' .. py:method:: Base.classmeth()',
|
||||||
|
' :module: target.abstractmethods',
|
||||||
|
' :abstractmethod:',
|
||||||
|
' :classmethod:',
|
||||||
|
' ',
|
||||||
|
' ',
|
||||||
|
' .. py:method:: Base.coroutinemeth()',
|
||||||
|
' :module: target.abstractmethods',
|
||||||
|
' :abstractmethod:',
|
||||||
|
' :async:',
|
||||||
|
' ',
|
||||||
|
' ',
|
||||||
|
' .. py:method:: Base.meth()',
|
||||||
|
' :module: target.abstractmethods',
|
||||||
|
' ',
|
||||||
|
' ',
|
||||||
|
' .. py:method:: Base.prop',
|
||||||
|
' :module: target.abstractmethods',
|
||||||
|
' :abstractmethod:',
|
||||||
|
' :property:',
|
||||||
|
' ',
|
||||||
|
' ',
|
||||||
|
' .. py:method:: Base.staticmeth()',
|
||||||
|
' :module: target.abstractmethods',
|
||||||
|
' :abstractmethod:',
|
||||||
|
' :staticmethod:',
|
||||||
|
' '
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures('setup_test')
|
@pytest.mark.usefixtures('setup_test')
|
||||||
def test_partialfunction():
|
def test_partialfunction():
|
||||||
options = {"members": None}
|
options = {"members": None}
|
||||||
|
@ -338,7 +338,9 @@ def test_pymethod_options(app):
|
|||||||
" .. py:method:: meth4\n"
|
" .. py:method:: meth4\n"
|
||||||
" :async:\n"
|
" :async:\n"
|
||||||
" .. py:method:: meth5\n"
|
" .. py:method:: meth5\n"
|
||||||
" :property:\n")
|
" :property:\n"
|
||||||
|
" .. py:method:: meth6\n"
|
||||||
|
" :abstractmethod:\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,
|
||||||
@ -353,6 +355,8 @@ def test_pymethod_options(app):
|
|||||||
addnodes.index,
|
addnodes.index,
|
||||||
desc,
|
desc,
|
||||||
addnodes.index,
|
addnodes.index,
|
||||||
|
desc,
|
||||||
|
addnodes.index,
|
||||||
desc)])]))
|
desc)])]))
|
||||||
|
|
||||||
# method
|
# method
|
||||||
@ -403,6 +407,16 @@ def test_pymethod_options(app):
|
|||||||
assert 'Class.meth5' in domain.objects
|
assert 'Class.meth5' in domain.objects
|
||||||
assert domain.objects['Class.meth5'] == ('index', 'method')
|
assert domain.objects['Class.meth5'] == ('index', 'method')
|
||||||
|
|
||||||
|
# :abstractmethod:
|
||||||
|
assert_node(doctree[1][1][10], addnodes.index,
|
||||||
|
entries=[('single', 'meth6() (Class method)', 'Class.meth6', '', None)])
|
||||||
|
assert_node(doctree[1][1][11], ([desc_signature, ([desc_annotation, "abstract "],
|
||||||
|
[desc_name, "meth6"],
|
||||||
|
[desc_parameterlist, ()])],
|
||||||
|
[desc_content, ()]))
|
||||||
|
assert 'Class.meth6' in domain.objects
|
||||||
|
assert domain.objects['Class.meth6'] == ('index', 'method')
|
||||||
|
|
||||||
|
|
||||||
def test_pyclassmethod(app):
|
def test_pyclassmethod(app):
|
||||||
text = (".. py:class:: Class\n"
|
text = (".. py:class:: Class\n"
|
||||||
|
Loading…
Reference in New Issue
Block a user