mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2026-09-03 20:52:55 -05:00
Close #7051: autodoc: Support instance variables without defaults (PEP-526)
This commit is contained in:
@@ -39,6 +39,7 @@ Features added
|
|||||||
* #2755: autodoc: Support type_comment style (ex. ``# type: (str) -> str``)
|
* #2755: autodoc: Support type_comment style (ex. ``# type: (str) -> str``)
|
||||||
annotation (python3.8+ or `typed_ast <https://github.com/python/typed_ast>`_
|
annotation (python3.8+ or `typed_ast <https://github.com/python/typed_ast>`_
|
||||||
is required)
|
is required)
|
||||||
|
* #7051: autodoc: Support instance variables without defaults (PEP-526)
|
||||||
* SphinxTranslator now calls visitor/departure method for super node class if
|
* SphinxTranslator now calls visitor/departure method for super node class if
|
||||||
visitor/departure method for original node class not found
|
visitor/departure method for original node class not found
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ from sphinx.deprecation import (
|
|||||||
RemovedInSphinx30Warning, RemovedInSphinx40Warning, deprecated_alias
|
RemovedInSphinx30Warning, RemovedInSphinx40Warning, deprecated_alias
|
||||||
)
|
)
|
||||||
from sphinx.environment import BuildEnvironment
|
from sphinx.environment import BuildEnvironment
|
||||||
from sphinx.ext.autodoc.importer import import_object, get_object_members
|
from sphinx.ext.autodoc.importer import import_object, get_module_members, get_object_members
|
||||||
from sphinx.ext.autodoc.mock import mock
|
from sphinx.ext.autodoc.mock import mock
|
||||||
from sphinx.locale import _, __
|
from sphinx.locale import _, __
|
||||||
from sphinx.pycode import ModuleAnalyzer, PycodeError
|
from sphinx.pycode import ModuleAnalyzer, PycodeError
|
||||||
@@ -32,9 +32,7 @@ from sphinx.util import inspect
|
|||||||
from sphinx.util import logging
|
from sphinx.util import logging
|
||||||
from sphinx.util import rpartition
|
from sphinx.util import rpartition
|
||||||
from sphinx.util.docstrings import prepare_docstring
|
from sphinx.util.docstrings import prepare_docstring
|
||||||
from sphinx.util.inspect import (
|
from sphinx.util.inspect import getdoc, object_description, safe_getattr, stringify_signature
|
||||||
getdoc, object_description, safe_getattr, safe_getmembers, stringify_signature
|
|
||||||
)
|
|
||||||
|
|
||||||
if False:
|
if False:
|
||||||
# For type annotation
|
# For type annotation
|
||||||
@@ -529,7 +527,10 @@ class Documenter:
|
|||||||
# process members and determine which to skip
|
# process members and determine which to skip
|
||||||
for (membername, member) in members:
|
for (membername, member) in members:
|
||||||
# if isattr is True, the member is documented as an attribute
|
# if isattr is True, the member is documented as an attribute
|
||||||
isattr = False
|
if member is INSTANCEATTR:
|
||||||
|
isattr = True
|
||||||
|
else:
|
||||||
|
isattr = False
|
||||||
|
|
||||||
doc = getdoc(member, self.get_attr, self.env.config.autodoc_inherit_docstrings)
|
doc = getdoc(member, self.get_attr, self.env.config.autodoc_inherit_docstrings)
|
||||||
|
|
||||||
@@ -793,7 +794,7 @@ class ModuleDocumenter(Documenter):
|
|||||||
hasattr(self.object, '__all__')):
|
hasattr(self.object, '__all__')):
|
||||||
# for implicit module members, check __module__ to avoid
|
# for implicit module members, check __module__ to avoid
|
||||||
# documenting imported objects
|
# documenting imported objects
|
||||||
return True, safe_getmembers(self.object)
|
return True, get_module_members(self.object)
|
||||||
else:
|
else:
|
||||||
memberlist = self.object.__all__
|
memberlist = self.object.__all__
|
||||||
# Sometimes __all__ is broken...
|
# Sometimes __all__ is broken...
|
||||||
@@ -806,7 +807,7 @@ class ModuleDocumenter(Documenter):
|
|||||||
type='autodoc'
|
type='autodoc'
|
||||||
)
|
)
|
||||||
# fall back to all members
|
# fall back to all members
|
||||||
return True, safe_getmembers(self.object)
|
return True, get_module_members(self.object)
|
||||||
else:
|
else:
|
||||||
memberlist = self.options.members or []
|
memberlist = self.options.members or []
|
||||||
ret = []
|
ret = []
|
||||||
@@ -1251,6 +1252,37 @@ class DataDocumenter(ModuleLevelDocumenter):
|
|||||||
or self.modname
|
or self.modname
|
||||||
|
|
||||||
|
|
||||||
|
class DataDeclarationDocumenter(DataDocumenter):
|
||||||
|
"""
|
||||||
|
Specialized Documenter subclass for data that cannot be imported
|
||||||
|
because they are declared without initial value (refs: PEP-526).
|
||||||
|
"""
|
||||||
|
objtype = 'datadecl'
|
||||||
|
directivetype = 'data'
|
||||||
|
member_order = 60
|
||||||
|
|
||||||
|
# must be higher than AttributeDocumenter
|
||||||
|
priority = 11
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any
|
||||||
|
) -> bool:
|
||||||
|
"""This documents only INSTANCEATTR members."""
|
||||||
|
return (isinstance(parent, ModuleDocumenter) and
|
||||||
|
isattr and
|
||||||
|
member is INSTANCEATTR)
|
||||||
|
|
||||||
|
def import_object(self) -> bool:
|
||||||
|
"""Never import anything."""
|
||||||
|
# disguise as a data
|
||||||
|
self.objtype = 'data'
|
||||||
|
return True
|
||||||
|
|
||||||
|
def add_content(self, more_content: Any, no_docstring: bool = False) -> None:
|
||||||
|
"""Never try to get a docstring from the object."""
|
||||||
|
super().add_content(more_content, no_docstring=True)
|
||||||
|
|
||||||
|
|
||||||
class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type: ignore
|
class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type: ignore
|
||||||
"""
|
"""
|
||||||
Specialized Documenter subclass for methods (normal, static and class).
|
Specialized Documenter subclass for methods (normal, static and class).
|
||||||
@@ -1438,7 +1470,9 @@ class InstanceAttributeDocumenter(AttributeDocumenter):
|
|||||||
def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any
|
def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""This documents only INSTANCEATTR members."""
|
"""This documents only INSTANCEATTR members."""
|
||||||
return isattr and (member is INSTANCEATTR)
|
return (not isinstance(parent, ModuleDocumenter) and
|
||||||
|
isattr and
|
||||||
|
member is INSTANCEATTR)
|
||||||
|
|
||||||
def import_object(self) -> bool:
|
def import_object(self) -> bool:
|
||||||
"""Never import anything."""
|
"""Never import anything."""
|
||||||
@@ -1550,6 +1584,7 @@ def setup(app: Sphinx) -> Dict[str, Any]:
|
|||||||
app.add_autodocumenter(ClassDocumenter)
|
app.add_autodocumenter(ClassDocumenter)
|
||||||
app.add_autodocumenter(ExceptionDocumenter)
|
app.add_autodocumenter(ExceptionDocumenter)
|
||||||
app.add_autodocumenter(DataDocumenter)
|
app.add_autodocumenter(DataDocumenter)
|
||||||
|
app.add_autodocumenter(DataDeclarationDocumenter)
|
||||||
app.add_autodocumenter(FunctionDocumenter)
|
app.add_autodocumenter(FunctionDocumenter)
|
||||||
app.add_autodocumenter(DecoratorDocumenter)
|
app.add_autodocumenter(DecoratorDocumenter)
|
||||||
app.add_autodocumenter(MethodDocumenter)
|
app.add_autodocumenter(MethodDocumenter)
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import importlib
|
|||||||
import traceback
|
import traceback
|
||||||
import warnings
|
import warnings
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
from typing import Any, Callable, Dict, List
|
from typing import Any, Callable, Dict, List, Tuple
|
||||||
|
|
||||||
from sphinx.deprecation import RemovedInSphinx40Warning, deprecated_alias
|
from sphinx.deprecation import RemovedInSphinx40Warning, deprecated_alias
|
||||||
from sphinx.util import logging
|
from sphinx.util import logging
|
||||||
@@ -101,12 +101,35 @@ def import_object(modname: str, objpath: List[str], objtype: str = '',
|
|||||||
raise ImportError(errmsg)
|
raise ImportError(errmsg)
|
||||||
|
|
||||||
|
|
||||||
|
def get_module_members(module: Any) -> List[Tuple[str, Any]]:
|
||||||
|
"""Get members of target module."""
|
||||||
|
from sphinx.ext.autodoc import INSTANCEATTR
|
||||||
|
|
||||||
|
members = {} # type: Dict[str, Tuple[str, Any]]
|
||||||
|
for name in dir(module):
|
||||||
|
try:
|
||||||
|
value = safe_getattr(module, name, None)
|
||||||
|
members[name] = (name, value)
|
||||||
|
except AttributeError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# annotation only member (ex. attr: int)
|
||||||
|
if hasattr(module, '__annotations__'):
|
||||||
|
for name in module.__annotations__:
|
||||||
|
if name not in members:
|
||||||
|
members[name] = (name, INSTANCEATTR)
|
||||||
|
|
||||||
|
return sorted(list(members.values()))
|
||||||
|
|
||||||
|
|
||||||
Attribute = namedtuple('Attribute', ['name', 'directly_defined', 'value'])
|
Attribute = namedtuple('Attribute', ['name', 'directly_defined', 'value'])
|
||||||
|
|
||||||
|
|
||||||
def get_object_members(subject: Any, objpath: List[str], attrgetter: Callable,
|
def get_object_members(subject: Any, objpath: List[str], attrgetter: Callable,
|
||||||
analyzer: Any = None) -> Dict[str, Attribute]:
|
analyzer: Any = None) -> Dict[str, Attribute]:
|
||||||
"""Get members and attributes of target object."""
|
"""Get members and attributes of target object."""
|
||||||
|
from sphinx.ext.autodoc import INSTANCEATTR
|
||||||
|
|
||||||
# the members directly defined in the class
|
# the members directly defined in the class
|
||||||
obj_dict = attrgetter(subject, '__dict__', {})
|
obj_dict = attrgetter(subject, '__dict__', {})
|
||||||
|
|
||||||
@@ -140,10 +163,14 @@ def get_object_members(subject: Any, objpath: List[str], attrgetter: Callable,
|
|||||||
except AttributeError:
|
except AttributeError:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# annotation only member (ex. attr: int)
|
||||||
|
if hasattr(subject, '__annotations__'):
|
||||||
|
for name in subject.__annotations__:
|
||||||
|
if name not in members:
|
||||||
|
members[name] = Attribute(name, True, INSTANCEATTR)
|
||||||
|
|
||||||
if analyzer:
|
if analyzer:
|
||||||
# append instance attributes (cf. self.attr1) if analyzer knows
|
# append instance attributes (cf. self.attr1) if analyzer knows
|
||||||
from sphinx.ext.autodoc import INSTANCEATTR
|
|
||||||
|
|
||||||
namespace = '.'.join(objpath)
|
namespace = '.'.join(objpath)
|
||||||
for (ns, name) in analyzer.find_attr_docs():
|
for (ns, name) in analyzer.find_attr_docs():
|
||||||
if namespace == ns and name not in members:
|
if namespace == ns and name not in members:
|
||||||
|
|||||||
@@ -71,13 +71,13 @@ def setup_documenters(app: Any) -> None:
|
|||||||
ModuleDocumenter, ClassDocumenter, ExceptionDocumenter, DataDocumenter,
|
ModuleDocumenter, ClassDocumenter, ExceptionDocumenter, DataDocumenter,
|
||||||
FunctionDocumenter, MethodDocumenter, AttributeDocumenter,
|
FunctionDocumenter, MethodDocumenter, AttributeDocumenter,
|
||||||
InstanceAttributeDocumenter, DecoratorDocumenter, PropertyDocumenter,
|
InstanceAttributeDocumenter, DecoratorDocumenter, PropertyDocumenter,
|
||||||
SlotsAttributeDocumenter,
|
SlotsAttributeDocumenter, DataDeclarationDocumenter,
|
||||||
)
|
)
|
||||||
documenters = [
|
documenters = [
|
||||||
ModuleDocumenter, ClassDocumenter, ExceptionDocumenter, DataDocumenter,
|
ModuleDocumenter, ClassDocumenter, ExceptionDocumenter, DataDocumenter,
|
||||||
FunctionDocumenter, MethodDocumenter, AttributeDocumenter,
|
FunctionDocumenter, MethodDocumenter, AttributeDocumenter,
|
||||||
InstanceAttributeDocumenter, DecoratorDocumenter, PropertyDocumenter,
|
InstanceAttributeDocumenter, DecoratorDocumenter, PropertyDocumenter,
|
||||||
SlotsAttributeDocumenter,
|
SlotsAttributeDocumenter, DataDeclarationDocumenter,
|
||||||
] # type: List[Type[Documenter]]
|
] # type: List[Type[Documenter]]
|
||||||
for documenter in documenters:
|
for documenter in documenters:
|
||||||
app.registry.add_documenter(documenter.objtype, documenter)
|
app.registry.add_documenter(documenter.objtype, documenter)
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
#: attr1
|
||||||
|
attr1: str = ''
|
||||||
|
#: attr2
|
||||||
|
attr2: str
|
||||||
|
|
||||||
|
|
||||||
|
class Class:
|
||||||
|
attr1: int = 0
|
||||||
|
attr2: int
|
||||||
@@ -1388,6 +1388,47 @@ def test_partialmethod_undoc_members(app):
|
|||||||
assert list(actual) == expected
|
assert list(actual) == expected
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(sys.version_info < (3, 6), reason='py36+ is available since python3.6.')
|
||||||
|
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||||
|
def test_autodoc_typed_instance_variables(app):
|
||||||
|
options = {"members": None,
|
||||||
|
"undoc-members": True}
|
||||||
|
actual = do_autodoc(app, 'module', 'target.typed_vars', options)
|
||||||
|
assert list(actual) == [
|
||||||
|
'',
|
||||||
|
'.. py:module:: target.typed_vars',
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
'.. py:class:: Class',
|
||||||
|
' :module: target.typed_vars',
|
||||||
|
'',
|
||||||
|
' ',
|
||||||
|
' .. py:attribute:: Class.attr1',
|
||||||
|
' :module: target.typed_vars',
|
||||||
|
' :annotation: = 0',
|
||||||
|
' ',
|
||||||
|
' ',
|
||||||
|
' .. py:attribute:: Class.attr2',
|
||||||
|
' :module: target.typed_vars',
|
||||||
|
' :annotation: = None',
|
||||||
|
' ',
|
||||||
|
'',
|
||||||
|
'.. py:data:: attr1',
|
||||||
|
' :module: target.typed_vars',
|
||||||
|
" :annotation: = ''",
|
||||||
|
'',
|
||||||
|
' attr1',
|
||||||
|
' ',
|
||||||
|
'',
|
||||||
|
'.. py:data:: attr2',
|
||||||
|
' :module: target.typed_vars',
|
||||||
|
" :annotation: = None",
|
||||||
|
'',
|
||||||
|
' attr2',
|
||||||
|
' '
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.sphinx('html', testroot='pycode-egg')
|
@pytest.mark.sphinx('html', testroot='pycode-egg')
|
||||||
def test_autodoc_for_egged_code(app):
|
def test_autodoc_for_egged_code(app):
|
||||||
options = {"members": None,
|
options = {"members": None,
|
||||||
|
|||||||
Reference in New Issue
Block a user