mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2026-09-03 20:52:55 -05:00
Merge pull request #3171 from tk0miya/3164_fix_inheritance_diagram
Change search order of ``sphinx.ext.inheritance_diagram``
This commit is contained in:
@@ -15,6 +15,7 @@ Bugs fixed
|
|||||||
* #3093: gettext build broken on image node under ``note`` directive.
|
* #3093: gettext build broken on image node under ``note`` directive.
|
||||||
* imgmath: crashes on showing error messages if image generation failed
|
* imgmath: crashes on showing error messages if image generation failed
|
||||||
* #3117: LaTeX writer crashes if admonition is placed before first section title
|
* #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)
|
Release 1.4.8 (released Oct 1, 2016)
|
||||||
====================================
|
====================================
|
||||||
|
|||||||
@@ -58,9 +58,61 @@ from sphinx.util import force_decode
|
|||||||
from sphinx.util.compat import Directive
|
from sphinx.util.compat import Directive
|
||||||
|
|
||||||
|
|
||||||
class_sig_re = re.compile(r'''^([\w.]*\.)? # module names
|
module_sig_re = re.compile(r'''^(?:([\w.]*)\.)? # module names
|
||||||
(\w+) \s* $ # class/final module name
|
(\w+) \s* $ # class/final module name
|
||||||
''', re.VERBOSE)
|
''', 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*."""
|
||||||
|
target = None
|
||||||
|
|
||||||
|
# import class or module using currmodule
|
||||||
|
if currmodule:
|
||||||
|
target = try_import(currmodule + '.' + name)
|
||||||
|
|
||||||
|
# import class or module without currmodule
|
||||||
|
if target is None:
|
||||||
|
target = try_import(name)
|
||||||
|
|
||||||
|
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 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 '
|
||||||
|
'not a class or module' % name)
|
||||||
|
|
||||||
|
|
||||||
class InheritanceException(Exception):
|
class InheritanceException(Exception):
|
||||||
@@ -88,56 +140,11 @@ class InheritanceGraph(object):
|
|||||||
raise InheritanceException('No classes found for '
|
raise InheritanceException('No classes found for '
|
||||||
'inheritance diagram')
|
'inheritance diagram')
|
||||||
|
|
||||||
def _import_class_or_module(self, 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)
|
|
||||||
|
|
||||||
fullname = (path or '') + base
|
|
||||||
path = (path and path.rstrip('.') or '')
|
|
||||||
|
|
||||||
# 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))
|
|
||||||
|
|
||||||
# If a class, just return it
|
|
||||||
if inspect.isclass(todoc):
|
|
||||||
return [todoc]
|
|
||||||
elif inspect.ismodule(todoc):
|
|
||||||
classes = []
|
|
||||||
for cls in todoc.__dict__.values():
|
|
||||||
if inspect.isclass(cls) and cls.__module__ == todoc.__name__:
|
|
||||||
classes.append(cls)
|
|
||||||
return classes
|
|
||||||
raise InheritanceException('%r specified for inheritance diagram is '
|
|
||||||
'not a class or module' % name)
|
|
||||||
|
|
||||||
def _import_classes(self, class_names, currmodule):
|
def _import_classes(self, class_names, currmodule):
|
||||||
"""Import a list of classes."""
|
"""Import a list of classes."""
|
||||||
classes = []
|
classes = []
|
||||||
for name in class_names:
|
for name in class_names:
|
||||||
classes.extend(self._import_class_or_module(name, currmodule))
|
classes.extend(import_classes(name, currmodule))
|
||||||
return classes
|
return classes
|
||||||
|
|
||||||
def _class_info(self, classes, show_builtins, private_bases, parts):
|
def _class_info(self, classes, show_builtins, private_bases, parts):
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
# example.py
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# example.sphinx
|
||||||
|
|
||||||
|
|
||||||
|
class DummyClass(object):
|
||||||
|
pass
|
||||||
@@ -9,9 +9,56 @@
|
|||||||
:license: BSD, see LICENSE for details.
|
:license: BSD, see LICENSE for details.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from util import with_app
|
import sys
|
||||||
|
from util import with_app, rootdir, raises
|
||||||
|
from sphinx.ext.inheritance_diagram import InheritanceException, import_classes
|
||||||
|
|
||||||
|
|
||||||
@with_app('html', testroot='ext-inheritance_diagram')
|
@with_app('html', testroot='ext-inheritance_diagram')
|
||||||
def test_inheritance_diagram_html(app, status, warning):
|
def test_inheritance_diagram_html(app, status, warning):
|
||||||
app.builder.build_all()
|
app.builder.build_all()
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
raises(InheritanceException, import_classes, 'unknown.Unknown', None)
|
||||||
|
|
||||||
|
# a module having no classes
|
||||||
|
classes = import_classes('sphinx', None)
|
||||||
|
assert classes == []
|
||||||
|
|
||||||
|
classes = import_classes('sphinx', 'foo')
|
||||||
|
assert classes == []
|
||||||
|
|
||||||
|
# all of classes in the module
|
||||||
|
classes = import_classes('sphinx.application', None)
|
||||||
|
assert set(classes) == set([Sphinx, TemplateBridge])
|
||||||
|
|
||||||
|
# specified class in the module
|
||||||
|
classes = import_classes('sphinx.application.Sphinx', None)
|
||||||
|
assert classes == [Sphinx]
|
||||||
|
|
||||||
|
# specified class in current module
|
||||||
|
classes = import_classes('Sphinx', 'sphinx.application')
|
||||||
|
assert classes == [Sphinx]
|
||||||
|
|
||||||
|
# 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')
|
||||||
|
|
||||||
|
# import submodule on current module (refs: #3164)
|
||||||
|
classes = import_classes('sphinx', 'example')
|
||||||
|
assert classes == [DummyClass]
|
||||||
|
finally:
|
||||||
|
sys.path.pop()
|
||||||
|
|||||||
Reference in New Issue
Block a user