#510: Fix inheritance diagrams for classes that are not picklable.

No need to keep the actual list of classes; only names and edges need to be
present in generate_dot().
This commit is contained in:
Georg Brandl 2010-08-23 14:03:15 +00:00
parent 80ad3d7d69
commit b90ca05941
2 changed files with 50 additions and 43 deletions

View File

@ -1,3 +1,9 @@
Release 1.0.3 (in development)
==============================
* #510: Fix inheritance diagrams for classes that are not picklable.
Release 1.0.2 (Aug 14, 2010) Release 1.0.2 (Aug 14, 2010)
============================ ============================

View File

@ -66,7 +66,7 @@ class InheritanceGraph(object):
from all the way to the root "object", and then is able to generate a from all the way to the root "object", and then is able to generate a
graphviz dot graph from them. graphviz dot graph from them.
""" """
def __init__(self, class_names, currmodule, show_builtins=False): def __init__(self, class_names, currmodule, show_builtins=False, parts=0):
""" """
*class_names* is a list of child classes to show bases from. *class_names* is a list of child classes to show bases from.
@ -74,12 +74,11 @@ class InheritanceGraph(object):
in the graph. in the graph.
""" """
self.class_names = class_names self.class_names = class_names
self.classes = self._import_classes(class_names, currmodule) classes = self._import_classes(class_names, currmodule)
self.all_classes = self._all_classes(self.classes) self.class_info = self._class_info(classes, show_builtins, parts)
if len(self.all_classes) == 0: if not self.class_info:
raise InheritanceException('No classes found for ' raise InheritanceException('No classes found for '
'inheritance diagram') 'inheritance diagram')
self.show_builtins = show_builtins
def _import_class_or_module(self, name, currmodule): def _import_class_or_module(self, name, currmodule):
""" """
@ -129,36 +128,48 @@ class InheritanceGraph(object):
'not a class or module' % name) '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(self._import_class_or_module(name, currmodule))
return classes return classes
def _all_classes(self, classes): def _class_info(self, classes, show_builtins, parts):
""" """Return name and bases for all classes that are ancestors of
Return a list of all classes that are ancestors of *classes*. *classes*.
*parts* gives the number of dotted name parts that is removed from the
displayed node names.
""" """
all_classes = {} all_classes = {}
builtins = __builtins__.values()
def recurse(cls): def recurse(cls):
all_classes[cls] = None if not show_builtins and cls in builtins:
for c in cls.__bases__: return
if c not in all_classes:
recurse(c) nodename = self.class_name(cls, parts)
fullname = self.class_name(cls, 0)
baselist = []
all_classes[cls] = (nodename, fullname, baselist)
for base in cls.__bases__:
if not show_builtins and base in builtins:
return
baselist.append(self.class_name(base, parts))
if base not in all_classes:
recurse(base)
for cls in classes: for cls in classes:
recurse(cls) recurse(cls)
return all_classes.keys() return all_classes.values()
def class_name(self, cls, parts=0): def class_name(self, cls, parts=0):
""" """Given a class object, return a fully-qualified name.
Given a class object, return a fully-qualified name. This
works for things I've tested in matplotlib so far, but may not This works for things I've tested in matplotlib so far, but may not be
be completely general. completely general.
""" """
module = cls.__module__ module = cls.__module__
if module == '__builtin__': if module == '__builtin__':
@ -174,7 +185,7 @@ class InheritanceGraph(object):
""" """
Get all of the class names involved in the graph. Get all of the class names involved in the graph.
""" """
return [self.class_name(x) for x in self.all_classes] return [fullname for (_, fullname, _) in self.class_info]
# These are the default attrs for graphviz # These are the default attrs for graphviz
default_graph_attrs = { default_graph_attrs = {
@ -200,7 +211,7 @@ class InheritanceGraph(object):
def _format_graph_attrs(self, attrs): def _format_graph_attrs(self, attrs):
return ''.join(['%s=%s;\n' % x for x in attrs.items()]) return ''.join(['%s=%s;\n' % x for x in attrs.items()])
def generate_dot(self, name, parts=0, urls={}, env=None, def generate_dot(self, name, urls={}, env=None,
graph_attrs={}, node_attrs={}, edge_attrs={}): graph_attrs={}, node_attrs={}, edge_attrs={}):
""" """
Generate a graphviz dot graph from the classes that Generate a graphviz dot graph from the classes that
@ -228,26 +239,17 @@ class InheritanceGraph(object):
res.append('digraph %s {\n' % name) res.append('digraph %s {\n' % name)
res.append(self._format_graph_attrs(g_attrs)) res.append(self._format_graph_attrs(g_attrs))
for cls in self.all_classes: for name, fullname, bases in self.class_info:
if not self.show_builtins and cls in __builtins__.values():
continue
name = self.class_name(cls, parts)
# Write the node # Write the node
this_node_attrs = n_attrs.copy() this_node_attrs = n_attrs.copy()
url = urls.get(self.class_name(cls)) url = urls.get(fullname)
if url is not None: if url is not None:
this_node_attrs['URL'] = '"%s"' % url this_node_attrs['URL'] = '"%s"' % url
res.append(' "%s" [%s];\n' % res.append(' "%s" [%s];\n' %
(name, self._format_node_attrs(this_node_attrs))) (name, self._format_node_attrs(this_node_attrs)))
# Write the edges # Write the edges
for base in cls.__bases__: for base_name in bases:
if not self.show_builtins and base in __builtins__.values():
continue
base_name = self.class_name(base, parts)
res.append(' "%s" -> "%s" [%s];\n' % res.append(' "%s" -> "%s" [%s];\n' %
(base_name, name, (base_name, name,
self._format_node_attrs(e_attrs))) self._format_node_attrs(e_attrs)))
@ -280,11 +282,15 @@ class InheritanceDiagram(Directive):
env = self.state.document.settings.env env = self.state.document.settings.env
class_names = self.arguments[0].split() class_names = self.arguments[0].split()
class_role = env.get_domain('py').role('class') class_role = env.get_domain('py').role('class')
# Store the original content for use as a hash
node['parts'] = self.options.get('parts', 0)
node['content'] = ', '.join(class_names)
# Create a graph starting with the list of classes # Create a graph starting with the list of classes
try: try:
graph = InheritanceGraph(class_names, graph = InheritanceGraph(
env.temp_data.get('py:module')) class_names, env.temp_data.get('py:module'),
parts=node['parts'])
except InheritanceException, err: except InheritanceException, err:
return [node.document.reporter.warning(err.args[0], return [node.document.reporter.warning(err.args[0],
line=self.lineno)] line=self.lineno)]
@ -300,9 +306,6 @@ class InheritanceDiagram(Directive):
# Store the graph object so we can use it to generate the # Store the graph object so we can use it to generate the
# dot file later # dot file later
node['graph'] = graph node['graph'] = graph
# Store the original content for use as a hash
node['parts'] = self.options.get('parts', 0)
node['content'] = ', '.join(class_names)
return [node] return [node]
@ -316,7 +319,6 @@ def html_visit_inheritance_diagram(self, node):
image map. image map.
""" """
graph = node['graph'] graph = node['graph']
parts = node['parts']
graph_hash = get_graph_hash(node) graph_hash = get_graph_hash(node)
name = 'inheritance%s' % graph_hash name = 'inheritance%s' % graph_hash
@ -329,7 +331,7 @@ def html_visit_inheritance_diagram(self, node):
elif child.get('refid') is not None: elif child.get('refid') is not None:
urls[child['reftitle']] = '#' + child.get('refid') urls[child['reftitle']] = '#' + child.get('refid')
dotcode = graph.generate_dot(name, parts, urls, env=self.builder.env) dotcode = graph.generate_dot(name, urls, env=self.builder.env)
render_dot_html(self, node, dotcode, [], 'inheritance', 'inheritance', render_dot_html(self, node, dotcode, [], 'inheritance', 'inheritance',
alt='Inheritance diagram of ' + node['content']) alt='Inheritance diagram of ' + node['content'])
raise nodes.SkipNode raise nodes.SkipNode
@ -340,12 +342,11 @@ def latex_visit_inheritance_diagram(self, node):
Output the graph for LaTeX. This will insert a PDF. Output the graph for LaTeX. This will insert a PDF.
""" """
graph = node['graph'] graph = node['graph']
parts = node['parts']
graph_hash = get_graph_hash(node) graph_hash = get_graph_hash(node)
name = 'inheritance%s' % graph_hash name = 'inheritance%s' % graph_hash
dotcode = graph.generate_dot(name, parts, env=self.builder.env, dotcode = graph.generate_dot(name, env=self.builder.env,
graph_attrs={'size': '"6.0,6.0"'}) graph_attrs={'size': '"6.0,6.0"'})
render_dot_latex(self, node, dotcode, [], 'inheritance') render_dot_latex(self, node, dotcode, [], 'inheritance')
raise nodes.SkipNode raise nodes.SkipNode