Skip to content

Commit

Permalink
Enable automatic formatting for sphinx/ext/inheritance_diagram.py
Browse files Browse the repository at this point in the history
  • Loading branch information
AA-Turner committed Dec 25, 2024
1 parent 7b9a431 commit c5672bf
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 51 deletions.
1 change: 0 additions & 1 deletion .ruff.toml
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,6 @@ exclude = [
"sphinx/domains/python/_object.py",
"sphinx/domains/rst.py",
"sphinx/domains/std/__init__.py",
"sphinx/ext/inheritance_diagram.py",
"sphinx/ext/linkcode.py",
"sphinx/ext/todo.py",
"sphinx/ext/viewcode.py",
Expand Down
145 changes: 95 additions & 50 deletions sphinx/ext/inheritance_diagram.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,13 @@ class E(B): pass
from sphinx.writers.latex import LaTeXTranslator
from sphinx.writers.texinfo import TexinfoTranslator

module_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,
)


PY_BUILTINS: Final = frozenset(filter(inspect.isclass, vars(builtins).values()))
Expand Down Expand Up @@ -116,17 +120,21 @@ def import_classes(name: str, currmodule: str) -> Any:
if target is None:
raise InheritanceException(
'Could not import class or module %r specified for '
'inheritance diagram' % name)
'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
return [cls for cls in target.__dict__.values()
if inspect.isclass(cls) and cls.__module__ == target.__name__]
raise InheritanceException('%r specified for inheritance diagram is '
'not a class or module' % name)
return [
cls
for cls in target.__dict__.values()
if inspect.isclass(cls) and cls.__module__ == target.__name__
]
msg = f'{name!r} specified for inheritance diagram is not a class or module'
raise InheritanceException(msg)


class InheritanceException(Exception):
Expand All @@ -140,19 +148,26 @@ class InheritanceGraph:
graphviz dot graph from them.
"""

def __init__(self, class_names: list[str], currmodule: str, show_builtins: bool = False,
private_bases: bool = False, parts: int = 0,
aliases: dict[str, str] | None = None, top_classes: Sequence[Any] = (),
) -> None:
def __init__(
self,
class_names: list[str],
currmodule: str,
show_builtins: bool = False,
private_bases: bool = False,
parts: int = 0,
aliases: dict[str, str] | None = None,
top_classes: Sequence[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
in the graph.
"""
self.class_names = class_names
classes = self._import_classes(class_names, currmodule)
self.class_info = self._class_info(classes, show_builtins,
private_bases, parts, aliases, top_classes)
self.class_info = self._class_info(
classes, show_builtins, private_bases, parts, aliases, top_classes
)
if not self.class_info:
msg = 'No classes found for inheritance diagram'
raise InheritanceException(msg)
Expand All @@ -164,9 +179,15 @@ def _import_classes(self, class_names: list[str], currmodule: str) -> list[Any]:
classes.extend(import_classes(name, currmodule))
return classes

def _class_info(self, classes: list[Any], show_builtins: bool, private_bases: bool,
parts: int, aliases: dict[str, str] | None, top_classes: Sequence[Any],
) -> list[tuple[str, str, Sequence[str], str | None]]:
def _class_info(
self,
classes: list[Any],
show_builtins: bool,
private_bases: bool,
parts: int,
aliases: dict[str, str] | None,
top_classes: Sequence[Any],
) -> list[tuple[str, str, Sequence[str], str | None]]:
"""Return name and bases for all classes that are ancestors of
*classes*.
Expand Down Expand Up @@ -197,7 +218,7 @@ def recurse(cls: Any) -> None:
tooltip = None
try:
if cls.__doc__:
doc = cls.__doc__.strip().split("\n")[0]
doc = cls.__doc__.strip().split('\n')[0]
if doc:
tooltip = '"%s"' % doc.replace('"', '\\"')
except Exception: # might raise AttributeError for strange classes
Expand All @@ -223,12 +244,11 @@ def recurse(cls: Any) -> None:

return [
(cls_name, fullname, tuple(bases), tooltip)
for (cls_name, fullname, bases, tooltip)
in all_classes.values()
for (cls_name, fullname, bases, tooltip) in all_classes.values()
]

def class_name(
self, cls: Any, parts: int = 0, aliases: dict[str, str] | None = None,
self, cls: Any, parts: int = 0, aliases: dict[str, str] | None = None
) -> str:
"""Given a class object, return a fully-qualified name.
Expand Down Expand Up @@ -263,8 +283,7 @@ def get_all_class_names(self) -> list[str]:
'shape': 'box',
'fontsize': 10,
'height': 0.25,
'fontname': '"Vera Sans, DejaVu Sans, Liberation Sans, '
'Arial, Helvetica, sans"',
'fontname': '"Vera Sans, DejaVu Sans, Liberation Sans, Arial, Helvetica, sans"',
'style': '"setlinewidth(0.5),filled"',
'fillcolor': 'white',
}
Expand All @@ -279,12 +298,15 @@ def _format_node_attrs(self, attrs: dict[str, Any]) -> str:
def _format_graph_attrs(self, attrs: dict[str, Any]) -> str:
return ''.join(f'{k}={v};\n' for k, v in sorted(attrs.items()))

def generate_dot(self, name: str, urls: dict[str, str] | None = None,
env: BuildEnvironment | None = None,
graph_attrs: dict | None = None,
node_attrs: dict | None = None,
edge_attrs: dict | None = None,
) -> str:
def generate_dot(
self,
name: str,
urls: dict[str, str] | None = None,
env: BuildEnvironment | None = None,
graph_attrs: dict | None = None,
node_attrs: dict | None = None,
edge_attrs: dict | None = None,
) -> str:
"""Generate a graphviz dot graph from the classes that were passed in
to __init__.
Expand Down Expand Up @@ -320,19 +342,21 @@ def generate_dot(self, name: str, urls: dict[str, str] | None = None,
# Write the node
this_node_attrs = n_attrs.copy()
if fullname in urls:
this_node_attrs["URL"] = f'"{urls[fullname]}"'
this_node_attrs["target"] = '"_top"'
this_node_attrs['URL'] = f'"{urls[fullname]}"'
this_node_attrs['target'] = '"_top"'
if tooltip:
this_node_attrs["tooltip"] = tooltip
res.append(f' "{cls_name}" [{self._format_node_attrs(this_node_attrs)}];\n')
this_node_attrs['tooltip'] = tooltip
res.append(
f' "{cls_name}" [{self._format_node_attrs(this_node_attrs)}];\n'
)

# Write the edges
res.extend(
f' "{base_name}" -> "{cls_name}" [{self._format_node_attrs(e_attrs)}];\n'
for base_name in bases
)
res.append("}\n")
return "".join(res)
res.append('}\n')
return ''.join(res)


class inheritance_diagram(graphviz):
Expand Down Expand Up @@ -376,11 +400,13 @@ def run(self) -> list[Node]:
# Create a graph starting with the list of classes
try:
graph = InheritanceGraph(
class_names, self.env.ref_context.get('py:module'), # type: ignore[arg-type]
class_names,
self.env.ref_context.get('py:module'), # type: ignore[arg-type]
parts=node['parts'],
private_bases='private-bases' in self.options,
aliases=self.config.inheritance_alias,
top_classes=node['top-classes'])
top_classes=node['top-classes'],
)
except InheritanceException as err:
return [node.document.reporter.warning(err, line=self.lineno)]

Expand All @@ -390,7 +416,8 @@ def run(self) -> list[Node]:
# removed from the doctree after we're done with them.
for name in graph.get_all_class_names():
refnodes, x = class_role( # type: ignore[misc]
'class', ':class:`%s`' % name, name, 0, self.state.inliner)
'class', f':class:`{name}`', name, 0, self.state.inliner
)
node.extend(refnodes)
# Store the graph object so we can use it to generate the
# dot file later
Expand All @@ -410,7 +437,9 @@ def get_graph_hash(node: inheritance_diagram) -> str:
return hashlib.md5(encoded, usedforsecurity=False).hexdigest()[-10:]


def html_visit_inheritance_diagram(self: HTML5Translator, node: inheritance_diagram) -> None:
def html_visit_inheritance_diagram(
self: HTML5Translator, node: inheritance_diagram
) -> None:
"""
Output the graph for HTML. This will insert a PNG with clickable
image map.
Expand All @@ -422,7 +451,9 @@ def html_visit_inheritance_diagram(self: HTML5Translator, node: inheritance_diag

# Create a mapping from fully-qualified class names to URLs.
graphviz_output_format = self.builder.env.config.graphviz_output_format.upper()
current_filename = os.path.basename(self.builder.current_docname + self.builder.out_suffix)
current_filename = os.path.basename(
self.builder.current_docname + self.builder.out_suffix
)
urls = {}
pending_xrefs = cast('Iterable[addnodes.pending_xref]', node)
for child in pending_xrefs:
Expand All @@ -441,12 +472,21 @@ def html_visit_inheritance_diagram(self: HTML5Translator, node: inheritance_diag
urls[child['reftitle']] = '#' + child.get('refid')

dotcode = graph.generate_dot(name, urls, env=self.builder.env)
render_dot_html(self, node, dotcode, {}, 'inheritance', 'inheritance',
alt='Inheritance diagram of ' + node['content'])
render_dot_html(
self,
node,
dotcode,
{},
'inheritance',
'inheritance',
alt='Inheritance diagram of ' + node['content'],
)
raise nodes.SkipNode


def latex_visit_inheritance_diagram(self: LaTeXTranslator, node: inheritance_diagram) -> None:
def latex_visit_inheritance_diagram(
self: LaTeXTranslator, node: inheritance_diagram
) -> None:
"""
Output the graph for LaTeX. This will insert a PDF.
"""
Expand All @@ -455,14 +495,17 @@ def latex_visit_inheritance_diagram(self: LaTeXTranslator, node: inheritance_dia
graph_hash = get_graph_hash(node)
name = 'inheritance%s' % graph_hash

dotcode = graph.generate_dot(name, env=self.builder.env,
graph_attrs={'size': '"6.0,6.0"'})
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


def texinfo_visit_inheritance_diagram(self: TexinfoTranslator, node: inheritance_diagram,
) -> None:
def texinfo_visit_inheritance_diagram(
self: TexinfoTranslator,
node: inheritance_diagram,
) -> None:
"""
Output the graph for Texinfo. This will insert a PNG.
"""
Expand All @@ -471,8 +514,9 @@ def texinfo_visit_inheritance_diagram(self: TexinfoTranslator, node: inheritance
graph_hash = get_graph_hash(node)
name = 'inheritance%s' % graph_hash

dotcode = graph.generate_dot(name, env=self.builder.env,
graph_attrs={'size': '"6.0,6.0"'})
dotcode = graph.generate_dot(
name, env=self.builder.env, graph_attrs={'size': '"6.0,6.0"'}
)
render_dot_texinfo(self, node, dotcode, {}, 'inheritance')
raise nodes.SkipNode

Expand All @@ -489,7 +533,8 @@ def setup(app: Sphinx) -> ExtensionMetadata:
html=(html_visit_inheritance_diagram, None),
text=(skip, None),
man=(skip, None),
texinfo=(texinfo_visit_inheritance_diagram, None))
texinfo=(texinfo_visit_inheritance_diagram, None),
)
app.add_directive('inheritance-diagram', InheritanceDiagram)
app.add_config_value('inheritance_graph_attrs', {}, '')
app.add_config_value('inheritance_node_attrs', {}, '')
Expand Down

0 comments on commit c5672bf

Please sign in to comment.