Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions doc/whatsnew/fragments/8476.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Add Pyreverse option to exclude standalone nodes from diagrams with `--no-standalone`.

Closes #8476
8 changes: 8 additions & 0 deletions pylint/pyreverse/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,14 @@
"help": "don't show attributes and methods in the class boxes; this disables -f values",
},
),
(
"no-standalone",
{
"action": "store_true",
"default": False,
"help": "only show nodes with connections",
},
),
(
"output",
{
Expand Down
13 changes: 13 additions & 0 deletions pylint/pyreverse/writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ def write_packages(self, diagram: PackageDiagram) -> None:
# sorted to get predictable (hence testable) results
for module in sorted(diagram.modules(), key=lambda x: x.title):
module.fig_id = module.node.qname()
if self.config.no_standalone and not any(
module in (rel.from_object, rel.to_object)
for rel in diagram.get_relationships("depends")
):
continue

self.printer.emit_node(
module.fig_id,
type_=NodeType.PACKAGE,
Expand All @@ -75,6 +81,13 @@ def write_classes(self, diagram: ClassDiagram) -> None:
# sorted to get predictable (hence testable) results
for obj in sorted(diagram.objects, key=lambda x: x.title): # type: ignore[no-any-return]
obj.fig_id = obj.node.qname()
if self.config.no_standalone and not any(
obj in (rel.from_object, rel.to_object)
for rel_type in ("specialization", "association", "aggregation")
for rel in diagram.get_relationships(rel_type)
):
continue

self.printer.emit_node(
obj.fig_id,
type_=NodeType.CLASS,
Expand Down
2 changes: 2 additions & 0 deletions pylint/testutils/pyreverse.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ def __init__(
all_ancestors: bool | None = None,
show_associated: int | None = None,
all_associated: bool | None = None,
no_standalone: bool = False,
show_builtin: bool = False,
show_stdlib: bool = False,
module_names: bool | None = None,
Expand All @@ -59,6 +60,7 @@ def __init__(
self.all_ancestors = all_ancestors
self.show_associated = show_associated
self.all_associated = all_associated
self.no_standalone = no_standalone
self.show_builtin = show_builtin
self.show_stdlib = show_stdlib
self.module_names = module_names
Expand Down
8 changes: 8 additions & 0 deletions tests/pyreverse/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ def colorized_dot_config() -> PyreverseConfig:
)


@pytest.fixture()
def no_standalone_dot_config() -> PyreverseConfig:
return PyreverseConfig(
output_format="dot",
no_standalone=True,
)


@pytest.fixture()
def puml_config() -> PyreverseConfig:
return PyreverseConfig(
Expand Down
12 changes: 12 additions & 0 deletions tests/pyreverse/data/no_standalone_classes.dot
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
digraph "classes" {
rankdir=BT
charset="utf-8"
"data.clientmodule_test.Ancestor" [color="black", fontcolor="black", label=<{Ancestor|attr : str<br ALIGN="LEFT"/>cls_member<br ALIGN="LEFT"/>|get_value()<br ALIGN="LEFT"/>set_value(value)<br ALIGN="LEFT"/>}>, shape="record", style="solid"];
"data.suppliermodule_test.DoNothing" [color="black", fontcolor="black", label=<{DoNothing|<br ALIGN="LEFT"/>|}>, shape="record", style="solid"];
"data.suppliermodule_test.DoNothing2" [color="black", fontcolor="black", label=<{DoNothing2|<br ALIGN="LEFT"/>|}>, shape="record", style="solid"];
"data.clientmodule_test.Specialization" [color="black", fontcolor="black", label=<{Specialization|TYPE : str<br ALIGN="LEFT"/>relation<br ALIGN="LEFT"/>relation2<br ALIGN="LEFT"/>top : str<br ALIGN="LEFT"/>|from_value(value: int)<br ALIGN="LEFT"/>increment_value(): None<br ALIGN="LEFT"/>transform_value(value: int): int<br ALIGN="LEFT"/>}>, shape="record", style="solid"];
"data.clientmodule_test.Specialization" -> "data.clientmodule_test.Ancestor" [arrowhead="empty", arrowtail="none"];
"data.suppliermodule_test.DoNothing" -> "data.clientmodule_test.Ancestor" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="cls_member", style="solid"];
"data.suppliermodule_test.DoNothing" -> "data.clientmodule_test.Specialization" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="relation", style="solid"];
"data.suppliermodule_test.DoNothing2" -> "data.clientmodule_test.Specialization" [arrowhead="odiamond", arrowtail="none", fontcolor="green", label="relation2", style="solid"];
}
7 changes: 7 additions & 0 deletions tests/pyreverse/data/no_standalone_packages.dot
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
digraph "packages" {
rankdir=BT
charset="utf-8"
"data.clientmodule_test" [color="black", label=<data.clientmodule_test>, shape="box", style="solid"];
"data.suppliermodule_test" [color="black", label=<data.suppliermodule_test>, shape="box", style="solid"];
"data.clientmodule_test" -> "data.suppliermodule_test" [arrowhead="open", arrowtail="none"];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
classDiagram
class A {
}
class B {
}
B --|> A
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class A: pass

class B(A): pass

class C: pass
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[testoptions]
command_line_args=--no-standalone
17 changes: 17 additions & 0 deletions tests/pyreverse/test_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"show_stdlib": False,
"only_classnames": False,
"output_directory": "",
"no_standalone": False,
}

TEST_DATA_DIR = os.path.join(os.path.dirname(__file__), "..", "data")
Expand All @@ -45,6 +46,7 @@
COLORIZED_PUML_FILES = ["packages_colorized.puml", "classes_colorized.puml"]
MMD_FILES = ["packages_No_Name.mmd", "classes_No_Name.mmd"]
HTML_FILES = ["packages_No_Name.html", "classes_No_Name.html"]
NO_STANDALONE_FILES = ["no_standalone_classes.dot", "no_standalone_packages.dot"]


class Config:
Expand Down Expand Up @@ -87,6 +89,15 @@ def setup_colorized_dot(
yield from _setup(project, colorized_dot_config, writer)


@pytest.fixture()
def setup_no_standalone_dot(
no_standalone_dot_config: PyreverseConfig, get_project: GetProjectCallable
) -> Iterator[None]:
writer = DiagramWriter(no_standalone_dot_config)
project = get_project(TEST_DATA_DIR)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have to specify name here like above in setup_colorized_dot. Also you have to rename the files from no_standalone_classes.dot to classes_no_standalone.dot (same for package diagram).

yield from _setup(project, no_standalone_dot_config, writer)


@pytest.fixture()
def setup_puml(
puml_config: PyreverseConfig, get_project: GetProjectCallable
Expand Down Expand Up @@ -161,6 +172,12 @@ def test_colorized_dot_files(generated_file: str) -> None:
_assert_files_are_equal(generated_file)


@pytest.mark.usefixtures("setup_no_standalone_dot")
@pytest.mark.parametrize("generated_file", NO_STANDALONE_FILES)
def test_no_standalone_dot_files(generated_file: str) -> None:
_assert_files_are_equal(generated_file)


@pytest.mark.usefixtures("setup_puml")
@pytest.mark.parametrize("generated_file", PUML_FILES)
def test_puml_files(generated_file: str) -> None:
Expand Down