mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Add top-classes option to sphinx.ext.inheritance_diagram
This will limit the scope of inheritance traversal
This commit is contained in:
parent
72e70b1b36
commit
9486f0d8f7
4
CHANGES
4
CHANGES
@ -17,6 +17,8 @@ Features added
|
||||
* C++, add a ``cpp:expr`` role for inserting inline C++ expressions or types.
|
||||
* #3638: Allow to change a label of reference to equation using
|
||||
``math_eqref_format``
|
||||
* Add ``top-classes`` option for the ``sphinx.ext.inheritance_diagram``
|
||||
extension to limit the scope of inheritance graphs.
|
||||
|
||||
Features removed
|
||||
----------------
|
||||
@ -52,8 +54,10 @@ Bugs fixed
|
||||
|
||||
Testing
|
||||
--------
|
||||
|
||||
* Add tests for the ``sphinx.ext.inheritance_diagram`` extension.
|
||||
|
||||
|
||||
Release 1.6.3 (in development)
|
||||
==============================
|
||||
|
||||
|
@ -42,6 +42,58 @@ It adds this directive:
|
||||
.. versionchanged:: 1.5
|
||||
Added ``caption`` option
|
||||
|
||||
It also supports a ``top-classes`` option which requires one or more class
|
||||
names separated by comma. If specified inheritance traversal will stop at the
|
||||
specified class names. Given the following Python module::
|
||||
|
||||
"""
|
||||
A
|
||||
/ \
|
||||
B C
|
||||
/ \ / \
|
||||
E D F
|
||||
"""
|
||||
|
||||
class A(object):
|
||||
pass
|
||||
|
||||
class B(A):
|
||||
pass
|
||||
|
||||
class C(A):
|
||||
pass
|
||||
|
||||
class D(B, C):
|
||||
pass
|
||||
|
||||
class E(B):
|
||||
pass
|
||||
|
||||
class F(C):
|
||||
pass
|
||||
|
||||
If you have specified a module in the inheritance diagram like this::
|
||||
|
||||
.. inheritance-diagram::
|
||||
dummy.test
|
||||
:top-classes: dummy.test.B, dummy.test.C
|
||||
|
||||
any base classes which are ancestors to ``top-classes`` and are also defined
|
||||
in the same module will be rendered as stand alone nodes. In this example
|
||||
class A will be rendered as stand alone node in the graph. This is a known
|
||||
issue due to how this extension works internally.
|
||||
|
||||
If you don't want class A (or any other ancestors) to be visible then specify
|
||||
only the classes you would like to generate the diagram for like this::
|
||||
|
||||
.. inheritance-diagram::
|
||||
dummy.test.D
|
||||
dummy.test.E
|
||||
dummy.test.F
|
||||
:top-classes: dummy.test.B, dummy.test.C
|
||||
|
||||
.. versionchanged:: 1.7
|
||||
Added ``top-classes`` option to limit the scope of inheritance graphs.
|
||||
|
||||
New config values are:
|
||||
|
||||
|
@ -133,8 +133,8 @@ class InheritanceGraph(object):
|
||||
graphviz dot graph from them.
|
||||
"""
|
||||
def __init__(self, class_names, currmodule, show_builtins=False,
|
||||
private_bases=False, parts=0):
|
||||
# type: (unicode, str, bool, bool, int) -> None
|
||||
private_bases=False, parts=0, top_classes=[]):
|
||||
# type: (unicode, str, bool, bool, int, List[Any]) -> None
|
||||
"""*class_names* is a list of child classes to show bases from.
|
||||
|
||||
If *show_builtins* is True, then Python builtins will be shown
|
||||
@ -143,7 +143,7 @@ class InheritanceGraph(object):
|
||||
self.class_names = class_names
|
||||
classes = self._import_classes(class_names, currmodule)
|
||||
self.class_info = self._class_info(classes, show_builtins,
|
||||
private_bases, parts)
|
||||
private_bases, parts, top_classes)
|
||||
if not self.class_info:
|
||||
raise InheritanceException('No classes found for '
|
||||
'inheritance diagram')
|
||||
@ -156,13 +156,16 @@ class InheritanceGraph(object):
|
||||
classes.extend(import_classes(name, currmodule))
|
||||
return classes
|
||||
|
||||
def _class_info(self, classes, show_builtins, private_bases, parts):
|
||||
# type: (List[Any], bool, bool, int) -> List[Tuple[unicode, unicode, List[unicode], unicode]] # NOQA
|
||||
def _class_info(self, classes, show_builtins, private_bases, parts, top_classes):
|
||||
# type: (List[Any], bool, bool, int, List[Any]) -> List[Tuple[unicode, unicode, List[unicode], unicode]] # NOQA
|
||||
"""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.
|
||||
|
||||
*top_classes* gives the name(s) of the top most ancestor class to traverse
|
||||
to. Multiple names can be specified separated by comma.
|
||||
"""
|
||||
all_classes = {}
|
||||
py_builtins = vars(builtins).values()
|
||||
@ -192,6 +195,10 @@ class InheritanceGraph(object):
|
||||
|
||||
baselist = [] # type: List[unicode]
|
||||
all_classes[cls] = (nodename, fullname, baselist, tooltip)
|
||||
|
||||
if fullname in top_classes:
|
||||
return
|
||||
|
||||
for base in cls.__bases__:
|
||||
if not show_builtins and base in py_builtins:
|
||||
continue
|
||||
@ -321,6 +328,7 @@ class InheritanceDiagram(Directive):
|
||||
'parts': directives.nonnegative_int,
|
||||
'private-bases': directives.flag,
|
||||
'caption': directives.unchanged,
|
||||
'top-classes': directives.unchanged_required,
|
||||
}
|
||||
|
||||
def run(self):
|
||||
@ -333,13 +341,19 @@ class InheritanceDiagram(Directive):
|
||||
# Store the original content for use as a hash
|
||||
node['parts'] = self.options.get('parts', 0)
|
||||
node['content'] = ', '.join(class_names)
|
||||
node['top-classes'] = []
|
||||
for cls in self.options.get('top-classes', '').split(','):
|
||||
cls = cls.strip()
|
||||
if cls:
|
||||
node['top-classes'].append(cls)
|
||||
|
||||
# Create a graph starting with the list of classes
|
||||
try:
|
||||
graph = InheritanceGraph(
|
||||
class_names, env.ref_context.get('py:module'),
|
||||
parts=node['parts'],
|
||||
private_bases='private-bases' in self.options)
|
||||
private_bases='private-bases' in self.options,
|
||||
top_classes=node['top-classes'])
|
||||
except InheritanceException as err:
|
||||
return [node.document.reporter.warning(err.args[0],
|
||||
line=self.lineno)]
|
||||
|
@ -0,0 +1,6 @@
|
||||
Diagram using module with 2 top classes
|
||||
=======================================
|
||||
|
||||
.. inheritance-diagram::
|
||||
dummy.test
|
||||
:top-classes: dummy.test.B, dummy.test.C
|
7
tests/roots/test-inheritance/diagram_w_1_top_class.rst
Normal file
7
tests/roots/test-inheritance/diagram_w_1_top_class.rst
Normal file
@ -0,0 +1,7 @@
|
||||
Diagram using 1 top class
|
||||
=========================
|
||||
|
||||
.. inheritance-diagram::
|
||||
dummy.test
|
||||
:top-classes: dummy.test.B
|
||||
|
9
tests/roots/test-inheritance/diagram_w_2_top_classes.rst
Normal file
9
tests/roots/test-inheritance/diagram_w_2_top_classes.rst
Normal file
@ -0,0 +1,9 @@
|
||||
Diagram using 2 top classes
|
||||
===========================
|
||||
|
||||
.. inheritance-diagram::
|
||||
dummy.test.F
|
||||
dummy.test.D
|
||||
dummy.test.E
|
||||
:top-classes: dummy.test.B, dummy.test.C
|
||||
|
@ -13,6 +13,7 @@ import os
|
||||
import pytest
|
||||
from sphinx.ext.inheritance_diagram import InheritanceDiagram
|
||||
|
||||
|
||||
@pytest.mark.sphinx(buildername="html", testroot="inheritance")
|
||||
@pytest.mark.usefixtures('if_graphviz_found')
|
||||
def test_inheritance_diagram(app, status, warning):
|
||||
@ -51,7 +52,8 @@ def test_inheritance_diagram(app, status, warning):
|
||||
('dummy.test.F', 'dummy.test.F', ['dummy.test.C'], None),
|
||||
('dummy.test.C', 'dummy.test.C', ['dummy.test.A'], None),
|
||||
('dummy.test.E', 'dummy.test.E', ['dummy.test.B'], None),
|
||||
('dummy.test.D', 'dummy.test.D', ['dummy.test.B', 'dummy.test.C'], None),
|
||||
('dummy.test.D', 'dummy.test.D',
|
||||
['dummy.test.B', 'dummy.test.C'], None),
|
||||
('dummy.test.B', 'dummy.test.B', ['dummy.test.A'], None)
|
||||
]
|
||||
|
||||
@ -65,3 +67,67 @@ def test_inheritance_diagram(app, status, warning):
|
||||
('D', 'dummy.test.D', ['B', 'C'], None),
|
||||
('B', 'dummy.test.B', ['A'], None)
|
||||
]
|
||||
|
||||
# inheritance diagram with 1 top class
|
||||
# :top-classes: dummy.test.B
|
||||
# rendering should be
|
||||
# A
|
||||
# \
|
||||
# B C
|
||||
# / \ / \
|
||||
# E D F
|
||||
#
|
||||
for cls in graphs['diagram_w_1_top_class'].class_info:
|
||||
assert cls in [
|
||||
('dummy.test.A', 'dummy.test.A', [], None),
|
||||
('dummy.test.F', 'dummy.test.F', ['dummy.test.C'], None),
|
||||
('dummy.test.C', 'dummy.test.C', ['dummy.test.A'], None),
|
||||
('dummy.test.E', 'dummy.test.E', ['dummy.test.B'], None),
|
||||
('dummy.test.D', 'dummy.test.D',
|
||||
['dummy.test.B', 'dummy.test.C'], None),
|
||||
('dummy.test.B', 'dummy.test.B', [], None)
|
||||
]
|
||||
|
||||
|
||||
# inheritance diagram with 2 top classes
|
||||
# :top-classes: dummy.test.B, dummy.test.C
|
||||
# Note: we're specifying separate classes, not the entire module here
|
||||
# rendering should be
|
||||
#
|
||||
# B C
|
||||
# / \ / \
|
||||
# E D F
|
||||
#
|
||||
for cls in graphs['diagram_w_2_top_classes'].class_info:
|
||||
assert cls in [
|
||||
('dummy.test.F', 'dummy.test.F', ['dummy.test.C'], None),
|
||||
('dummy.test.C', 'dummy.test.C', [], None),
|
||||
('dummy.test.E', 'dummy.test.E', ['dummy.test.B'], None),
|
||||
('dummy.test.D', 'dummy.test.D',
|
||||
['dummy.test.B', 'dummy.test.C'], None),
|
||||
('dummy.test.B', 'dummy.test.B', [], None)
|
||||
]
|
||||
|
||||
# inheritance diagram with 2 top classes and specifiying the entire module
|
||||
# rendering should be
|
||||
#
|
||||
# A
|
||||
# B C
|
||||
# / \ / \
|
||||
# E D F
|
||||
#
|
||||
# Note: dummy.test.A is included in the graph before its descendants are even processed
|
||||
# b/c we've specified to load the entire module. The way InheritanceGraph works it is very
|
||||
# hard to exclude parent classes once after they have been included in the graph.
|
||||
# If you'd like to not show class A in the graph don't specify the entire module.
|
||||
# this is a known issue.
|
||||
for cls in graphs['diagram_module_w_2_top_classes'].class_info:
|
||||
assert cls in [
|
||||
('dummy.test.F', 'dummy.test.F', ['dummy.test.C'], None),
|
||||
('dummy.test.C', 'dummy.test.C', [], None),
|
||||
('dummy.test.E', 'dummy.test.E', ['dummy.test.B'], None),
|
||||
('dummy.test.D', 'dummy.test.D',
|
||||
['dummy.test.B', 'dummy.test.C'], None),
|
||||
('dummy.test.B', 'dummy.test.B', [], None),
|
||||
('dummy.test.A', 'dummy.test.A', [], None),
|
||||
]
|
||||
|
Loading…
Reference in New Issue
Block a user