""" 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, Tuple from sphinx.util import logging from sphinx.util.inspect import isclass, isenumclass, safe_getattr logger = logging.getLogger(__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()) 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) obj = attrgetter(obj, attrname) 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) 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())) class Attribute(NamedTuple): name: str directly_defined: bool value: Any def get_object_members(subject: Any, objpath: List[str], attrgetter: Callable, analyzer: Any = 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, value in obj_dict.items(): if name not in superclass.__dict__: 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 for name in subject.__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 if 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) 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