diff --git a/CHANGES b/CHANGES index 9c6ca126bf..582f7a301e 100644 --- a/CHANGES +++ b/CHANGES @@ -15,6 +15,7 @@ Bugs fixed * #3093: gettext build broken on image node under ``note`` directive. * imgmath: crashes on showing error messages if image generation failed * #3117: LaTeX writer crashes if admonition is placed before first section title +* #3164: Change search order of ``sphinx.ext.inheritance_diagram`` Release 1.4.8 (released Oct 1, 2016) ==================================== diff --git a/sphinx/ext/inheritance_diagram.py b/sphinx/ext/inheritance_diagram.py index e29d849e99..eec56dc86a 100644 --- a/sphinx/ext/inheritance_diagram.py +++ b/sphinx/ext/inheritance_diagram.py @@ -58,51 +58,57 @@ from sphinx.util import force_decode from sphinx.util.compat import Directive -class_sig_re = re.compile(r'''^([\w.]*\.)? # module names - (\w+) \s* $ # class/final module name - ''', re.VERBOSE) +module_sig_re = re.compile(r'''^(?:([\w.]*)\.)? # module names + (\w+) \s* $ # class/final module name + ''', re.VERBOSE) + + +def try_import(objname): + """Import a object or module using *name* and *currentmodule*. + *name* should be a relative name from *currentmodule* or + a fully-qualified name. + + Returns imported object or module. If failed, returns None value. + """ + try: + __import__(objname) + return sys.modules.get(objname) + except ImportError: + modname, attrname = module_sig_re.match(objname).groups() + if modname is None: + return None + try: + __import__(modname) + return getattr(sys.modules.get(modname), attrname, None) + except ImportError: + return None def import_classes(name, currmodule): """Import a class using its fully-qualified *name*.""" - try: - path, base = class_sig_re.match(name).groups() - except (AttributeError, ValueError): - raise InheritanceException('Invalid class or module %r specified ' - 'for inheritance diagram' % name) + target = None - fullname = (path or '') + base - path = (path and path.rstrip('.') or '') + # import class or module using currmodule + if currmodule: + target = try_import(currmodule + '.' + name) - # two possibilities: either it is a module, then import it - try: - __import__(fullname) - todoc = sys.modules[fullname] - except ImportError: - # else it is a class, then import the module - if not path: - if currmodule: - # try the current module - path = currmodule - else: - raise InheritanceException( - 'Could not import class %r specified for ' - 'inheritance diagram' % base) - try: - __import__(path) - todoc = getattr(sys.modules[path], base) - except (ImportError, AttributeError): - raise InheritanceException( - 'Could not import class or module %r specified for ' - 'inheritance diagram' % (path + '.' + base)) + # import class or module without currmodule + if target is None: + target = try_import(name) - # If a class, just return it - if inspect.isclass(todoc): - return [todoc] - elif inspect.ismodule(todoc): + if target is None: + raise InheritanceException( + 'Could not import class or module %r specified for ' + 'inheritance diagram' % name) + + if inspect.isclass(target): + # If imported object is a class, just return it + return [target] + elif inspect.ismodule(target): + # If imported object is a module, return classes defined on it classes = [] - for cls in todoc.__dict__.values(): - if inspect.isclass(cls) and cls.__module__ == todoc.__name__: + for cls in target.__dict__.values(): + if inspect.isclass(cls) and cls.__module__ == target.__name__: classes.append(cls) return classes raise InheritanceException('%r specified for inheritance diagram is ' diff --git a/tests/test_ext_inheritance_diagram.py b/tests/test_ext_inheritance_diagram.py index e641cc0de2..ba912254a8 100644 --- a/tests/test_ext_inheritance_diagram.py +++ b/tests/test_ext_inheritance_diagram.py @@ -21,9 +21,11 @@ def test_inheritance_diagram_html(app, status, warning): def test_import_classes(): from sphinx.application import Sphinx, TemplateBridge + from sphinx.util.i18n import CatalogInfo try: sys.path.append(rootdir / 'roots/test-ext-inheritance_diagram') + from example.sphinx import DummyClass # got exception for unknown class or module raises(InheritanceException, import_classes, 'unknown', None) @@ -48,15 +50,15 @@ def test_import_classes(): classes = import_classes('Sphinx', 'sphinx.application') assert classes == [Sphinx] - # ignore current module if name include the module name - raises(InheritanceException, import_classes, 'i18n.CatalogInfo', 'sphinx.util') + # relative module name to current module + classes = import_classes('i18n.CatalogInfo', 'sphinx.util') + assert classes == [CatalogInfo] # got exception for functions raises(InheritanceException, import_classes, 'encode_uri', 'sphinx.util') - # try to load example.sphinx, but inheritance_diagram imports sphinx instead - # refs: #3164 + # import submodule on current module (refs: #3164) classes = import_classes('sphinx', 'example') - assert classes == [] + assert classes == [DummyClass] finally: sys.path.pop()