From b708f7b82f4074274f07a5b5f7886cc618a3e15a Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Tue, 16 Apr 2019 22:00:41 +0900 Subject: [PATCH] autodoc: Add PropertyDocumenter to detect properties --- sphinx/ext/autodoc/__init__.py | 32 ++++++++++++++++++++++++++++++ sphinx/ext/autosummary/generate.py | 13 ++++++++---- tests/test_autodoc.py | 25 +++++++++++++---------- 3 files changed, 56 insertions(+), 14 deletions(-) diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index 2a4df2159..9f9dd2fde 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -1415,6 +1415,37 @@ class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter): super().add_content(more_content, no_docstring) +class PropertyDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter): # type: ignore + """ + Specialized Documenter subclass for properties. + """ + objtype = 'property' + directivetype = 'method' + member_order = 60 + + # before AttributeDocumenter + priority = AttributeDocumenter.priority + 1 + + @classmethod + def can_document_member(cls, member, membername, isattr, parent): + # type: (Any, str, bool, Any) -> bool + return inspect.isproperty(member) and isinstance(parent, ClassDocumenter) + + def document_members(self, all_members=False): + # type: (bool) -> None + pass + + def get_real_modname(self): + # type: () -> str + return self.get_attr(self.parent or self.object, '__module__', None) \ + or self.modname + + def add_directive_header(self, sig): + # type: (str) -> None + super().add_directive_header(sig) + self.add_line(' :property:', self.get_sourcename()) + + class InstanceAttributeDocumenter(AttributeDocumenter): """ Specialized Documenter subclass for attributes that cannot be imported @@ -1506,6 +1537,7 @@ def setup(app): app.add_autodocumenter(DecoratorDocumenter) app.add_autodocumenter(MethodDocumenter) app.add_autodocumenter(AttributeDocumenter) + app.add_autodocumenter(PropertyDocumenter) app.add_autodocumenter(InstanceAttributeDocumenter) app.add_config_value('autoclass_content', 'class', True) diff --git a/sphinx/ext/autosummary/generate.py b/sphinx/ext/autosummary/generate.py index 1e9dbedc8..c8972b499 100644 --- a/sphinx/ext/autosummary/generate.py +++ b/sphinx/ext/autosummary/generate.py @@ -40,7 +40,7 @@ from sphinx.util.rst import escape as rst_escape if False: # For type annotation - from typing import Any, Callable, Dict, List, Tuple, Type, Union # NOQA + from typing import Any, Callable, Dict, List, Set, Tuple, Type, Union # NOQA from sphinx.builders import Builder # NOQA from sphinx.ext.autodoc import Documenter # NOQA @@ -170,7 +170,12 @@ def generate_autosummary_docs(sources, output_dir=None, suffix='.rst', template = template_env.get_template('autosummary/base.rst') def get_members(obj, typ, include_public=[], imported=True): - # type: (Any, str, List[str], bool) -> Tuple[List[str], List[str]] + # type: (Any, Union[str, Set[str]], List[str], bool) -> Tuple[List[str], List[str]] # NOQA + if isinstance(typ, str): + types = {typ} + else: + types = typ + items = [] # type: List[str] for name in dir(obj): try: @@ -178,7 +183,7 @@ def generate_autosummary_docs(sources, output_dir=None, suffix='.rst', except AttributeError: continue documenter = get_documenter(app, value, obj) - if documenter.objtype == typ: + if documenter.objtype in types: if imported or getattr(value, '__module__', None) == obj.__name__: # skip imported members if expected items.append(name) @@ -203,7 +208,7 @@ def generate_autosummary_docs(sources, output_dir=None, suffix='.rst', ns['methods'], ns['all_methods'] = \ get_members(obj, 'method', ['__init__']) ns['attributes'], ns['all_attributes'] = \ - get_members(obj, 'attribute') + get_members(obj, {'attribute', 'property'}) parts = name.split('.') if doc.objtype in ('method', 'attribute'): diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py index 5f616b791..563ab8fcf 100644 --- a/tests/test_autodoc.py +++ b/tests/test_autodoc.py @@ -756,7 +756,7 @@ def test_autodoc_undoc_members(app): ' .. py:attribute:: Class.mdocattr', ' .. py:method:: Class.meth()', ' .. py:method:: Class.moore(a, e, f) -> happiness', - ' .. py:attribute:: Class.prop', + ' .. py:method:: Class.prop', ' .. py:method:: Class.roger(a, *, b=2, c=3, d=4, e=5, f=6)', ' .. py:attribute:: Class.skipattr', ' .. py:method:: Class.skipmeth()', @@ -777,6 +777,7 @@ def test_autodoc_inherited_members(app): ' .. py:method:: Class.inheritedstaticmeth(cls)', ' .. py:method:: Class.meth()', ' .. py:method:: Class.moore(a, e, f) -> happiness', + ' .. py:method:: Class.prop', ' .. py:method:: Class.skipmeth()' ] @@ -836,7 +837,7 @@ def test_autodoc_special_members(app): ' .. py:attribute:: Class.mdocattr', ' .. py:method:: Class.meth()', ' .. py:method:: Class.moore(a, e, f) -> happiness', - ' .. py:attribute:: Class.prop', + ' .. py:method:: Class.prop', ' .. py:method:: Class.roger(a, *, b=2, c=3, d=4, e=5, f=6)', ' .. py:attribute:: Class.skipattr', ' .. py:method:: Class.skipmeth()', @@ -1028,7 +1029,7 @@ def test_autodoc_member_order(app): ' .. py:method:: Class.excludemeth()', ' .. py:attribute:: Class.skipattr', ' .. py:attribute:: Class.attr', - ' .. py:attribute:: Class.prop', + ' .. py:method:: Class.prop', ' .. py:attribute:: Class.docattr', ' .. py:attribute:: Class.udocattr', ' .. py:attribute:: Class.mdocattr', @@ -1062,7 +1063,7 @@ def test_autodoc_member_order(app): ' .. py:attribute:: Class.inst_attr_inline', ' .. py:attribute:: Class.inst_attr_string', ' .. py:attribute:: Class.mdocattr', - ' .. py:attribute:: Class.prop', + ' .. py:method:: Class.prop', ' .. py:attribute:: Class.skipattr', ' .. py:attribute:: Class.udocattr' ] @@ -1085,7 +1086,7 @@ def test_autodoc_member_order(app): ' .. py:attribute:: Class.mdocattr', ' .. py:method:: Class.meth()', ' .. py:method:: Class.moore(a, e, f) -> happiness', - ' .. py:attribute:: Class.prop', + ' .. py:method:: Class.prop', ' .. py:method:: Class.roger(a, *, b=2, c=3, d=4, e=5, f=6)', ' .. py:attribute:: Class.skipattr', ' .. py:method:: Class.skipmeth()', @@ -1152,14 +1153,16 @@ def test_autodoc_docstring_signature(app): ' indented line', ' ', ' ', - ' .. py:attribute:: DocstringSig.prop1', + ' .. py:method:: DocstringSig.prop1', ' :module: target', + ' :property:', ' ', ' First line of docstring', ' ', ' ', - ' .. py:attribute:: DocstringSig.prop2', + ' .. py:method:: DocstringSig.prop2', ' :module: target', + ' :property:', ' ', ' First line of docstring', ' Second line of docstring', @@ -1194,15 +1197,17 @@ def test_autodoc_docstring_signature(app): ' indented line', ' ', ' ', - ' .. py:attribute:: DocstringSig.prop1', + ' .. py:method:: DocstringSig.prop1', ' :module: target', + ' :property:', ' ', ' DocstringSig.prop1(self)', ' First line of docstring', ' ', ' ', - ' .. py:attribute:: DocstringSig.prop2', + ' .. py:method:: DocstringSig.prop2', ' :module: target', + ' :property:', ' ', ' First line of docstring', ' Second line of docstring', @@ -1717,7 +1722,7 @@ def test_autodoc_default_options_with_values(app): ' .. py:method:: Class.skipmeth()', ' .. py:method:: Class.excludemeth()', ' .. py:attribute:: Class.attr', - ' .. py:attribute:: Class.prop', + ' .. py:method:: Class.prop', ' .. py:attribute:: Class.docattr', ' .. py:attribute:: Class.udocattr', ' .. py:attribute:: Class.mdocattr',