From bec61ff10bf6e15bdb92812713038cbbeb535328 Mon Sep 17 00:00:00 2001 From: Nick Drozd Date: Tue, 4 Jul 2023 14:49:57 -0400 Subject: [PATCH 01/18] Pyreverse: Use empty arrows for type-checking imports --- pylint/pyreverse/diagrams.py | 23 +++++++++++++++++++---- pylint/pyreverse/dot_printer.py | 1 + pylint/pyreverse/inspector.py | 1 + pylint/pyreverse/printer.py | 1 + pylint/pyreverse/writer.py | 7 +++++++ 5 files changed, 29 insertions(+), 4 deletions(-) diff --git a/pylint/pyreverse/diagrams.py b/pylint/pyreverse/diagrams.py index bc2ba18c5b..d99e6dacc0 100644 --- a/pylint/pyreverse/diagrams.py +++ b/pylint/pyreverse/diagrams.py @@ -12,7 +12,7 @@ import astroid from astroid import nodes, util -from pylint.checkers.utils import decorated_with_property +from pylint.checkers.utils import decorated_with_property, in_type_checking_block from pylint.pyreverse.utils import FilterMixIn @@ -281,9 +281,17 @@ def get_module(self, name: str, node: nodes.Module) -> PackageEntity: def add_from_depend(self, node: nodes.ImportFrom, from_module: str) -> None: """Add dependencies created by from-imports.""" mod_name = node.root().name - obj = self.module(mod_name) - if from_module not in obj.node.depends: - obj.node.depends.append(from_module) + package = self.module(mod_name).node + + if not in_type_checking_block(node): + if from_module not in package.depends: + package.depends.append(from_module) + else: + if ( + from_module not in package.type_depends + and from_module not in package.depends + ): + package.type_depends.append(from_module) def extract_relationships(self) -> None: """Extract relationships between nodes in the diagram.""" @@ -304,3 +312,10 @@ def extract_relationships(self) -> None: except KeyError: continue self.add_relationship(package_obj, dep, "depends") + + for dep_name in package_obj.node.type_depends: + try: + dep = self.get_module(dep_name, package_obj.node) + except KeyError: + continue + self.add_relationship(package_obj, dep, "type_depends") diff --git a/pylint/pyreverse/dot_printer.py b/pylint/pyreverse/dot_printer.py index edaea2384e..dd3ad72944 100644 --- a/pylint/pyreverse/dot_printer.py +++ b/pylint/pyreverse/dot_printer.py @@ -43,6 +43,7 @@ class HTMLLabels(Enum): "style": "solid", }, EdgeType.USES: {"arrowtail": "none", "arrowhead": "open"}, + EdgeType.TYPE_USES: {"arrowtail": "none", "arrowhead": "empty"}, } diff --git a/pylint/pyreverse/inspector.py b/pylint/pyreverse/inspector.py index 0cabe94733..df5c7b601d 100644 --- a/pylint/pyreverse/inspector.py +++ b/pylint/pyreverse/inspector.py @@ -139,6 +139,7 @@ def visit_module(self, node: nodes.Module) -> None: return node.locals_type = collections.defaultdict(list) node.depends = [] + node.type_depends = [] if self.tag: node.uid = self.generate_id() diff --git a/pylint/pyreverse/printer.py b/pylint/pyreverse/printer.py index f08c746024..a9658e96de 100644 --- a/pylint/pyreverse/printer.py +++ b/pylint/pyreverse/printer.py @@ -25,6 +25,7 @@ class EdgeType(Enum): ASSOCIATION = "association" AGGREGATION = "aggregation" USES = "uses" + TYPE_USES = "type_uses" class Layout(Enum): diff --git a/pylint/pyreverse/writer.py b/pylint/pyreverse/writer.py index 91190c0669..02dbe7b969 100644 --- a/pylint/pyreverse/writer.py +++ b/pylint/pyreverse/writer.py @@ -76,6 +76,13 @@ def write_packages(self, diagram: PackageDiagram) -> None: type_=EdgeType.USES, ) + for rel in diagram.get_relationships("type_depends"): + self.printer.emit_edge( + rel.from_object.fig_id, + rel.to_object.fig_id, + type_=EdgeType.TYPE_USES, + ) + def write_classes(self, diagram: ClassDiagram) -> None: """Write a class diagram.""" # sorted to get predictable (hence testable) results From 1a2b998d62d9c943dc8d07ca4e16854933243aa7 Mon Sep 17 00:00:00 2001 From: Nick Drozd Date: Tue, 4 Jul 2023 16:33:47 -0400 Subject: [PATCH 02/18] Use dashed lines instead of empty arrows --- pylint/pyreverse/dot_printer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pylint/pyreverse/dot_printer.py b/pylint/pyreverse/dot_printer.py index dd3ad72944..7035951295 100644 --- a/pylint/pyreverse/dot_printer.py +++ b/pylint/pyreverse/dot_printer.py @@ -43,7 +43,7 @@ class HTMLLabels(Enum): "style": "solid", }, EdgeType.USES: {"arrowtail": "none", "arrowhead": "open"}, - EdgeType.TYPE_USES: {"arrowtail": "none", "arrowhead": "empty"}, + EdgeType.TYPE_USES: {"arrowtail": "none", "arrowhead": "open", "style": "dashed"}, } From 614354ebb1d2e361baf08ceee6426f0361b4add4 Mon Sep 17 00:00:00 2001 From: Nick Drozd Date: Wed, 5 Jul 2023 10:47:13 -0400 Subject: [PATCH 03/18] Simplify branch logic --- pylint/pyreverse/diagrams.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/pylint/pyreverse/diagrams.py b/pylint/pyreverse/diagrams.py index d99e6dacc0..767fed710d 100644 --- a/pylint/pyreverse/diagrams.py +++ b/pylint/pyreverse/diagrams.py @@ -283,15 +283,13 @@ def add_from_depend(self, node: nodes.ImportFrom, from_module: str) -> None: mod_name = node.root().name package = self.module(mod_name).node + if from_module in package.depends: + return + if not in_type_checking_block(node): - if from_module not in package.depends: - package.depends.append(from_module) - else: - if ( - from_module not in package.type_depends - and from_module not in package.depends - ): - package.type_depends.append(from_module) + package.depends.append(from_module) + elif from_module not in package.type_depends: + package.type_depends.append(from_module) def extract_relationships(self) -> None: """Extract relationships between nodes in the diagram.""" From 95663f9c27fe85c534ebc811fb9c08dc460e937a Mon Sep 17 00:00:00 2001 From: Nick Drozd Date: Wed, 5 Jul 2023 11:27:34 -0400 Subject: [PATCH 04/18] Start test --- .../pyreverse/data/classes_type_check_imports.dot | 4 ++++ .../pyreverse/data/packages_type_check_imports.dot | 12 ++++++++++++ .../class_diagrams/type_check_imports/__init__.py | 0 .../class_diagrams/type_check_imports/mod_a.py | 3 +++ .../class_diagrams/type_check_imports/mod_b.py | 6 ++++++ .../class_diagrams/type_check_imports/mod_c.py | 7 +++++++ .../class_diagrams/type_check_imports/mod_d.py | 9 +++++++++ .../class_diagrams/type_check_imports/packages.mmd | 14 ++++++++++++++ 8 files changed, 55 insertions(+) create mode 100644 tests/pyreverse/data/classes_type_check_imports.dot create mode 100644 tests/pyreverse/data/packages_type_check_imports.dot create mode 100644 tests/pyreverse/functional/class_diagrams/type_check_imports/__init__.py create mode 100644 tests/pyreverse/functional/class_diagrams/type_check_imports/mod_a.py create mode 100644 tests/pyreverse/functional/class_diagrams/type_check_imports/mod_b.py create mode 100644 tests/pyreverse/functional/class_diagrams/type_check_imports/mod_c.py create mode 100644 tests/pyreverse/functional/class_diagrams/type_check_imports/mod_d.py create mode 100644 tests/pyreverse/functional/class_diagrams/type_check_imports/packages.mmd diff --git a/tests/pyreverse/data/classes_type_check_imports.dot b/tests/pyreverse/data/classes_type_check_imports.dot new file mode 100644 index 0000000000..68c50c9608 --- /dev/null +++ b/tests/pyreverse/data/classes_type_check_imports.dot @@ -0,0 +1,4 @@ +digraph "classes" { +rankdir=BT +charset="utf-8" +} diff --git a/tests/pyreverse/data/packages_type_check_imports.dot b/tests/pyreverse/data/packages_type_check_imports.dot new file mode 100644 index 0000000000..01f5ac7f85 --- /dev/null +++ b/tests/pyreverse/data/packages_type_check_imports.dot @@ -0,0 +1,12 @@ +digraph "packages" { +rankdir=BT +charset="utf-8" +"type_check_imports" [color="black", label=, shape="box", style="solid"]; +"type_check_imports.mod_a" [color="black", label=, shape="box", style="solid"]; +"type_check_imports.mod_b" [color="black", label=, shape="box", style="solid"]; +"type_check_imports.mod_c" [color="black", label=, shape="box", style="solid"]; +"type_check_imports.mod_d" [color="black", label=, shape="box", style="solid"]; +"type_check_imports.mod_b" -> "type_check_imports.mod_a" [arrowhead="open", arrowtail="none"]; +"type_check_imports.mod_d" -> "type_check_imports.mod_a" [arrowhead="open", arrowtail="none"]; +"type_check_imports.mod_c" -> "type_check_imports.mod_a" [arrowhead="open", arrowtail="none", style="dashed"]; +} diff --git a/tests/pyreverse/functional/class_diagrams/type_check_imports/__init__.py b/tests/pyreverse/functional/class_diagrams/type_check_imports/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/pyreverse/functional/class_diagrams/type_check_imports/mod_a.py b/tests/pyreverse/functional/class_diagrams/type_check_imports/mod_a.py new file mode 100644 index 0000000000..bceec8d697 --- /dev/null +++ b/tests/pyreverse/functional/class_diagrams/type_check_imports/mod_a.py @@ -0,0 +1,3 @@ +Int = int + +List = list diff --git a/tests/pyreverse/functional/class_diagrams/type_check_imports/mod_b.py b/tests/pyreverse/functional/class_diagrams/type_check_imports/mod_b.py new file mode 100644 index 0000000000..5aec3d5355 --- /dev/null +++ b/tests/pyreverse/functional/class_diagrams/type_check_imports/mod_b.py @@ -0,0 +1,6 @@ +from typing import Any + +from mod_a import Int + +def is_int(x) -> bool: + return isinstance(x, Int) diff --git a/tests/pyreverse/functional/class_diagrams/type_check_imports/mod_c.py b/tests/pyreverse/functional/class_diagrams/type_check_imports/mod_c.py new file mode 100644 index 0000000000..bf4a46dcb1 --- /dev/null +++ b/tests/pyreverse/functional/class_diagrams/type_check_imports/mod_c.py @@ -0,0 +1,7 @@ +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from mod_a import Int + +def some_int() -> Int: + return 5 diff --git a/tests/pyreverse/functional/class_diagrams/type_check_imports/mod_d.py b/tests/pyreverse/functional/class_diagrams/type_check_imports/mod_d.py new file mode 100644 index 0000000000..a8537f55b5 --- /dev/null +++ b/tests/pyreverse/functional/class_diagrams/type_check_imports/mod_d.py @@ -0,0 +1,9 @@ +from typing import Any, TYPE_CHECKING + +from mod_a import Int + +if TYPE_CHECKING: + from mod_a import List + +def list_int(x: Any) -> List[Int]: + return [x] if isinstance(x, Int) else [] diff --git a/tests/pyreverse/functional/class_diagrams/type_check_imports/packages.mmd b/tests/pyreverse/functional/class_diagrams/type_check_imports/packages.mmd new file mode 100644 index 0000000000..91f0e65f73 --- /dev/null +++ b/tests/pyreverse/functional/class_diagrams/type_check_imports/packages.mmd @@ -0,0 +1,14 @@ +classDiagram + class type_check_imports { + } + class mod_a { + } + class mod_b { + } + class mod_c { + } + class mod_d { + } + mod_b --> mod_a + mod_d --> mod_a + mod_c -.-> mod_a From 458a7370a73c2a705a4480e22e34f5680f821fef Mon Sep 17 00:00:00 2001 From: Nick Drozd Date: Wed, 5 Jul 2023 11:27:45 -0400 Subject: [PATCH 05/18] Add mermaid writer --- pylint/pyreverse/mermaidjs_printer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pylint/pyreverse/mermaidjs_printer.py b/pylint/pyreverse/mermaidjs_printer.py index 61d0d79486..ae48cc3f29 100644 --- a/pylint/pyreverse/mermaidjs_printer.py +++ b/pylint/pyreverse/mermaidjs_printer.py @@ -24,6 +24,7 @@ class MermaidJSPrinter(Printer): EdgeType.ASSOCIATION: "--*", EdgeType.AGGREGATION: "--o", EdgeType.USES: "-->", + EdgeType.TYPE_USES: "-.->", } def _open_graph(self) -> None: From 37010409ceca4b9535afad4e8aba9bb2fcff6d7b Mon Sep 17 00:00:00 2001 From: Nick Drozd Date: Wed, 5 Jul 2023 11:34:57 -0400 Subject: [PATCH 06/18] move test data --- .../type_check_imports/__init__.py | 0 .../type_check_imports/mod_a.py | 0 .../type_check_imports/mod_b.py | 0 .../type_check_imports/mod_c.py | 0 .../type_check_imports/mod_d.py | 0 .../type_check_imports/packages.mmd | 0 tests/pyreverse/test_writer.py | 1 + 7 files changed, 1 insertion(+) rename tests/pyreverse/functional/{class_diagrams => package_diagrams}/type_check_imports/__init__.py (100%) rename tests/pyreverse/functional/{class_diagrams => package_diagrams}/type_check_imports/mod_a.py (100%) rename tests/pyreverse/functional/{class_diagrams => package_diagrams}/type_check_imports/mod_b.py (100%) rename tests/pyreverse/functional/{class_diagrams => package_diagrams}/type_check_imports/mod_c.py (100%) rename tests/pyreverse/functional/{class_diagrams => package_diagrams}/type_check_imports/mod_d.py (100%) rename tests/pyreverse/functional/{class_diagrams => package_diagrams}/type_check_imports/packages.mmd (100%) diff --git a/tests/pyreverse/functional/class_diagrams/type_check_imports/__init__.py b/tests/pyreverse/functional/package_diagrams/type_check_imports/__init__.py similarity index 100% rename from tests/pyreverse/functional/class_diagrams/type_check_imports/__init__.py rename to tests/pyreverse/functional/package_diagrams/type_check_imports/__init__.py diff --git a/tests/pyreverse/functional/class_diagrams/type_check_imports/mod_a.py b/tests/pyreverse/functional/package_diagrams/type_check_imports/mod_a.py similarity index 100% rename from tests/pyreverse/functional/class_diagrams/type_check_imports/mod_a.py rename to tests/pyreverse/functional/package_diagrams/type_check_imports/mod_a.py diff --git a/tests/pyreverse/functional/class_diagrams/type_check_imports/mod_b.py b/tests/pyreverse/functional/package_diagrams/type_check_imports/mod_b.py similarity index 100% rename from tests/pyreverse/functional/class_diagrams/type_check_imports/mod_b.py rename to tests/pyreverse/functional/package_diagrams/type_check_imports/mod_b.py diff --git a/tests/pyreverse/functional/class_diagrams/type_check_imports/mod_c.py b/tests/pyreverse/functional/package_diagrams/type_check_imports/mod_c.py similarity index 100% rename from tests/pyreverse/functional/class_diagrams/type_check_imports/mod_c.py rename to tests/pyreverse/functional/package_diagrams/type_check_imports/mod_c.py diff --git a/tests/pyreverse/functional/class_diagrams/type_check_imports/mod_d.py b/tests/pyreverse/functional/package_diagrams/type_check_imports/mod_d.py similarity index 100% rename from tests/pyreverse/functional/class_diagrams/type_check_imports/mod_d.py rename to tests/pyreverse/functional/package_diagrams/type_check_imports/mod_d.py diff --git a/tests/pyreverse/functional/class_diagrams/type_check_imports/packages.mmd b/tests/pyreverse/functional/package_diagrams/type_check_imports/packages.mmd similarity index 100% rename from tests/pyreverse/functional/class_diagrams/type_check_imports/packages.mmd rename to tests/pyreverse/functional/package_diagrams/type_check_imports/packages.mmd diff --git a/tests/pyreverse/test_writer.py b/tests/pyreverse/test_writer.py index ed0f5b31ee..fb3d15836f 100644 --- a/tests/pyreverse/test_writer.py +++ b/tests/pyreverse/test_writer.py @@ -49,6 +49,7 @@ MMD_FILES = ["packages_No_Name.mmd", "classes_No_Name.mmd"] HTML_FILES = ["packages_No_Name.html", "classes_No_Name.html"] NO_STANDALONE_FILES = ["classes_no_standalone.dot", "packages_no_standalone.dot"] +TYPE_CHECK_IMPORTS_FILES = ["packages_type_check_imports.dot", "classes_type_check_imports.dot"] class Config: From 3bb5b2c4397ca1289ab6158bb556679f7c9cc0bd Mon Sep 17 00:00:00 2001 From: Nick Drozd Date: Wed, 5 Jul 2023 12:25:00 -0400 Subject: [PATCH 07/18] add config fixtures --- tests/pyreverse/conftest.py | 7 +++++++ tests/pyreverse/test_writer.py | 15 +++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/tests/pyreverse/conftest.py b/tests/pyreverse/conftest.py index 9e1741f0a8..3225c88a8c 100644 --- a/tests/pyreverse/conftest.py +++ b/tests/pyreverse/conftest.py @@ -36,6 +36,13 @@ def no_standalone_dot_config() -> PyreverseConfig: ) +@pytest.fixture() +def type_check_imports_dot_config() -> PyreverseConfig: + return PyreverseConfig( + output_format="dot", + ) + + @pytest.fixture() def puml_config() -> PyreverseConfig: return PyreverseConfig( diff --git a/tests/pyreverse/test_writer.py b/tests/pyreverse/test_writer.py index fb3d15836f..90922d634c 100644 --- a/tests/pyreverse/test_writer.py +++ b/tests/pyreverse/test_writer.py @@ -106,6 +106,15 @@ def setup_no_standalone_dot( yield from _setup(project, no_standalone_dot_config, writer) +@pytest.fixture() +def setup_type_check_imports_dot( + type_check_imports_dot_config: PyreverseConfig, get_project: GetProjectCallable +) -> Iterator[None]: + writer = DiagramWriter(type_check_imports_dot_config) + project = get_project(TEST_DATA_DIR, name="type_check_imports") + yield from _setup(project, type_check_imports_dot_config, writer) + + @pytest.fixture() def setup_puml( puml_config: PyreverseConfig, get_project: GetProjectCallable @@ -174,6 +183,12 @@ def test_no_standalone_dot_files(generated_file: str) -> None: _assert_files_are_equal(generated_file) +@pytest.mark.usefixtures("setup_type_check_imports_dot") +@pytest.mark.parametrize("generated_file", TYPE_CHECK_IMPORTS_FILES) +def test_type_check_imports_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: From 59b5ba1907fd61f8d1177f26ea30b8d80ab0d00f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 5 Jul 2023 16:30:39 +0000 Subject: [PATCH 08/18] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../functional/package_diagrams/type_check_imports/mod_b.py | 1 + .../functional/package_diagrams/type_check_imports/mod_d.py | 2 +- tests/pyreverse/test_writer.py | 5 ++++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/pyreverse/functional/package_diagrams/type_check_imports/mod_b.py b/tests/pyreverse/functional/package_diagrams/type_check_imports/mod_b.py index 5aec3d5355..2fdc98b54c 100644 --- a/tests/pyreverse/functional/package_diagrams/type_check_imports/mod_b.py +++ b/tests/pyreverse/functional/package_diagrams/type_check_imports/mod_b.py @@ -2,5 +2,6 @@ from mod_a import Int + def is_int(x) -> bool: return isinstance(x, Int) diff --git a/tests/pyreverse/functional/package_diagrams/type_check_imports/mod_d.py b/tests/pyreverse/functional/package_diagrams/type_check_imports/mod_d.py index a8537f55b5..97be1417d2 100644 --- a/tests/pyreverse/functional/package_diagrams/type_check_imports/mod_d.py +++ b/tests/pyreverse/functional/package_diagrams/type_check_imports/mod_d.py @@ -1,4 +1,4 @@ -from typing import Any, TYPE_CHECKING +from typing import TYPE_CHECKING, Any from mod_a import Int diff --git a/tests/pyreverse/test_writer.py b/tests/pyreverse/test_writer.py index 90922d634c..a7b44fb5d9 100644 --- a/tests/pyreverse/test_writer.py +++ b/tests/pyreverse/test_writer.py @@ -49,7 +49,10 @@ MMD_FILES = ["packages_No_Name.mmd", "classes_No_Name.mmd"] HTML_FILES = ["packages_No_Name.html", "classes_No_Name.html"] NO_STANDALONE_FILES = ["classes_no_standalone.dot", "packages_no_standalone.dot"] -TYPE_CHECK_IMPORTS_FILES = ["packages_type_check_imports.dot", "classes_type_check_imports.dot"] +TYPE_CHECK_IMPORTS_FILES = [ + "packages_type_check_imports.dot", + "classes_type_check_imports.dot", +] class Config: From 611a3535e5b80533071e7c43ac97a94a8ebcd81a Mon Sep 17 00:00:00 2001 From: Nick Drozd Date: Wed, 5 Jul 2023 14:44:43 -0400 Subject: [PATCH 09/18] get test data from right place --- .../data/classes_type_check_imports.dot | 2 +- .../data/packages_type_check_imports.dot | 19 ++++++++++--------- .../functional/package_diagrams/__init__.py | 0 tests/pyreverse/test_writer.py | 5 ++++- 4 files changed, 15 insertions(+), 11 deletions(-) create mode 100644 tests/pyreverse/functional/package_diagrams/__init__.py diff --git a/tests/pyreverse/data/classes_type_check_imports.dot b/tests/pyreverse/data/classes_type_check_imports.dot index 68c50c9608..592d9716ca 100644 --- a/tests/pyreverse/data/classes_type_check_imports.dot +++ b/tests/pyreverse/data/classes_type_check_imports.dot @@ -1,4 +1,4 @@ -digraph "classes" { +digraph "classes_type_check_imports" { rankdir=BT charset="utf-8" } diff --git a/tests/pyreverse/data/packages_type_check_imports.dot b/tests/pyreverse/data/packages_type_check_imports.dot index 01f5ac7f85..e44ed82444 100644 --- a/tests/pyreverse/data/packages_type_check_imports.dot +++ b/tests/pyreverse/data/packages_type_check_imports.dot @@ -1,12 +1,13 @@ -digraph "packages" { +digraph "packages_type_check_imports" { rankdir=BT charset="utf-8" -"type_check_imports" [color="black", label=, shape="box", style="solid"]; -"type_check_imports.mod_a" [color="black", label=, shape="box", style="solid"]; -"type_check_imports.mod_b" [color="black", label=, shape="box", style="solid"]; -"type_check_imports.mod_c" [color="black", label=, shape="box", style="solid"]; -"type_check_imports.mod_d" [color="black", label=, shape="box", style="solid"]; -"type_check_imports.mod_b" -> "type_check_imports.mod_a" [arrowhead="open", arrowtail="none"]; -"type_check_imports.mod_d" -> "type_check_imports.mod_a" [arrowhead="open", arrowtail="none"]; -"type_check_imports.mod_c" -> "type_check_imports.mod_a" [arrowhead="open", arrowtail="none", style="dashed"]; +"package_diagrams" [color="black", label=, shape="box", style="solid"]; +"package_diagrams.type_check_imports" [color="black", label=, shape="box", style="solid"]; +"package_diagrams.type_check_imports.mod_a" [color="black", label=, shape="box", style="solid"]; +"package_diagrams.type_check_imports.mod_b" [color="black", label=, shape="box", style="solid"]; +"package_diagrams.type_check_imports.mod_c" [color="black", label=, shape="box", style="solid"]; +"package_diagrams.type_check_imports.mod_d" [color="black", label=, shape="box", style="solid"]; +"package_diagrams.type_check_imports.mod_b" -> "package_diagrams.type_check_imports.mod_a" [arrowhead="open", arrowtail="none"]; +"package_diagrams.type_check_imports.mod_d" -> "package_diagrams.type_check_imports.mod_a" [arrowhead="open", arrowtail="none"]; +"package_diagrams.type_check_imports.mod_c" -> "package_diagrams.type_check_imports.mod_a" [arrowhead="open", arrowtail="none", style="dashed"]; } diff --git a/tests/pyreverse/functional/package_diagrams/__init__.py b/tests/pyreverse/functional/package_diagrams/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/pyreverse/test_writer.py b/tests/pyreverse/test_writer.py index a7b44fb5d9..40b00e1d55 100644 --- a/tests/pyreverse/test_writer.py +++ b/tests/pyreverse/test_writer.py @@ -114,7 +114,10 @@ def setup_type_check_imports_dot( type_check_imports_dot_config: PyreverseConfig, get_project: GetProjectCallable ) -> Iterator[None]: writer = DiagramWriter(type_check_imports_dot_config) - project = get_project(TEST_DATA_DIR, name="type_check_imports") + project = get_project( + os.path.join(os.path.dirname(__file__), "functional", "package_diagrams"), + name="type_check_imports") + yield from _setup(project, type_check_imports_dot_config, writer) From dc3049705cf6f71277777b415d329f92977f8236 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 5 Jul 2023 18:49:56 +0000 Subject: [PATCH 10/18] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/pyreverse/test_writer.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/pyreverse/test_writer.py b/tests/pyreverse/test_writer.py index 40b00e1d55..2baa165f45 100644 --- a/tests/pyreverse/test_writer.py +++ b/tests/pyreverse/test_writer.py @@ -116,7 +116,8 @@ def setup_type_check_imports_dot( writer = DiagramWriter(type_check_imports_dot_config) project = get_project( os.path.join(os.path.dirname(__file__), "functional", "package_diagrams"), - name="type_check_imports") + name="type_check_imports", + ) yield from _setup(project, type_check_imports_dot_config, writer) From 5dd383176a77a671a0e1221e99e005e735c76fab Mon Sep 17 00:00:00 2001 From: Nick Drozd Date: Thu, 6 Jul 2023 08:52:56 -0400 Subject: [PATCH 11/18] Cut KeyError check --- pylint/pyreverse/diagrams.py | 5 +---- .../functional/package_diagrams/type_check_imports/mod_d.py | 3 ++- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/pylint/pyreverse/diagrams.py b/pylint/pyreverse/diagrams.py index 767fed710d..32273054bb 100644 --- a/pylint/pyreverse/diagrams.py +++ b/pylint/pyreverse/diagrams.py @@ -312,8 +312,5 @@ def extract_relationships(self) -> None: self.add_relationship(package_obj, dep, "depends") for dep_name in package_obj.node.type_depends: - try: - dep = self.get_module(dep_name, package_obj.node) - except KeyError: - continue + dep = self.get_module(dep_name, package_obj.node) self.add_relationship(package_obj, dep, "type_depends") diff --git a/tests/pyreverse/functional/package_diagrams/type_check_imports/mod_d.py b/tests/pyreverse/functional/package_diagrams/type_check_imports/mod_d.py index 97be1417d2..6027e73ce6 100644 --- a/tests/pyreverse/functional/package_diagrams/type_check_imports/mod_d.py +++ b/tests/pyreverse/functional/package_diagrams/type_check_imports/mod_d.py @@ -1,9 +1,10 @@ -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING from mod_a import Int if TYPE_CHECKING: from mod_a import List + from typing import Any def list_int(x: Any) -> List[Int]: return [x] if isinstance(x, Int) else [] From afb46322065bf53fb53cff33a90ed9a9a12dd732 Mon Sep 17 00:00:00 2001 From: Nick Drozd Date: Thu, 6 Jul 2023 08:56:40 -0400 Subject: [PATCH 12/18] Add news --- doc/whatsnew/fragments/8112.feature | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 doc/whatsnew/fragments/8112.feature diff --git a/doc/whatsnew/fragments/8112.feature b/doc/whatsnew/fragments/8112.feature new file mode 100644 index 0000000000..cee1f37a86 --- /dev/null +++ b/doc/whatsnew/fragments/8112.feature @@ -0,0 +1,3 @@ +In Pyreverse package dependency diagrams, show when a module imports another only for type-checking. + +Closes #8112 From b6d2cc5972094922d2de52388938202ecbb9ccd1 Mon Sep 17 00:00:00 2001 From: Nick Drozd Date: Thu, 6 Jul 2023 09:04:24 -0400 Subject: [PATCH 13/18] Add uml --- pylint/pyreverse/plantuml_printer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pylint/pyreverse/plantuml_printer.py b/pylint/pyreverse/plantuml_printer.py index 5f703b62eb..140848941f 100644 --- a/pylint/pyreverse/plantuml_printer.py +++ b/pylint/pyreverse/plantuml_printer.py @@ -24,6 +24,7 @@ class PlantUmlPrinter(Printer): EdgeType.ASSOCIATION: "--*", EdgeType.AGGREGATION: "--o", EdgeType.USES: "-->", + EdgeType.TYPE_USES: "..>", } def _open_graph(self) -> None: From 94c99bc27c4634a913b8e6563d34e9e47bf10b8b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 6 Jul 2023 13:06:37 +0000 Subject: [PATCH 14/18] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../functional/package_diagrams/type_check_imports/mod_d.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/pyreverse/functional/package_diagrams/type_check_imports/mod_d.py b/tests/pyreverse/functional/package_diagrams/type_check_imports/mod_d.py index 6027e73ce6..af61065db1 100644 --- a/tests/pyreverse/functional/package_diagrams/type_check_imports/mod_d.py +++ b/tests/pyreverse/functional/package_diagrams/type_check_imports/mod_d.py @@ -3,8 +3,9 @@ from mod_a import Int if TYPE_CHECKING: - from mod_a import List from typing import Any + from mod_a import List + def list_int(x: Any) -> List[Int]: return [x] if isinstance(x, Int) else [] From 49d5e25fdba9a52c70ff6ca37d85e9f7975cec5f Mon Sep 17 00:00:00 2001 From: Nick Drozd Date: Thu, 6 Jul 2023 09:38:21 -0400 Subject: [PATCH 15/18] Bring back KeyError check --- pylint/pyreverse/diagrams.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pylint/pyreverse/diagrams.py b/pylint/pyreverse/diagrams.py index 32273054bb..767fed710d 100644 --- a/pylint/pyreverse/diagrams.py +++ b/pylint/pyreverse/diagrams.py @@ -312,5 +312,8 @@ def extract_relationships(self) -> None: self.add_relationship(package_obj, dep, "depends") for dep_name in package_obj.node.type_depends: - dep = self.get_module(dep_name, package_obj.node) + try: + dep = self.get_module(dep_name, package_obj.node) + except KeyError: + continue self.add_relationship(package_obj, dep, "type_depends") From 20d47fe9c859f4addc0d14b27e2cdd674f8d913e Mon Sep 17 00:00:00 2001 From: Nick Drozd Date: Fri, 7 Jul 2023 09:28:13 -0400 Subject: [PATCH 16/18] Skip KeyError coverage --- pylint/pyreverse/diagrams.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pylint/pyreverse/diagrams.py b/pylint/pyreverse/diagrams.py index 767fed710d..41db5ff5a9 100644 --- a/pylint/pyreverse/diagrams.py +++ b/pylint/pyreverse/diagrams.py @@ -314,6 +314,6 @@ def extract_relationships(self) -> None: for dep_name in package_obj.node.type_depends: try: dep = self.get_module(dep_name, package_obj.node) - except KeyError: + except KeyError: # pragma: no cover continue self.add_relationship(package_obj, dep, "type_depends") From 40570672c1f291136fc83e692cc7573b20ef5f71 Mon Sep 17 00:00:00 2001 From: Nick Drozd Date: Fri, 7 Jul 2023 14:42:31 -0400 Subject: [PATCH 17/18] Rename type dependency --- pylint/pyreverse/dot_printer.py | 6 +++++- pylint/pyreverse/mermaidjs_printer.py | 2 +- pylint/pyreverse/plantuml_printer.py | 2 +- pylint/pyreverse/printer.py | 2 +- pylint/pyreverse/writer.py | 2 +- 5 files changed, 9 insertions(+), 5 deletions(-) diff --git a/pylint/pyreverse/dot_printer.py b/pylint/pyreverse/dot_printer.py index 7035951295..ddd895d309 100644 --- a/pylint/pyreverse/dot_printer.py +++ b/pylint/pyreverse/dot_printer.py @@ -43,7 +43,11 @@ class HTMLLabels(Enum): "style": "solid", }, EdgeType.USES: {"arrowtail": "none", "arrowhead": "open"}, - EdgeType.TYPE_USES: {"arrowtail": "none", "arrowhead": "open", "style": "dashed"}, + EdgeType.TYPE_DEPENDENCY: { + "arrowtail": "none", + "arrowhead": "open", + "style": "dashed", + }, } diff --git a/pylint/pyreverse/mermaidjs_printer.py b/pylint/pyreverse/mermaidjs_printer.py index ae48cc3f29..b0b64e0b43 100644 --- a/pylint/pyreverse/mermaidjs_printer.py +++ b/pylint/pyreverse/mermaidjs_printer.py @@ -24,7 +24,7 @@ class MermaidJSPrinter(Printer): EdgeType.ASSOCIATION: "--*", EdgeType.AGGREGATION: "--o", EdgeType.USES: "-->", - EdgeType.TYPE_USES: "-.->", + EdgeType.TYPE_DEPENDENCY: "-.->", } def _open_graph(self) -> None: diff --git a/pylint/pyreverse/plantuml_printer.py b/pylint/pyreverse/plantuml_printer.py index 140848941f..379d57a4c6 100644 --- a/pylint/pyreverse/plantuml_printer.py +++ b/pylint/pyreverse/plantuml_printer.py @@ -24,7 +24,7 @@ class PlantUmlPrinter(Printer): EdgeType.ASSOCIATION: "--*", EdgeType.AGGREGATION: "--o", EdgeType.USES: "-->", - EdgeType.TYPE_USES: "..>", + EdgeType.TYPE_DEPENDENCY: "..>", } def _open_graph(self) -> None: diff --git a/pylint/pyreverse/printer.py b/pylint/pyreverse/printer.py index a9658e96de..caa7917ca0 100644 --- a/pylint/pyreverse/printer.py +++ b/pylint/pyreverse/printer.py @@ -25,7 +25,7 @@ class EdgeType(Enum): ASSOCIATION = "association" AGGREGATION = "aggregation" USES = "uses" - TYPE_USES = "type_uses" + TYPE_DEPENDENCY = "type_dependency" class Layout(Enum): diff --git a/pylint/pyreverse/writer.py b/pylint/pyreverse/writer.py index 02dbe7b969..5ef5420496 100644 --- a/pylint/pyreverse/writer.py +++ b/pylint/pyreverse/writer.py @@ -80,7 +80,7 @@ def write_packages(self, diagram: PackageDiagram) -> None: self.printer.emit_edge( rel.from_object.fig_id, rel.to_object.fig_id, - type_=EdgeType.TYPE_USES, + type_=EdgeType.TYPE_DEPENDENCY, ) def write_classes(self, diagram: ClassDiagram) -> None: From abcfcc27a1c1163c2818f77c84e6e6d001b9e731 Mon Sep 17 00:00:00 2001 From: Nick Drozd Date: Fri, 7 Jul 2023 14:45:24 -0400 Subject: [PATCH 18/18] Cut type_check_imports_dot_config --- tests/pyreverse/conftest.py | 7 ------- tests/pyreverse/test_writer.py | 6 +++--- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/tests/pyreverse/conftest.py b/tests/pyreverse/conftest.py index 3225c88a8c..9e1741f0a8 100644 --- a/tests/pyreverse/conftest.py +++ b/tests/pyreverse/conftest.py @@ -36,13 +36,6 @@ def no_standalone_dot_config() -> PyreverseConfig: ) -@pytest.fixture() -def type_check_imports_dot_config() -> PyreverseConfig: - return PyreverseConfig( - output_format="dot", - ) - - @pytest.fixture() def puml_config() -> PyreverseConfig: return PyreverseConfig( diff --git a/tests/pyreverse/test_writer.py b/tests/pyreverse/test_writer.py index 2baa165f45..d3a2ce5aae 100644 --- a/tests/pyreverse/test_writer.py +++ b/tests/pyreverse/test_writer.py @@ -111,15 +111,15 @@ def setup_no_standalone_dot( @pytest.fixture() def setup_type_check_imports_dot( - type_check_imports_dot_config: PyreverseConfig, get_project: GetProjectCallable + default_config: PyreverseConfig, get_project: GetProjectCallable ) -> Iterator[None]: - writer = DiagramWriter(type_check_imports_dot_config) + writer = DiagramWriter(default_config) project = get_project( os.path.join(os.path.dirname(__file__), "functional", "package_diagrams"), name="type_check_imports", ) - yield from _setup(project, type_check_imports_dot_config, writer) + yield from _setup(project, default_config, writer) @pytest.fixture()