From 73f6d224eef1376df51144d2d4f2883db746fc28 Mon Sep 17 00:00:00 2001 From: Julfried Date: Mon, 11 Nov 2024 15:57:11 +0100 Subject: [PATCH 1/5] Add command line option to limit depth --- pylint/pyreverse/main.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/pylint/pyreverse/main.py b/pylint/pyreverse/main.py index 972a46741f..7538f9b533 100644 --- a/pylint/pyreverse/main.py +++ b/pylint/pyreverse/main.py @@ -192,6 +192,22 @@ "help": "Use colored output. Classes/modules of the same package get the same color.", }, ), + ( + "max-depth", + { + "dest": "max_depth", + "action": "store", + "default": None, + "metavar": "", + "type": "int", + "group": OPTIONS_GROUPS["DISPLAY"], + "help": ( + "Maximum depth in package/module hierarchy to display. A depth of 0 shows only " + "top-level packages, 1 shows one level of subpackages, etc. If not specified, " + "all packages/modules are shown." + ), + }, + ), ( "max-color-depth", { From 50844ea774696de167874979638c84834c549f5f Mon Sep 17 00:00:00 2001 From: Julfried Date: Mon, 11 Nov 2024 16:27:40 +0100 Subject: [PATCH 2/5] Update writer to only show modules below max depth setting --- pylint/pyreverse/writer.py | 45 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/pylint/pyreverse/writer.py b/pylint/pyreverse/writer.py index 093c459598..efd6723696 100644 --- a/pylint/pyreverse/writer.py +++ b/pylint/pyreverse/writer.py @@ -35,6 +35,7 @@ def __init__(self, config: argparse.Namespace) -> None: self.printer: Printer # defined in set_printer self.file_name = "" # defined in set_printer self.depth = self.config.max_color_depth + self.max_depth = self.config.max_depth # default colors are an adaption of the seaborn colorblind palette self.available_colors = itertools.cycle(self.config.color_palette) self.used_colors: dict[str, str] = {} @@ -53,6 +54,15 @@ def write(self, diadefs: Iterable[ClassDiagram | PackageDiagram]) -> None: self.write_classes(diagram) self.save() + def should_show_node(self, qualified_name: str) -> bool: + """Determine if a node should be shown based on depth settings.""" + # If no depth limit is set ==> show all nodes + if self.max_depth is None: + return True + # Count dots to determine depth, subtract 1 since root counts as depth 0 + node_depth = qualified_name.count(".") + return bool(node_depth <= self.max_depth) + def write_packages(self, diagram: PackageDiagram) -> None: """Write a package diagram.""" module_info: dict[str, dict[str, int]] = {} @@ -61,6 +71,10 @@ def write_packages(self, diagram: PackageDiagram) -> None: for module in sorted(diagram.modules(), key=lambda x: x.title): module.fig_id = module.node.qname() + # Filter nodes based on depth + if not self.should_show_node(module.fig_id): + continue + if self.config.no_standalone and not any( module in (rel.from_object, rel.to_object) for rel in diagram.get_relationships("depends") @@ -83,6 +97,10 @@ def write_packages(self, diagram: PackageDiagram) -> None: from_id = rel.from_object.fig_id to_id = rel.to_object.fig_id + # Filter nodes based on depth ==> skip if either source or target nodes is beyond the max depth + if not self.should_show_node(from_id) or not self.should_show_node(to_id): + continue + self.printer.emit_edge( from_id, to_id, @@ -96,6 +114,10 @@ def write_packages(self, diagram: PackageDiagram) -> None: from_id = rel.from_object.fig_id to_id = rel.to_object.fig_id + # Filter nodes based on depth ==> skip if either source or target nodes is beyond the max depth + if not self.should_show_node(from_id) or not self.should_show_node(to_id): + continue + self.printer.emit_edge( from_id, to_id, @@ -115,6 +137,11 @@ 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): obj.fig_id = obj.node.qname() + + # Filter class based on depth setting + if not self.should_show_node(obj.fig_id): + continue + if self.config.no_standalone and not any( obj in (rel.from_object, rel.to_object) for rel_type in ("specialization", "association", "aggregation") @@ -129,6 +156,12 @@ def write_classes(self, diagram: ClassDiagram) -> None: ) # inheritance links for rel in diagram.get_relationships("specialization"): + # Filter nodes based on depth setting + if not self.should_show_node( + rel.from_object.fig_id + ) or not self.should_show_node(rel.to_object.fig_id): + continue + self.printer.emit_edge( rel.from_object.fig_id, rel.to_object.fig_id, @@ -137,6 +170,12 @@ def write_classes(self, diagram: ClassDiagram) -> None: associations: dict[str, set[str]] = defaultdict(set) # generate associations for rel in diagram.get_relationships("association"): + # Filter nodes based on depth setting + if not self.should_show_node( + rel.from_object.fig_id + ) or not self.should_show_node(rel.to_object.fig_id): + continue + associations[rel.from_object.fig_id].add(rel.to_object.fig_id) self.printer.emit_edge( rel.from_object.fig_id, @@ -146,6 +185,12 @@ def write_classes(self, diagram: ClassDiagram) -> None: ) # generate aggregations for rel in diagram.get_relationships("aggregation"): + # Filter nodes based on depth setting + if not self.should_show_node( + rel.from_object.fig_id + ) or not self.should_show_node(rel.to_object.fig_id): + continue + if rel.to_object.fig_id in associations[rel.from_object.fig_id]: continue self.printer.emit_edge( From ccc801c075caaa563fe70729128662b822eb5f0b Mon Sep 17 00:00:00 2001 From: Julfried Date: Mon, 11 Nov 2024 16:39:16 +0100 Subject: [PATCH 3/5] Update PyreverseConfig in testutils --- pylint/testutils/pyreverse.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pylint/testutils/pyreverse.py b/pylint/testutils/pyreverse.py index 115eda416a..eddb8ab5bb 100644 --- a/pylint/testutils/pyreverse.py +++ b/pylint/testutils/pyreverse.py @@ -23,6 +23,7 @@ class PyreverseConfig( The default values correspond to the defaults of the options' parser. """ + # pylint: disable=too-many-locals def __init__( self, *, @@ -40,6 +41,7 @@ def __init__( output_format: str = "dot", colorized: bool = False, max_color_depth: int = 2, + max_depth: int | None = None, color_palette: tuple[str, ...] = DEFAULT_COLOR_PALETTE, ignore_list: tuple[str, ...] = tuple(), project: str = "", @@ -62,12 +64,15 @@ def __init__( self.only_classnames = only_classnames self.output_format = output_format self.colorized = colorized + self.max_depth = max_depth self.max_color_depth = max_color_depth self.color_palette = color_palette self.ignore_list = ignore_list self.project = project self.output_directory = output_directory + # pylint: enable=too-many-locals + class TestFileOptions(TypedDict): source_roots: list[str] From 779dd53a4c89a8c52f333fe3d2e9099e99bc706d Mon Sep 17 00:00:00 2001 From: Julfried Date: Mon, 11 Nov 2024 16:54:57 +0100 Subject: [PATCH 4/5] Improve depth logic for classes --- pylint/pyreverse/writer.py | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/pylint/pyreverse/writer.py b/pylint/pyreverse/writer.py index efd6723696..b88c056e21 100644 --- a/pylint/pyreverse/writer.py +++ b/pylint/pyreverse/writer.py @@ -54,12 +54,26 @@ def write(self, diadefs: Iterable[ClassDiagram | PackageDiagram]) -> None: self.write_classes(diagram) self.save() - def should_show_node(self, qualified_name: str) -> bool: + def should_show_node(self, qualified_name: str, is_class: bool = False) -> bool: """Determine if a node should be shown based on depth settings.""" # If no depth limit is set ==> show all nodes if self.max_depth is None: return True - # Count dots to determine depth, subtract 1 since root counts as depth 0 + + # For classes, we want to measure depth from their containing module + if is_class: + # Get the module part (everything before the last dot) + last_dot = qualified_name.rfind(".") + if last_dot == -1: + module_path = "" + else: + module_path = qualified_name[:last_dot] + + # Count module depth + module_depth = module_path.count(".") + return bool(module_depth <= self.max_depth) + + # For packages/modules, count full depth node_depth = qualified_name.count(".") return bool(node_depth <= self.max_depth) @@ -139,7 +153,7 @@ def write_classes(self, diagram: ClassDiagram) -> None: obj.fig_id = obj.node.qname() # Filter class based on depth setting - if not self.should_show_node(obj.fig_id): + if not self.should_show_node(obj.fig_id, is_class=True): continue if self.config.no_standalone and not any( @@ -158,8 +172,8 @@ def write_classes(self, diagram: ClassDiagram) -> None: for rel in diagram.get_relationships("specialization"): # Filter nodes based on depth setting if not self.should_show_node( - rel.from_object.fig_id - ) or not self.should_show_node(rel.to_object.fig_id): + rel.from_object.fig_id, is_class=True + ) or not self.should_show_node(rel.to_object.fig_id, is_class=True): continue self.printer.emit_edge( @@ -172,8 +186,8 @@ def write_classes(self, diagram: ClassDiagram) -> None: for rel in diagram.get_relationships("association"): # Filter nodes based on depth setting if not self.should_show_node( - rel.from_object.fig_id - ) or not self.should_show_node(rel.to_object.fig_id): + rel.from_object.fig_id, is_class=True + ) or not self.should_show_node(rel.to_object.fig_id, is_class=True): continue associations[rel.from_object.fig_id].add(rel.to_object.fig_id) @@ -187,8 +201,8 @@ def write_classes(self, diagram: ClassDiagram) -> None: for rel in diagram.get_relationships("aggregation"): # Filter nodes based on depth setting if not self.should_show_node( - rel.from_object.fig_id - ) or not self.should_show_node(rel.to_object.fig_id): + rel.from_object.fig_id, is_class=True + ) or not self.should_show_node(rel.to_object.fig_id, is_class=True): continue if rel.to_object.fig_id in associations[rel.from_object.fig_id]: From 7c95c20b5b1cb80bc958c8d34226b7c3d06b498e Mon Sep 17 00:00:00 2001 From: Julfried Date: Mon, 11 Nov 2024 16:59:27 +0100 Subject: [PATCH 5/5] Update docs --- doc/additional_tools/pyreverse/configuration.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc/additional_tools/pyreverse/configuration.rst b/doc/additional_tools/pyreverse/configuration.rst index 845386e533..f980e1ac4f 100644 --- a/doc/additional_tools/pyreverse/configuration.rst +++ b/doc/additional_tools/pyreverse/configuration.rst @@ -115,6 +115,13 @@ Display Options **Default:** ``2`` +--max-depth +----------- +*Maximum depth in package/module hierarchy to display. A depth of 0 shows only top-level packages, 1 shows one level of subpackages, etc. If not specified, all packages/modules are shown.* + +**Default:** ``None`` + + --module-names -------------- *Include module name in the representation of classes.*