""" sphinx.ext.autodoc.importer ~~~~~~~~~~~~~~~~~~~~~~~~~~~ Importer utilities for autodoc :copyright: Copyright 2007-2020 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, Optional, Tuple from sphinx.deprecation import RemovedInSphinx40Warning, deprecated_alias from sphinx.pycode import ModuleAnalyzer from sphinx.util import logging from sphinx.util.inspect import isclass, isenumclass, safe_getattr if False: # For type annotation from typing import Type # NOQA 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 """ try: with warnings.catch_warnings(): warnings.filterwarnings("ignore", category=ImportWarning) with logging.skip_warningiserror(not warningiserror): return importlib.import_module(modname) 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()) from exc def import_object(modname: str, objpath: List[str], objtype: str = '', attrgetter: Callable[[Any, str], Any] = safe_getattr, warningiserror: bool = False) -> Any: if objpath: logger.debug('[autodoc] from %s import %s', modname, '.'.join(objpath)) else: logger.debug('[autodoc] import %s', modname) try: module = None exc_on_importing = None objpath = list(objpath) while module is None: try: module = import_module(modname, warningiserror=warningiserror) logger.debug('[autodoc] import %s => %r', modname, module) except ImportError as exc: logger.debug('[autodoc] import %s => failed', modname) exc_on_importing = exc if '.' in modname: # retry with parent module modname, name = modname.rsplit('.', 1) objpath.insert(0, name) else: raise obj = module parent = None object_name = None for attrname in objpath: parent = obj logger.debug('[autodoc] getattr(_, %r)', 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] except (AttributeError, ImportError) as exc: if isinstance(exc, AttributeError) and exc_on_importing: # restore ImportError exc = exc_on_importing if objpath: errmsg = ('autodoc: failed to import %s %r from module %r' % (objtype, '.'.join(objpath), modname)) else: errmsg = 'autodoc: failed to import %s %r' % (objtype, modname) if isinstance(exc, ImportError): # import_module() raises ImportError having real exception obj and # traceback real_exc, traceback_msg = exc.args if isinstance(real_exc, SystemExit): errmsg += ('; the module executes module level statement ' 'and it might call sys.exit().') elif isinstance(real_exc, ImportError) and real_exc.args: errmsg += '; the following exception was raised:\n%s' % real_exc.args[0] else: errmsg += '; the following exception was raised:\n%s' % traceback_msg else: errmsg += '; the following exception was raised:\n%s' % traceback.format_exc() logger.debug(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 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', str), ('directly_defined', bool), ('value', Any)]) def _getmro(obj: Any) -> Tuple["Type", ...]: """Get __mro__ from given *obj* safely.""" __mro__ = safe_getattr(obj, '__mro__', None) if isinstance(__mro__, tuple): return __mro__ else: return tuple() def _getannotations(obj: Any) -> Mapping[str, Any]: """Get __annotations__ from given *obj* safely.""" __annotations__ = safe_getattr(obj, '__annotations__', None) if isinstance(__annotations__, Mapping): return __annotations__ else: return {} def get_object_members(subject: Any, objpath: List[str], attrgetter: Callable, analyzer: ModuleAnalyzer = None) -> Dict[str, Attribute]: """Get members and attributes of target object.""" from sphinx.ext.autodoc import INSTANCEATTR # the members directly defined in the class obj_dict = attrgetter(subject, '__dict__', {}) members = {} # type: Dict[str, Attribute] # enum members if isenumclass(subject): for name, value in subject.__members__.items(): if name not in members: members[name] = Attribute(name, True, value) superclass = subject.__mro__[1] for name in obj_dict: if name not in superclass.__dict__: value = safe_getattr(subject, name) 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 slots = subject.__slots__ if isinstance(slots, str): slots = [slots] for name in slots: members[name] = Attribute(name, True, SLOTSATTR) # other members for name in dir(subject): try: value = attrgetter(subject, name) directly_defined = name in obj_dict 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) for i, cls in enumerate(_getmro(subject)): for name in _getannotations(cls): name = unmangle(cls, name) if name and name not in members: members[name] = Attribute(name, i == 0, INSTANCEATTR) if analyzer: # append instance attributes (cf. self.attr1) if analyzer knows namespace = '.'.join(objpath) for (ns, name) in analyzer.find_attr_docs(): if namespace == ns and name not in members: members[name] = Attribute(name, True, INSTANCEATTR) return members from sphinx.ext.autodoc.mock import ( # NOQA _MockModule, _MockObject, MockFinder, MockLoader, mock ) deprecated_alias('sphinx.ext.autodoc.importer', { '_MockModule': _MockModule, '_MockObject': _MockObject, 'MockFinder': MockFinder, 'MockLoader': MockLoader, 'mock': mock, }, 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', })