summaryrefslogtreecommitdiff
path: root/sphinx/ext/autodoc/importer.py
diff options
context:
space:
mode:
Diffstat (limited to 'sphinx/ext/autodoc/importer.py')
-rw-r--r--sphinx/ext/autodoc/importer.py193
1 files changed, 170 insertions, 23 deletions
diff --git a/sphinx/ext/autodoc/importer.py b/sphinx/ext/autodoc/importer.py
index cdccf710d..ffcb27ecc 100644
--- a/sphinx/ext/autodoc/importer.py
+++ b/sphinx/ext/autodoc/importer.py
@@ -4,23 +4,61 @@
Importer utilities for autodoc
- :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS.
+ :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
import importlib
import traceback
import warnings
-from typing import Any, Callable, Dict, List, Mapping, NamedTuple, Tuple
+from typing import Any, Callable, Dict, List, Mapping, NamedTuple, Optional, Tuple
-from sphinx.deprecation import RemovedInSphinx40Warning, deprecated_alias
-from sphinx.pycode import ModuleAnalyzer
+from sphinx.deprecation import (RemovedInSphinx40Warning, RemovedInSphinx50Warning,
+ deprecated_alias)
+from sphinx.pycode import ModuleAnalyzer, PycodeError
from sphinx.util import logging
-from sphinx.util.inspect import isclass, isenumclass, safe_getattr
+from sphinx.util.inspect import (getannotations, getmro, getslots, isclass, isenumclass,
+ safe_getattr)
+
+if False:
+ # For type annotation
+ from typing import Type # NOQA
+
+ from sphinx.ext.autodoc import ObjectMember
logger = logging.getLogger(__name__)
+def mangle(subject: Any, name: str) -> str:
+ """mangle the given name."""
+ try:
+ if isclass(subject) and name.startswith('__') and not name.endswith('__'):
+ return "_%s%s" % (subject.__name__, name)
+ except AttributeError:
+ pass
+
+ return name
+
+
+def unmangle(subject: Any, name: str) -> Optional[str]:
+ """unmangle the given name."""
+ try:
+ if isclass(subject) and not name.endswith('__'):
+ prefix = "_%s__" % subject.__name__
+ if name.startswith(prefix):
+ return name.replace(prefix, "__", 1)
+ else:
+ for cls in subject.__mro__:
+ prefix = "_%s__" % cls.__name__
+ if name.startswith(prefix):
+ # mangled attribute defined in parent class
+ return None
+ except AttributeError:
+ pass
+
+ return name
+
+
def import_module(modname: str, warningiserror: bool = False) -> Any:
"""
Call importlib.import_module(modname), convert exceptions to ImportError
@@ -33,7 +71,7 @@ def import_module(modname: str, warningiserror: bool = False) -> Any:
except BaseException as exc:
# Importing modules may cause any side effects, including
# SystemExit, so we need to catch all errors.
- raise ImportError(exc, traceback.format_exc())
+ raise ImportError(exc, traceback.format_exc()) from exc
def import_object(modname: str, objpath: List[str], objtype: str = '',
@@ -68,7 +106,8 @@ def import_object(modname: str, objpath: List[str], objtype: str = '',
for attrname in objpath:
parent = obj
logger.debug('[autodoc] getattr(_, %r)', attrname)
- obj = attrgetter(obj, attrname)
+ mangled_name = mangle(obj, attrname)
+ obj = attrgetter(obj, mangled_name)
logger.debug('[autodoc] => %r', obj)
object_name = attrname
return [module, parent, object_name, obj]
@@ -98,13 +137,16 @@ def import_object(modname: str, objpath: List[str], objtype: str = '',
errmsg += '; the following exception was raised:\n%s' % traceback.format_exc()
logger.debug(errmsg)
- raise ImportError(errmsg)
+ raise ImportError(errmsg) from exc
def get_module_members(module: Any) -> List[Tuple[str, Any]]:
"""Get members of target module."""
from sphinx.ext.autodoc import INSTANCEATTR
+ warnings.warn('sphinx.ext.autodoc.importer.get_module_members() is deprecated.',
+ RemovedInSphinx50Warning)
+
members = {} # type: Dict[str, Tuple[str, Any]]
for name in dir(module):
try:
@@ -114,10 +156,12 @@ def get_module_members(module: Any) -> List[Tuple[str, Any]]:
continue
# annotation only member (ex. attr: int)
- if hasattr(module, '__annotations__'):
- for name in module.__annotations__:
+ try:
+ for name in getannotations(module):
if name not in members:
members[name] = (name, INSTANCEATTR)
+ except AttributeError:
+ pass
return sorted(list(members.values()))
@@ -127,6 +171,18 @@ Attribute = NamedTuple('Attribute', [('name', str),
('value', Any)])
+def _getmro(obj: Any) -> Tuple["Type", ...]:
+ warnings.warn('sphinx.ext.autodoc.importer._getmro() is deprecated.',
+ RemovedInSphinx40Warning)
+ return getmro(obj)
+
+
+def _getannotations(obj: Any) -> Mapping[str, Any]:
+ warnings.warn('sphinx.ext.autodoc.importer._getannotations() is deprecated.',
+ RemovedInSphinx40Warning)
+ return getannotations(obj)
+
+
def get_object_members(subject: Any, objpath: List[str], attrgetter: Callable,
analyzer: ModuleAnalyzer = None) -> Dict[str, Attribute]:
"""Get members and attributes of target object."""
@@ -150,27 +206,36 @@ def get_object_members(subject: Any, objpath: List[str], attrgetter: Callable,
members[name] = Attribute(name, True, value)
# members in __slots__
- if isclass(subject) and getattr(subject, '__slots__', None) is not None:
- from sphinx.ext.autodoc import SLOTSATTR
+ try:
+ __slots__ = getslots(subject)
+ if __slots__:
+ from sphinx.ext.autodoc import SLOTSATTR
- for name in subject.__slots__:
- members[name] = Attribute(name, True, SLOTSATTR)
+ for name in __slots__:
+ members[name] = Attribute(name, True, SLOTSATTR)
+ except (AttributeError, TypeError, ValueError):
+ pass
# other members
for name in dir(subject):
try:
value = attrgetter(subject, name)
directly_defined = name in obj_dict
- if name not in members:
+ name = unmangle(subject, name)
+ if name and name not in members:
members[name] = Attribute(name, directly_defined, value)
except AttributeError:
continue
# annotation only member (ex. attr: int)
- if hasattr(subject, '__annotations__') and isinstance(subject.__annotations__, Mapping):
- for name in subject.__annotations__:
- if name not in members:
- members[name] = Attribute(name, True, INSTANCEATTR)
+ for i, cls in enumerate(getmro(subject)):
+ try:
+ for name in getannotations(cls):
+ name = unmangle(cls, name)
+ if name and name not in members:
+ members[name] = Attribute(name, i == 0, INSTANCEATTR)
+ except AttributeError:
+ pass
if analyzer:
# append instance attributes (cf. self.attr1) if analyzer knows
@@ -182,9 +247,84 @@ def get_object_members(subject: Any, objpath: List[str], attrgetter: Callable,
return members
-from sphinx.ext.autodoc.mock import ( # NOQA
- _MockModule, _MockObject, MockFinder, MockLoader, mock
-)
+def get_class_members(subject: Any, objpath: List[str], attrgetter: Callable
+ ) -> Dict[str, "ObjectMember"]:
+ """Get members and attributes of target class."""
+ from sphinx.ext.autodoc import INSTANCEATTR, ObjectMember
+
+ # the members directly defined in the class
+ obj_dict = attrgetter(subject, '__dict__', {})
+
+ members = {} # type: Dict[str, ObjectMember]
+
+ # enum members
+ if isenumclass(subject):
+ for name, value in subject.__members__.items():
+ if name not in members:
+ members[name] = ObjectMember(name, value, class_=subject)
+
+ superclass = subject.__mro__[1]
+ for name in obj_dict:
+ if name not in superclass.__dict__:
+ value = safe_getattr(subject, name)
+ members[name] = ObjectMember(name, value, class_=subject)
+
+ # members in __slots__
+ try:
+ __slots__ = getslots(subject)
+ if __slots__:
+ from sphinx.ext.autodoc import SLOTSATTR
+
+ for name, docstring in __slots__.items():
+ members[name] = ObjectMember(name, SLOTSATTR, class_=subject,
+ docstring=docstring)
+ except (AttributeError, TypeError, ValueError):
+ pass
+
+ # other members
+ for name in dir(subject):
+ try:
+ value = attrgetter(subject, name)
+ unmangled = unmangle(subject, name)
+ if unmangled and unmangled not in members:
+ if name in obj_dict:
+ members[unmangled] = ObjectMember(unmangled, value, class_=subject)
+ else:
+ members[unmangled] = ObjectMember(unmangled, value)
+ except AttributeError:
+ continue
+
+ try:
+ for cls in getmro(subject):
+ # annotation only member (ex. attr: int)
+ try:
+ for name in getannotations(cls):
+ name = unmangle(cls, name)
+ if name and name not in members:
+ members[name] = ObjectMember(name, INSTANCEATTR, class_=cls)
+ except AttributeError:
+ pass
+
+ # append instance attributes (cf. self.attr1) if analyzer knows
+ try:
+ modname = safe_getattr(cls, '__module__')
+ qualname = safe_getattr(cls, '__qualname__')
+ analyzer = ModuleAnalyzer.for_module(modname)
+ analyzer.analyze()
+ for (ns, name), docstring in analyzer.attr_docs.items():
+ if ns == qualname and name not in members:
+ members[name] = ObjectMember(name, INSTANCEATTR, class_=cls,
+ docstring='\n'.join(docstring))
+ except (AttributeError, PycodeError):
+ pass
+ except AttributeError:
+ pass
+
+ return members
+
+
+from sphinx.ext.autodoc.mock import (MockFinder, MockLoader, _MockModule, _MockObject, # NOQA
+ mock)
deprecated_alias('sphinx.ext.autodoc.importer',
{
@@ -194,4 +334,11 @@ deprecated_alias('sphinx.ext.autodoc.importer',
'MockLoader': MockLoader,
'mock': mock,
},
- RemovedInSphinx40Warning)
+ RemovedInSphinx40Warning,
+ {
+ '_MockModule': 'sphinx.ext.autodoc.mock._MockModule',
+ '_MockObject': 'sphinx.ext.autodoc.mock._MockObject',
+ 'MockFinder': 'sphinx.ext.autodoc.mock.MockFinder',
+ 'MockLoader': 'sphinx.ext.autodoc.mock.MockLoader',
+ 'mock': 'sphinx.ext.autodoc.mock.mock',
+ })