Georg Brandl avatar Georg Brandl committed f6c989e

#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().

Comments (0)

Files changed (2)

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

sphinx/ext/inheritance_diagram.py

     from all the way to the root "object", and then is able to generate a
     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.
 
         in the graph.
         """
         self.class_names = class_names
-        self.classes = self._import_classes(class_names, currmodule)
-        self.all_classes = self._all_classes(self.classes)
-        if len(self.all_classes) == 0:
+        classes = self._import_classes(class_names, currmodule)
+        self.class_info = self._class_info(classes, show_builtins, parts)
+        if not self.class_info:
             raise InheritanceException('No classes found for '
                                        'inheritance diagram')
-        self.show_builtins = show_builtins
 
     def _import_class_or_module(self, name, currmodule):
         """
                                    'not a class or module' % name)
 
     def _import_classes(self, class_names, currmodule):
-        """
-        Import a list of classes.
-        """
+        """Import a list of classes."""
         classes = []
         for name in class_names:
             classes.extend(self._import_class_or_module(name, currmodule))
         return classes
 
-    def _all_classes(self, classes):
-        """
-        Return a list of all classes that are ancestors of *classes*.
+    def _class_info(self, classes, show_builtins, parts):
+        """Return name and bases for all classes that are ancestors of
+        *classes*.
+
+        *parts* gives the number of dotted name parts that is removed from the
+        displayed node names.
         """
         all_classes = {}
+        builtins = __builtins__.values()
 
         def recurse(cls):
-            all_classes[cls] = None
-            for c in cls.__bases__:
-                if c not in all_classes:
-                    recurse(c)
+            if not show_builtins and cls in builtins:
+                return
+
+            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:
             recurse(cls)
 
-        return all_classes.keys()
+        return all_classes.values()
 
     def class_name(self, cls, parts=0):
-        """
-        Given a class object, return a fully-qualified name.  This
-        works for things I've tested in matplotlib so far, but may not
-        be completely general.
+        """Given a class object, return a fully-qualified name.
+
+        This works for things I've tested in matplotlib so far, but may not be
+        completely general.
         """
         module = cls.__module__
         if module == '__builtin__':
         """
         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
     default_graph_attrs = {
     def _format_graph_attrs(self, attrs):
         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={}):
         """
         Generate a graphviz dot graph from the classes that
         res.append('digraph %s {\n' % name)
         res.append(self._format_graph_attrs(g_attrs))
 
-        for cls in self.all_classes:
-            if not self.show_builtins and cls in __builtins__.values():
-                continue
-
-            name = self.class_name(cls, parts)
-
+        for name, fullname, bases in self.class_info:
             # Write the node
             this_node_attrs = n_attrs.copy()
-            url = urls.get(self.class_name(cls))
+            url = urls.get(fullname)
             if url is not None:
                 this_node_attrs['URL'] = '"%s"' % url
             res.append('  "%s" [%s];\n' %
                        (name, self._format_node_attrs(this_node_attrs)))
 
             # Write the edges
-            for base in cls.__bases__:
-                if not self.show_builtins and base in __builtins__.values():
-                    continue
-
-                base_name = self.class_name(base, parts)
+            for base_name in bases:
                 res.append('  "%s" -> "%s" [%s];\n' %
                            (base_name, name,
                             self._format_node_attrs(e_attrs)))
         env = self.state.document.settings.env
         class_names = self.arguments[0].split()
         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
         try:
-            graph = InheritanceGraph(class_names,
-                                     env.temp_data.get('py:module'))
+            graph = InheritanceGraph(
+                class_names, env.temp_data.get('py:module'),
+                parts=node['parts'])
         except InheritanceException, err:
             return [node.document.reporter.warning(err.args[0],
                                                    line=self.lineno)]
         # Store the graph object so we can use it to generate the
         # dot file later
         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]
 
 
     image map.
     """
     graph = node['graph']
-    parts = node['parts']
 
     graph_hash = get_graph_hash(node)
     name = 'inheritance%s' % graph_hash
         elif child.get('refid') is not None:
             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',
                     alt='Inheritance diagram of ' + node['content'])
     raise nodes.SkipNode
     Output the graph for LaTeX.  This will insert a PDF.
     """
     graph = node['graph']
-    parts = node['parts']
 
     graph_hash = get_graph_hash(node)
     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"'})
     render_dot_latex(self, node, dotcode, [], 'inheritance')
     raise nodes.SkipNode
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.