Refactor: Add import_object()

This commit is contained in:
Takeshi KOMIYA 2017-06-18 00:39:23 +09:00
parent d4728d0cb8
commit 1d64ade749
2 changed files with 59 additions and 51 deletions

View File

@ -14,9 +14,8 @@
import re import re
import sys import sys
import inspect import inspect
import traceback
from six import PY2, iterkeys, iteritems, itervalues, text_type, class_types, string_types from six import iterkeys, iteritems, itervalues, text_type, class_types, string_types
from docutils import nodes from docutils import nodes
from docutils.utils import assemble_option_dict from docutils.utils import assemble_option_dict
@ -24,7 +23,7 @@ from docutils.parsers.rst import Directive
from docutils.statemachine import ViewList from docutils.statemachine import ViewList
import sphinx import sphinx
from sphinx.ext.autodoc.importer import mock, import_module from sphinx.ext.autodoc.importer import mock, import_object
from sphinx.ext.autodoc.importer import _MockImporter # to keep compatibility # NOQA from sphinx.ext.autodoc.importer import _MockImporter # to keep compatibility # NOQA
from sphinx.ext.autodoc.inspector import format_annotation, formatargspec # to keep compatibility # NOQA from sphinx.ext.autodoc.inspector import format_annotation, formatargspec # to keep compatibility # NOQA
from sphinx.util import rpartition, force_decode from sphinx.util import rpartition, force_decode
@ -384,56 +383,15 @@ class Documenter(object):
Returns True if successful, False if an error occurred. Returns True if successful, False if an error occurred.
""" """
if self.objpath:
logger.debug('[autodoc] from %s import %s',
self.modname, '.'.join(self.objpath))
# always enable mock import hook
# it will do nothing if autodoc_mock_imports is empty
with mock(self.env.config.autodoc_mock_imports): with mock(self.env.config.autodoc_mock_imports):
try: try:
logger.debug('[autodoc] import %s', self.modname) ret = import_object(self.modname, self.objpath, self.objtype,
obj = import_module(self.modname, self.env.config.autodoc_warningiserror) attrgetter=self.get_attr,
parent = None warningiserror=self.env.config.autodoc_warningiserror)
self.module = obj self.module, self.parent, self.object_name, self.object = ret
logger.debug('[autodoc] => %r', obj)
for part in self.objpath:
parent = obj
logger.debug('[autodoc] getattr(_, %r)', part)
obj = self.get_attr(obj, part)
logger.debug('[autodoc] => %r', obj)
self.object_name = part
self.parent = parent
self.object = obj
return True return True
except (AttributeError, ImportError) as exc: except ImportError as exc:
if self.objpath: self.directive.warn(exc.args[0])
errmsg = 'autodoc: failed to import %s %r from module %r' % \
(self.objtype, '.'.join(self.objpath), self.modname)
else:
errmsg = 'autodoc: failed to import %s %r' % \
(self.objtype, self.fullname)
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)
self.directive.warn(errmsg)
self.env.note_reread() self.env.note_reread()
return False return False

View File

@ -15,11 +15,14 @@ import traceback
import contextlib import contextlib
from types import FunctionType, MethodType, ModuleType from types import FunctionType, MethodType, ModuleType
from six import PY2
from sphinx.util import logging from sphinx.util import logging
from sphinx.util.inspect import safe_getattr
if False: if False:
# For type annotation # For type annotation
from typing import Any, Generator, List, Set # NOQA from typing import Any, Callable, Generator, List, Set # NOQA
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -144,3 +147,50 @@ def import_module(modname, warningiserror=False):
# Importing modules may cause any side effects, including # Importing modules may cause any side effects, including
# SystemExit, so we need to catch all errors. # SystemExit, so we need to catch all errors.
raise ImportError(exc, 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]) -> 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)