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.py109
1 files changed, 103 insertions, 6 deletions
diff --git a/sphinx/ext/autodoc/importer.py b/sphinx/ext/autodoc/importer.py
index e024d38f3..101cb930f 100644
--- a/sphinx/ext/autodoc/importer.py
+++ b/sphinx/ext/autodoc/importer.py
@@ -5,20 +5,25 @@
Importer utilities for autodoc
- :copyright: Copyright 2007-2017 by the Sphinx team, see AUTHORS.
+ :copyright: Copyright 2007-2018 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
import sys
-import traceback
import warnings
+import traceback
+import contextlib
+from collections import namedtuple
from types import FunctionType, MethodType, ModuleType
+from six import PY2
+
from sphinx.util import logging
+from sphinx.util.inspect import isenumclass, safe_getattr
if False:
# For type annotation
- from typing import Any, List, Set # NOQA
+ from typing import Any, Callable, Dict, Generator, List, Optional, Set # NOQA
logger = logging.getLogger(__name__)
@@ -77,7 +82,6 @@ class _MockModule(ModuleType):
class _MockImporter(object):
-
def __init__(self, names):
# type: (List[str]) -> None
self.base_packages = set() # type: Set[str]
@@ -120,6 +124,16 @@ class _MockImporter(object):
return module
+@contextlib.contextmanager
+def mock(names):
+ # type: (List[str]) -> Generator
+ try:
+ importer = _MockImporter(names)
+ yield
+ finally:
+ importer.disable()
+
+
def import_module(modname, warningiserror=False):
"""
Call __import__(modname), convert exceptions to ImportError
@@ -130,7 +144,90 @@ def import_module(modname, warningiserror=False):
with logging.skip_warningiserror(not warningiserror):
__import__(modname)
return sys.modules[modname]
- except BaseException:
+ except BaseException as exc:
# Importing modules may cause any side effects, including
# SystemExit, so we need to catch all errors.
- raise ImportError(traceback.format_exc())
+ raise ImportError(exc, traceback.format_exc())
+
+
+def import_object(modname, objpath, objtype='', attrgetter=safe_getattr, warningiserror=False):
+ # type: (str, List[unicode], str, Callable[[Any, unicode], Any], bool) -> Any
+ if objpath:
+ logger.debug('[autodoc] from %s import %s', modname, '.'.join(objpath))
+ else:
+ logger.debug('[autodoc] import %s', modname)
+
+ try:
+ module = import_module(modname, warningiserror=warningiserror)
+ logger.debug('[autodoc] => %r', module)
+ 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 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):
+ 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()
+
+ if PY2:
+ errmsg = errmsg.decode('utf-8') # type: ignore
+ logger.debug(errmsg)
+ raise ImportError(errmsg)
+
+
+Attribute = namedtuple('Attribute', ['name', 'directly_defined', 'value'])
+
+
+def get_object_members(subject, objpath, attrgetter, analyzer=None):
+ # type: (Any, List[unicode], Callable, Any) -> Dict[str, Attribute] # NOQA
+ """Get members and attributes of target object."""
+ # the members directly defined in the class
+ obj_dict = attrgetter(subject, '__dict__', {})
+
+ # Py34 doesn't have enum members in __dict__.
+ if sys.version_info[:2] == (3, 4) and isenumclass(subject):
+ obj_dict = dict(obj_dict)
+ for name, value in subject.__members__.items():
+ obj_dict[name] = value
+
+ members = {}
+ for name in dir(subject):
+ try:
+ value = attrgetter(subject, name)
+ directly_defined = name in obj_dict
+ members[name] = Attribute(name, directly_defined, value)
+ except AttributeError:
+ continue
+
+ if analyzer:
+ # append instance attributes (cf. self.attr1) if analyzer knows
+ from sphinx.ext.autodoc import INSTANCEATTR
+
+ 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