From 3fca43aa15047ff62f775c27d0c3e85aaa935549 Mon Sep 17 00:00:00 2001 From: Julfried <51880314+Julfried@users.noreply.github.com> Date: Sat, 26 Oct 2024 01:29:18 +0200 Subject: [PATCH 01/12] Add comprehensive documentation for Pyreverse command-line options --- doc/pyreverse.rst | 86 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/doc/pyreverse.rst b/doc/pyreverse.rst index 7595683d75..d324080d38 100644 --- a/doc/pyreverse.rst +++ b/doc/pyreverse.rst @@ -23,6 +23,92 @@ To see a full list of the available options, run:: pyreverse -h +Command Line Options +.................... + +Pyreverse provides various options to control the output and appearance of your diagrams: + +Output Control +~~~~~~~~~~~~~~ + +``--output``, ``-o `` + Specify the output format (e.g., .dot, .puml, .plantuml, .mmd, .html). + Additional formats are available if Graphviz is installed (see `Graphviz output formats `_). + +``--output-directory``, ``-d `` + Set the output directory path for generated diagrams. + +``--project``, ``-p `` + Set the project name. + +Filtering and Scope +~~~~~~~~~~~~~~~~~~~ + +``--filter-mode``, ``-f `` + Control which class members are shown: + + * ``PUB_ONLY`` (default): Show only public attributes + * ``ALL``: Show all members + * ``SPECIAL``: Show all but Python special functions (except constructor) + * ``OTHER``: Show all but protected and private attributes + +``--class``, ``-c `` + Create a focused diagram showing a specific class and its relations. + Automatically enables -ASmy options. + +``--show-ancestors``, ``-a `` + Include specified number of ancestor generations. + +``--all-ancestors``, ``-A`` + Include all ancestor classes. + +``--show-associated``, ``-s `` + Include specified levels of associated classes. + +``--all-associated``, ``-S`` + Include all associated classes recursively. + +``--show-builtin``, ``-b`` + Include Python's builtin objects. + +``--show-stdlib``, ``-L`` + Include standard library objects. + +Display Options +~~~~~~~~~~~~~~~ + +``--module-names``, ``-m `` + Show or hide module names in class representations. + +``--only-classnames``, ``-k`` + Show only class names, hiding attributes and methods. + +``--no-standalone`` + Only show connected classes. + +``--colorized`` + Enable colored output. Classes/modules in the same package share colors. + +``--max-color-depth `` + Control color distinction by package depth (default: 2). + +``--color-palette `` + Specify custom colors for the diagram. + +Project Configuration +~~~~~~~~~~~~~~~~~~~~~ + +``--ignore `` + Exclude specific files or directories (use base names). + +``--source-roots [,...]>`` + Define source roots for package namespace resolution. + Supports glob patterns. + +``--verbose`` + Enable detailed output for debugging. + + Example Output '''''''''''''' From 248038b32630eceada98e70ab9ff7928c3f1a4b8 Mon Sep 17 00:00:00 2001 From: Julfried Date: Mon, 28 Oct 2024 17:48:32 +0100 Subject: [PATCH 02/12] Add the documentation directly to the code as requested --- pylint/pyreverse/main.py | 120 +++++++++++++++++++++++++-------------- 1 file changed, 77 insertions(+), 43 deletions(-) diff --git a/pylint/pyreverse/main.py b/pylint/pyreverse/main.py index 3ba0b6c77d..4c4352b71d 100644 --- a/pylint/pyreverse/main.py +++ b/pylint/pyreverse/main.py @@ -46,7 +46,17 @@ "#DDDDDD", # pale grey ) + +OPTIONS_GROUPS = { + "FILTERING": "Filtering and Scope", + "DISPLAY": "Display Options", + "OUTPUT": "Output Control", + "PROJECT": "Project Configuration", +} + + OPTIONS: Options = ( + # Filtering and Scope options ( "filter-mode", { @@ -56,7 +66,8 @@ "type": "string", "action": "store", "metavar": "", - "help": """filter attributes and functions according to + "group": OPTIONS_GROUPS["FILTERING"], + "help": """Filter attributes and functions according to . Correct modes are : 'PUB_ONLY' filter all non public attributes [DEFAULT], equivalent to PRIVATE+SPECIAL_A @@ -76,7 +87,8 @@ "type": "csv", "dest": "classes", "default": None, - "help": "create a class diagram with all classes related to ;\ + "group": OPTIONS_GROUPS["FILTERING"], + "help": "Create a class diagram with all classes related to ;\ this uses by default the options -ASmy", }, ), @@ -88,7 +100,8 @@ "metavar": "", "type": "int", "default": None, - "help": "show generations of ancestor classes not in ", + "group": OPTIONS_GROUPS["FILTERING"], + "help": "Show generations of ancestor classes not in .", }, ), ( @@ -97,7 +110,8 @@ "short": "A", "default": None, "action": "store_true", - "help": "show all ancestors off all classes in ", + "group": OPTIONS_GROUPS["FILTERING"], + "help": "Show all ancestors of all classes in .", }, ), ( @@ -108,7 +122,8 @@ "metavar": "", "type": "int", "default": None, - "help": "show levels of associated classes not in ", + "group": OPTIONS_GROUPS["FILTERING"], + "help": "Show levels of associated classes not in .", }, ), ( @@ -117,7 +132,8 @@ "short": "S", "default": None, "action": "store_true", - "help": "show recursively all associated off all associated classes", + "group": OPTIONS_GROUPS["FILTERING"], + "help": "Show all classes associated with the target classes, including indirect associations.", }, ), ( @@ -126,7 +142,8 @@ "short": "b", "action": "store_true", "default": False, - "help": "include builtin objects in representation of classes", + "group": OPTIONS_GROUPS["FILTERING"], + "help": "Include builtin objects in representation of classes.", }, ), ( @@ -135,9 +152,11 @@ "short": "L", "action": "store_true", "default": False, - "help": "include standard library objects in representation of classes", + "group": OPTIONS_GROUPS["FILTERING"], + "help": "Include standard library objects in representation of classes.", }, ), + # Display Options ( "module-names", { @@ -145,7 +164,8 @@ "default": None, "type": "yn", "metavar": "", - "help": "include module name in representation of classes", + "group": OPTIONS_GROUPS["DISPLAY"], + "help": "Include module name in the representation of classes.", }, ), ( @@ -154,7 +174,8 @@ "short": "k", "action": "store_true", "default": False, - "help": "don't show attributes and methods in the class boxes; this disables -f values", + "group": OPTIONS_GROUPS["DISPLAY"], + "help": "Don't show attributes and methods in the class boxes; this disables -f values.", }, ), ( @@ -162,24 +183,8 @@ { "action": "store_true", "default": False, - "help": "only show nodes with connections", - }, - ), - ( - "output", - { - "short": "o", - "dest": "output_format", - "action": "store", - "default": "dot", - "metavar": "", - "type": "string", - "help": ( - "create a *. output file if format is available. Available " - f"formats are: {', '.join(DIRECTLY_SUPPORTED_FORMATS)}. Any other " - f"format will be tried to create by means of the 'dot' command line " - f"tool, which requires a graphviz installation." - ), + "group": OPTIONS_GROUPS["DISPLAY"], + "help": "Only show nodes with connections.", }, ), ( @@ -188,6 +193,7 @@ "dest": "colorized", "action": "store_true", "default": False, + "group": OPTIONS_GROUPS["DISPLAY"], "help": "Use colored output. Classes/modules of the same package get the same color.", }, ), @@ -199,7 +205,8 @@ "default": 2, "metavar": "", "type": "int", - "help": "Use separate colors up to package depth of ", + "group": OPTIONS_GROUPS["DISPLAY"], + "help": "Use separate colors up to package depth of . Higher depths wil reuse colors.", }, ), ( @@ -210,9 +217,43 @@ "default": DEFAULT_COLOR_PALETTE, "metavar": "", "type": "csv", - "help": "Comma separated list of colors to use", + "group": OPTIONS_GROUPS["DISPLAY"], + "help": "Comma separated list of colors to use for the package depth coloring.", }, ), + # Output Control options + ( + "output", + { + "short": "o", + "dest": "output_format", + "action": "store", + "default": "dot", + "metavar": "", + "type": "string", + "group": OPTIONS_GROUPS["OUTPUT"], + "help": ( + "Create a *. output file if format is available. Available " + f"formats are: {', '.join(["." + format for format in DIRECTLY_SUPPORTED_FORMATS])}. Any other " + f"format will be tried to be created by using the 'dot' command line " + f"tool, which requires a graphviz installation. In this case, these additional " + "formats are available (see `Graphviz output formats `_)." + ), + }, + ), + ( + "output-directory", + { + "default": "", + "type": "path", + "short": "d", + "action": "store", + "metavar": "", + "group": OPTIONS_GROUPS["OUTPUT"], + "help": "Set the output directory path.", + }, + ), + # Project Configuration options ( "ignore", { @@ -220,6 +261,7 @@ "metavar": "", "dest": "ignore_list", "default": constants.DEFAULT_IGNORE_LIST, + "group": OPTIONS_GROUPS["PROJECT"], "help": "Files or directories to be skipped. They should be base names, not paths.", }, ), @@ -230,18 +272,8 @@ "type": "string", "short": "p", "metavar": "", - "help": "set the project name.", - }, - ), - ( - "output-directory", - { - "default": "", - "type": "path", - "short": "d", - "action": "store", - "metavar": "", - "help": "set the output directory path.", + "group": OPTIONS_GROUPS["PROJECT"], + "help": "Set the project name. This will later be appended to the output file names.", }, ), ( @@ -250,9 +282,10 @@ "type": "glob_paths_csv", "metavar": "[,...]", "default": (), + "group": OPTIONS_GROUPS["PROJECT"], "help": "Add paths to the list of the source roots. Supports globbing patterns. The " "source root is an absolute path or a path relative to the current working directory " - "used to determine a package namespace for modules located under the source root.", + "used to determine a package namespace for modules located under the source root." }, ), ( @@ -260,6 +293,7 @@ { "action": "store_true", "default": False, + "group": OPTIONS_GROUPS["PROJECT"], "help": "Makes pyreverse more verbose/talkative. Mostly useful for debugging.", }, ), From 7b421f566c0c22cdda0449ea70471b55f066faf0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 28 Oct 2024 16:49:28 +0000 Subject: [PATCH 03/12] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- pylint/pyreverse/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pylint/pyreverse/main.py b/pylint/pyreverse/main.py index 4c4352b71d..38317b266f 100644 --- a/pylint/pyreverse/main.py +++ b/pylint/pyreverse/main.py @@ -285,7 +285,7 @@ "group": OPTIONS_GROUPS["PROJECT"], "help": "Add paths to the list of the source roots. Supports globbing patterns. The " "source root is an absolute path or a path relative to the current working directory " - "used to determine a package namespace for modules located under the source root." + "used to determine a package namespace for modules located under the source root.", }, ), ( From dcccbb464f681cadff7bcd5d79ba8d1ef66b8240 Mon Sep 17 00:00:00 2001 From: Julfried Date: Tue, 29 Oct 2024 09:27:08 +0100 Subject: [PATCH 04/12] Try to fix failing tests with older python versions --- pylint/pyreverse/main.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pylint/pyreverse/main.py b/pylint/pyreverse/main.py index 38317b266f..54289a8309 100644 --- a/pylint/pyreverse/main.py +++ b/pylint/pyreverse/main.py @@ -234,9 +234,9 @@ "group": OPTIONS_GROUPS["OUTPUT"], "help": ( "Create a *. output file if format is available. Available " - f"formats are: {', '.join(["." + format for format in DIRECTLY_SUPPORTED_FORMATS])}. Any other " - f"format will be tried to be created by using the 'dot' command line " - f"tool, which requires a graphviz installation. In this case, these additional " + f"formats are: {', '.join('.' + fmt for fmt in DIRECTLY_SUPPORTED_FORMATS)}. Any other " + "format will be tried to be created by using the 'dot' command line " + "tool, which requires a graphviz installation. In this case, these additional " "formats are available (see `Graphviz output formats `_)." ), }, From de1686e439a00218cf0a9389032f4f7e3c7eb2ab Mon Sep 17 00:00:00 2001 From: Julfried Date: Wed, 30 Oct 2024 13:35:31 +0100 Subject: [PATCH 05/12] Turn docstring of the Run class into a regular python comment to avoid unwanted output --- pylint/pyreverse/main.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pylint/pyreverse/main.py b/pylint/pyreverse/main.py index 54289a8309..e6441ba185 100644 --- a/pylint/pyreverse/main.py +++ b/pylint/pyreverse/main.py @@ -206,7 +206,7 @@ "metavar": "", "type": "int", "group": OPTIONS_GROUPS["DISPLAY"], - "help": "Use separate colors up to package depth of . Higher depths wil reuse colors.", + "help": "Use separate colors up to package depth of . Higher depths will reuse colors.", }, ), ( @@ -300,9 +300,8 @@ ) +# Base class providing common behaviour for pyreverse commands class Run(_ArgumentsManager, _ArgumentsProvider): - """Base class providing common behaviour for pyreverse commands.""" - options = OPTIONS name = "pyreverse" From ed703e57317c4ff1a2dbe24ad47e715606bd03f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Wed, 30 Oct 2024 21:19:29 +0100 Subject: [PATCH 06/12] Add configuration section to Pyreverse documentation --- doc/conf.py | 1 + doc/exts/pyreverse_configuration.py | 66 +++++++++ doc/index.rst | 2 +- doc/pyreverse/configuration.rst | 153 +++++++++++++++++++++ doc/{pyreverse.rst => pyreverse/index.rst} | 12 +- pylint/pyreverse/main.py | 21 ++- 6 files changed, 238 insertions(+), 17 deletions(-) create mode 100644 doc/exts/pyreverse_configuration.py create mode 100644 doc/pyreverse/configuration.rst rename doc/{pyreverse.rst => pyreverse/index.rst} (95%) diff --git a/doc/conf.py b/doc/conf.py index 50d891eb93..741d1975c1 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -44,6 +44,7 @@ "pylint_extensions", "pylint_messages", "pylint_options", + "pyreverse_configuration", "sphinx.ext.autosectionlabel", "sphinx.ext.intersphinx", "sphinx_reredirects", diff --git a/doc/exts/pyreverse_configuration.py b/doc/exts/pyreverse_configuration.py new file mode 100644 index 0000000000..a031917cef --- /dev/null +++ b/doc/exts/pyreverse_configuration.py @@ -0,0 +1,66 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/pylint-dev/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/pylint-dev/pylint/blob/main/CONTRIBUTORS.txt + +"""Script used to generate the pyreverse configuration page.""" + +from __future__ import annotations + +from pathlib import Path +from typing import NamedTuple + +from sphinx.application import Sphinx + +from pylint.pyreverse.main import Run +from pylint.typing import OptionDict +from pylint.utils import get_rst_title + + +class OptionsData(NamedTuple): + name: str + optdict: OptionDict + + +PYREVERSE_PATH = Path(__file__).resolve().parent.parent / "pyreverse" +"""Path to the pyreverse documentation folder.""" + + +def _write_config_page(run: Run) -> None: + """Create or overwrite the configuration page.""" + sections: list[str] = [ + ".. This file is auto-generated. Make any changes to the associated\n" + ".. docs extension in 'doc/exts/pyreverse_configuration.py'.\n\n", + get_rst_title("Pyreverse Configuration", "^"), + ] + + options: list[OptionsData] = [OptionsData(name, info) for name, info in run.options] + + config_string = "" + for option in sorted(options, key=lambda x: x.name): + config_string += get_rst_title(f"--{option.name}", '"') + config_string += f"*{option.optdict.get('help')}*\n\n" + + if option.optdict.get("default") == "": + config_string += '**Default:** ``""``\n\n\n' + else: + config_string += f"**Default:** ``{option.optdict.get('default')}``\n\n\n" + + sections.append(config_string) + final_page = "\n\n".join(sections) + + with open(PYREVERSE_PATH / "configuration.rst", "w", encoding="utf-8") as stream: + stream.write(final_page) + + +# pylint: disable-next=unused-argument +def build_options_page(app: Sphinx | None) -> None: + # Write configuration page + _write_config_page(Run([])) + + +def setup(app: Sphinx) -> dict[str, bool]: + """Connects the extension to the Sphinx process.""" + # Register callback at the builder-inited Sphinx event + # See https://www.sphinx-doc.org/en/master/extdev/appapi.html + app.connect("builder-inited", build_options_page) + return {"parallel_read_safe": True} diff --git a/doc/index.rst b/doc/index.rst index 1e2c8f044a..dc86cb295b 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -33,7 +33,7 @@ :titlesonly: :hidden: - pyreverse + pyreverse/index symilar .. toctree:: diff --git a/doc/pyreverse/configuration.rst b/doc/pyreverse/configuration.rst new file mode 100644 index 0000000000..fbf93b4a46 --- /dev/null +++ b/doc/pyreverse/configuration.rst @@ -0,0 +1,153 @@ +.. This file is auto-generated. Make any changes to the associated +.. docs extension in 'doc/exts/pyreverse_configuration.py'. + + + +Pyreverse Configuration +^^^^^^^^^^^^^^^^^^^^^^^ + + +--all-ancestors +""""""""""""""" +*Show all ancestors of all classes in .* + +**Default:** ``None`` + + +--all-associated +"""""""""""""""" +*Show all classes associated with the target classes, including indirect associations.* + +**Default:** ``None`` + + +--class +""""""" +*Create a class diagram with all classes related to ; this uses by default the options -ASmy* + +**Default:** ``None`` + + +--color-palette +""""""""""""""" +*Comma separated list of colors to use for the package depth coloring.* + +**Default:** ``('#77AADD', '#99DDFF', '#44BB99', '#BBCC33', '#AAAA00', '#EEDD88', '#EE8866', '#FFAABB', '#DDDDDD')`` + + +--colorized +""""""""""" +*Use colored output. Classes/modules of the same package get the same color.* + +**Default:** ``False`` + + +--filter-mode +""""""""""""" +*Filter attributes and functions according to . Correct modes are: +'PUB_ONLY' filter all non public attributes [DEFAULT], equivalent to PRIVATE+SPECIAL +'ALL' no filter +'SPECIAL' filter Python special functions except constructor +'OTHER' filter protected and private attributes* + +**Default:** ``PUB_ONLY`` + + +--ignore +"""""""" +*Files or directories to be skipped. They should be base names, not paths.* + +**Default:** ``('CVS',)`` + + +--max-color-depth +""""""""""""""""" +*Use separate colors up to package depth of . Higher depths will reuse colors.* + +**Default:** ``2`` + + +--module-names +"""""""""""""" +*Include module name in the representation of classes.* + +**Default:** ``None`` + + +--no-standalone +""""""""""""""" +*Only show nodes with connections.* + +**Default:** ``False`` + + +--only-classnames +""""""""""""""""" +*Don't show attributes and methods in the class boxes; this disables -f values.* + +**Default:** ``False`` + + +--output +"""""""" +*Create a *. output file if format is available. Available formats are: .dot, .puml, .plantuml, .mmd, .html. Any other format will be tried to be created by using the 'dot' command line tool, which requires a graphviz installation. In this case, these additional formats are available (see `Graphviz output formats `_).* + +**Default:** ``dot`` + + +--output-directory +"""""""""""""""""" +*Set the output directory path.* + +**Default:** ``""`` + + +--project +""""""""" +*Set the project name. This will later be appended to the output file names.* + +**Default:** ``""`` + + +--show-ancestors +"""""""""""""""" +*Show generations of ancestor classes not in .* + +**Default:** ``None`` + + +--show-associated +""""""""""""""""" +*Show levels of associated classes not in .* + +**Default:** ``None`` + + +--show-builtin +"""""""""""""" +*Include builtin objects in representation of classes.* + +**Default:** ``False`` + + +--show-stdlib +""""""""""""" +*Include standard library objects in representation of classes.* + +**Default:** ``False`` + + +--source-roots +"""""""""""""" +*Add paths to the list of the source roots. Supports globbing patterns. The source root is an absolute path or a path relative to the current working directory used to determine a package namespace for modules located under the source root.* + +**Default:** ``()`` + + +--verbose +""""""""" +*Makes pyreverse more verbose/talkative. Mostly useful for debugging.* + +**Default:** ``False`` + + diff --git a/doc/pyreverse.rst b/doc/pyreverse/index.rst similarity index 95% rename from doc/pyreverse.rst rename to doc/pyreverse/index.rst index d324080d38..bfe9669160 100644 --- a/doc/pyreverse.rst +++ b/doc/pyreverse/index.rst @@ -117,7 +117,7 @@ Example diagrams generated with the ``.puml`` output format are shown below. Class Diagram ............. -.. image:: media/pyreverse_example_classes.png +.. image:: ../media/pyreverse_example_classes.png :width: 625 :height: 589 :alt: Class diagram generated by pyreverse @@ -127,7 +127,7 @@ Class Diagram Package Diagram ............... -.. image:: media/pyreverse_example_packages.png +.. image:: ../media/pyreverse_example_packages.png :width: 344 :height: 177 :alt: Package diagram generated by pyreverse @@ -146,8 +146,14 @@ For example, running:: will generate the full class and package diagrams for ``pylint``, but will additionally generate a file ``pylint.checkers.classes.ClassChecker.dot``: -.. image:: media/ClassChecker_diagram.png +.. image:: ../media/ClassChecker_diagram.png :width: 757 :height: 1452 :alt: Package diagram generated by pyreverse :align: center + +.. toctree:: + :maxdepth: 1 + :hidden: + + configuration diff --git a/pylint/pyreverse/main.py b/pylint/pyreverse/main.py index e6441ba185..b95e32a846 100644 --- a/pylint/pyreverse/main.py +++ b/pylint/pyreverse/main.py @@ -8,7 +8,6 @@ import sys from collections.abc import Sequence -from typing import NoReturn from pylint import constants from pylint.config.arguments_manager import _ArgumentsManager @@ -67,15 +66,11 @@ "action": "store", "metavar": "", "group": OPTIONS_GROUPS["FILTERING"], - "help": """Filter attributes and functions according to - . Correct modes are : - 'PUB_ONLY' filter all non public attributes - [DEFAULT], equivalent to PRIVATE+SPECIAL_A - 'ALL' no filter - 'SPECIAL' filter Python special functions - except constructor - 'OTHER' filter protected and private - attributes""", + "help": """Filter attributes and functions according to . Correct modes are: +'PUB_ONLY' filter all non public attributes [DEFAULT], equivalent to PRIVATE+SPECIAL +'ALL' no filter +'SPECIAL' filter Python special functions except constructor +'OTHER' filter protected and private attributes""", }, ), ( @@ -305,7 +300,7 @@ class Run(_ArgumentsManager, _ArgumentsProvider): options = OPTIONS name = "pyreverse" - def __init__(self, args: Sequence[str]) -> NoReturn: + def __init__(self, args: Sequence[str]) -> None: # Immediately exit if user asks for version if "--version" in args: print("pyreverse is included in pylint:") @@ -327,7 +322,6 @@ def __init__(self, args: Sequence[str]) -> NoReturn: ) check_if_graphviz_supports_format(self.config.output_format) - sys.exit(self.run(args)) def run(self, args: list[str]) -> int: """Checking arguments and run project.""" @@ -352,4 +346,5 @@ def run(self, args: list[str]) -> int: if __name__ == "__main__": - Run(sys.argv[1:]) + args = sys.argv[1:] + Run(args).run(args) From ca8727d5d2b30cb0aa29762db90e52588d728573 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 30 Oct 2024 20:20:29 +0000 Subject: [PATCH 07/12] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- doc/pyreverse/configuration.rst | 2 -- pylint/pyreverse/main.py | 1 - 2 files changed, 3 deletions(-) diff --git a/doc/pyreverse/configuration.rst b/doc/pyreverse/configuration.rst index fbf93b4a46..442c60aa10 100644 --- a/doc/pyreverse/configuration.rst +++ b/doc/pyreverse/configuration.rst @@ -149,5 +149,3 @@ Pyreverse Configuration *Makes pyreverse more verbose/talkative. Mostly useful for debugging.* **Default:** ``False`` - - diff --git a/pylint/pyreverse/main.py b/pylint/pyreverse/main.py index b95e32a846..7d90cfca3e 100644 --- a/pylint/pyreverse/main.py +++ b/pylint/pyreverse/main.py @@ -322,7 +322,6 @@ def __init__(self, args: Sequence[str]) -> None: ) check_if_graphviz_supports_format(self.config.output_format) - def run(self, args: list[str]) -> int: """Checking arguments and run project.""" if not args: From 110c1c454a670f9452650a90798c687af562a76d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Wed, 30 Oct 2024 21:42:29 +0100 Subject: [PATCH 08/12] Separate into different directory --- .../pyreverse/configuration.rst | 106 +++++++----- doc/additional_tools/pyreverse/index.rst | 72 ++++++++ .../symilar/index.rst} | 0 doc/exts/pyreverse_configuration.py | 27 ++- doc/index.rst | 4 +- doc/pyreverse/index.rst | 159 ------------------ pylint/__init__.py | 5 +- pylint/pyreverse/inspector.py | 4 +- pylint/pyreverse/main.py | 6 +- tests/pyreverse/test_main.py | 4 +- 10 files changed, 164 insertions(+), 223 deletions(-) rename doc/{ => additional_tools}/pyreverse/configuration.rst (95%) create mode 100644 doc/additional_tools/pyreverse/index.rst rename doc/{symilar.rst => additional_tools/symilar/index.rst} (100%) delete mode 100644 doc/pyreverse/index.rst diff --git a/doc/pyreverse/configuration.rst b/doc/additional_tools/pyreverse/configuration.rst similarity index 95% rename from doc/pyreverse/configuration.rst rename to doc/additional_tools/pyreverse/configuration.rst index 442c60aa10..aa42e8c9a5 100644 --- a/doc/pyreverse/configuration.rst +++ b/doc/additional_tools/pyreverse/configuration.rst @@ -7,6 +7,9 @@ Pyreverse Configuration ^^^^^^^^^^^^^^^^^^^^^^^ +Filtering and Scope +------------------- + --all-ancestors """"""""""""""" *Show all ancestors of all classes in .* @@ -28,20 +31,6 @@ Pyreverse Configuration **Default:** ``None`` ---color-palette -""""""""""""""" -*Comma separated list of colors to use for the package depth coloring.* - -**Default:** ``('#77AADD', '#99DDFF', '#44BB99', '#BBCC33', '#AAAA00', '#EEDD88', '#EE8866', '#FFAABB', '#DDDDDD')`` - - ---colorized -""""""""""" -*Use colored output. Classes/modules of the same package get the same color.* - -**Default:** ``False`` - - --filter-mode """"""""""""" *Filter attributes and functions according to . Correct modes are: @@ -53,11 +42,51 @@ Pyreverse Configuration **Default:** ``PUB_ONLY`` ---ignore -"""""""" -*Files or directories to be skipped. They should be base names, not paths.* +--show-ancestors +"""""""""""""""" +*Show generations of ancestor classes not in .* -**Default:** ``('CVS',)`` +**Default:** ``None`` + + +--show-associated +""""""""""""""""" +*Show levels of associated classes not in .* + +**Default:** ``None`` + + +--show-builtin +"""""""""""""" +*Include builtin objects in representation of classes.* + +**Default:** ``False`` + + +--show-stdlib +""""""""""""" +*Include standard library objects in representation of classes.* + +**Default:** ``False`` + + + + +Display Options +--------------- + +--color-palette +""""""""""""""" +*Comma separated list of colors to use for the package depth coloring.* + +**Default:** ``('#77AADD', '#99DDFF', '#44BB99', '#BBCC33', '#AAAA00', '#EEDD88', '#EE8866', '#FFAABB', '#DDDDDD')`` + + +--colorized +""""""""""" +*Use colored output. Classes/modules of the same package get the same color.* + +**Default:** ``False`` --max-color-depth @@ -88,6 +117,11 @@ Pyreverse Configuration **Default:** ``False`` + + +Output Control +-------------- + --output """""""" *Create a *. output file if format is available. Available formats are: .dot, .puml, .plantuml, .mmd, .html. Any other format will be tried to be created by using the 'dot' command line tool, which requires a graphviz installation. In this case, these additional formats are available (see `Graphviz output formats `_).* @@ -102,39 +136,23 @@ Pyreverse Configuration **Default:** ``""`` ---project -""""""""" -*Set the project name. This will later be appended to the output file names.* - -**Default:** ``""`` - ---show-ancestors -"""""""""""""""" -*Show generations of ancestor classes not in .* -**Default:** ``None`` +Project Configuration +--------------------- +--ignore +"""""""" +*Files or directories to be skipped. They should be base names, not paths.* ---show-associated -""""""""""""""""" -*Show levels of associated classes not in .* - -**Default:** ``None`` - - ---show-builtin -"""""""""""""" -*Include builtin objects in representation of classes.* - -**Default:** ``False`` +**Default:** ``('CVS',)`` ---show-stdlib -""""""""""""" -*Include standard library objects in representation of classes.* +--project +""""""""" +*Set the project name. This will later be appended to the output file names.* -**Default:** ``False`` +**Default:** ``""`` --source-roots diff --git a/doc/additional_tools/pyreverse/index.rst b/doc/additional_tools/pyreverse/index.rst new file mode 100644 index 0000000000..41f74731dc --- /dev/null +++ b/doc/additional_tools/pyreverse/index.rst @@ -0,0 +1,72 @@ +.. _pyreverse: + +Pyreverse +--------- + +``pyreverse`` analyzes your source code and generates package and class diagrams. + +It supports output to ``.dot``/``.gv``, ``.puml``/``.plantuml`` (PlantUML) and ``.mmd``/``.html`` (MermaidJS) file formats. +If Graphviz (or the ``dot`` command) is installed, all `output formats supported by Graphviz `_ +can be used as well. In this case, ``pyreverse`` first generates a temporary ``.gv`` file, which is then +fed to Graphviz to generate the final image. + +Running Pyreverse +''''''''''''''''' + +To run ``pyreverse``, use:: + + pyreverse [options] + + can also be a single Python module. +To see a full list of the available options, run:: + + pyreverse -h + +Example Output +'''''''''''''' + +Example diagrams generated with the ``.puml`` output format are shown below. + +Class Diagram +............. + +.. image:: ../../media/pyreverse_example_classes.png + :width: 625 + :height: 589 + :alt: Class diagram generated by pyreverse + :align: center + + +Package Diagram +............... + +.. image:: ../../media/pyreverse_example_packages.png + :width: 344 + :height: 177 + :alt: Package diagram generated by pyreverse + :align: center + + +Creating Class Diagrams for Specific Classes +'''''''''''''''''''''''''''''''''''''''''''' + +In many cases creating a single diagram depicting all classes in the project yields a rather unwieldy, giant diagram. +While limiting the input path to a single package or module can already help greatly to narrow down the scope, the ``-c`` option +provides another way to create a class diagram focusing on a single class and its collaborators. +For example, running:: + + pyreverse -ASmy -c pylint.checkers.classes.ClassChecker pylint + +will generate the full class and package diagrams for ``pylint``, but will additionally generate a file ``pylint.checkers.classes.ClassChecker.dot``: + +.. image:: ../../media/ClassChecker_diagram.png + :width: 757 + :height: 1452 + :alt: Package diagram generated by pyreverse + :align: center + +.. toctree:: + :maxdepth: 1 + :hidden: + + configuration diff --git a/doc/symilar.rst b/doc/additional_tools/symilar/index.rst similarity index 100% rename from doc/symilar.rst rename to doc/additional_tools/symilar/index.rst diff --git a/doc/exts/pyreverse_configuration.py b/doc/exts/pyreverse_configuration.py index a031917cef..f4d77ef216 100644 --- a/doc/exts/pyreverse_configuration.py +++ b/doc/exts/pyreverse_configuration.py @@ -11,7 +11,7 @@ from sphinx.application import Sphinx -from pylint.pyreverse.main import Run +from pylint.pyreverse.main import OPTIONS_GROUPS, Run from pylint.typing import OptionDict from pylint.utils import get_rst_title @@ -21,7 +21,9 @@ class OptionsData(NamedTuple): optdict: OptionDict -PYREVERSE_PATH = Path(__file__).resolve().parent.parent / "pyreverse" +PYREVERSE_PATH = ( + Path(__file__).resolve().parent.parent / "additional_tools" / "pyreverse" +) """Path to the pyreverse documentation folder.""" @@ -34,19 +36,26 @@ def _write_config_page(run: Run) -> None: ] options: list[OptionsData] = [OptionsData(name, info) for name, info in run.options] + option_groups: dict[str, list[str]] = {g: [] for g in OPTIONS_GROUPS.values()} - config_string = "" for option in sorted(options, key=lambda x: x.name): - config_string += get_rst_title(f"--{option.name}", '"') - config_string += f"*{option.optdict.get('help')}*\n\n" + option_string = get_rst_title(f"--{option.name}", '"') + option_string += f"*{option.optdict.get('help')}*\n\n" if option.optdict.get("default") == "": - config_string += '**Default:** ``""``\n\n\n' + option_string += '**Default:** ``""``\n\n\n' else: - config_string += f"**Default:** ``{option.optdict.get('default')}``\n\n\n" + option_string += f"**Default:** ``{option.optdict.get('default')}``\n\n\n" - sections.append(config_string) - final_page = "\n\n".join(sections) + option_groups[str(option.optdict.get("group"))].append(option_string) + + for group_title in OPTIONS_GROUPS.values(): + sections.append( + get_rst_title(group_title, "-") + "\n" + "".join(option_groups[group_title]) + ) + + # Join all sections and remove the final two newlines + final_page = "\n\n".join(sections)[:-2] with open(PYREVERSE_PATH / "configuration.rst", "w", encoding="utf-8") as stream: stream.write(final_page) diff --git a/doc/index.rst b/doc/index.rst index dc86cb295b..8bcdeac8bb 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -33,8 +33,8 @@ :titlesonly: :hidden: - pyreverse/index - symilar + additional_tools/pyreverse/index + additional_tools/symilar/index .. toctree:: :caption: Changelog diff --git a/doc/pyreverse/index.rst b/doc/pyreverse/index.rst deleted file mode 100644 index bfe9669160..0000000000 --- a/doc/pyreverse/index.rst +++ /dev/null @@ -1,159 +0,0 @@ -.. _pyreverse: - -Pyreverse ---------- - -``pyreverse`` analyzes your source code and generates package and class diagrams. - -It supports output to ``.dot``/``.gv``, ``.puml``/``.plantuml`` (PlantUML) and ``.mmd``/``.html`` (MermaidJS) file formats. -If Graphviz (or the ``dot`` command) is installed, all `output formats supported by Graphviz `_ -can be used as well. In this case, ``pyreverse`` first generates a temporary ``.gv`` file, which is then -fed to Graphviz to generate the final image. - -Running Pyreverse -''''''''''''''''' - -To run ``pyreverse``, use:: - - pyreverse [options] - - can also be a single Python module. -To see a full list of the available options, run:: - - pyreverse -h - - -Command Line Options -.................... - -Pyreverse provides various options to control the output and appearance of your diagrams: - -Output Control -~~~~~~~~~~~~~~ - -``--output``, ``-o `` - Specify the output format (e.g., .dot, .puml, .plantuml, .mmd, .html). - Additional formats are available if Graphviz is installed (see `Graphviz output formats `_). - -``--output-directory``, ``-d `` - Set the output directory path for generated diagrams. - -``--project``, ``-p `` - Set the project name. - -Filtering and Scope -~~~~~~~~~~~~~~~~~~~ - -``--filter-mode``, ``-f `` - Control which class members are shown: - - * ``PUB_ONLY`` (default): Show only public attributes - * ``ALL``: Show all members - * ``SPECIAL``: Show all but Python special functions (except constructor) - * ``OTHER``: Show all but protected and private attributes - -``--class``, ``-c `` - Create a focused diagram showing a specific class and its relations. - Automatically enables -ASmy options. - -``--show-ancestors``, ``-a `` - Include specified number of ancestor generations. - -``--all-ancestors``, ``-A`` - Include all ancestor classes. - -``--show-associated``, ``-s `` - Include specified levels of associated classes. - -``--all-associated``, ``-S`` - Include all associated classes recursively. - -``--show-builtin``, ``-b`` - Include Python's builtin objects. - -``--show-stdlib``, ``-L`` - Include standard library objects. - -Display Options -~~~~~~~~~~~~~~~ - -``--module-names``, ``-m `` - Show or hide module names in class representations. - -``--only-classnames``, ``-k`` - Show only class names, hiding attributes and methods. - -``--no-standalone`` - Only show connected classes. - -``--colorized`` - Enable colored output. Classes/modules in the same package share colors. - -``--max-color-depth `` - Control color distinction by package depth (default: 2). - -``--color-palette `` - Specify custom colors for the diagram. - -Project Configuration -~~~~~~~~~~~~~~~~~~~~~ - -``--ignore `` - Exclude specific files or directories (use base names). - -``--source-roots [,...]>`` - Define source roots for package namespace resolution. - Supports glob patterns. - -``--verbose`` - Enable detailed output for debugging. - - -Example Output -'''''''''''''' - -Example diagrams generated with the ``.puml`` output format are shown below. - -Class Diagram -............. - -.. image:: ../media/pyreverse_example_classes.png - :width: 625 - :height: 589 - :alt: Class diagram generated by pyreverse - :align: center - - -Package Diagram -............... - -.. image:: ../media/pyreverse_example_packages.png - :width: 344 - :height: 177 - :alt: Package diagram generated by pyreverse - :align: center - - -Creating Class Diagrams for Specific Classes -'''''''''''''''''''''''''''''''''''''''''''' - -In many cases creating a single diagram depicting all classes in the project yields a rather unwieldy, giant diagram. -While limiting the input path to a single package or module can already help greatly to narrow down the scope, the ``-c`` option -provides another way to create a class diagram focusing on a single class and its collaborators. -For example, running:: - - pyreverse -ASmy -c pylint.checkers.classes.ClassChecker pylint - -will generate the full class and package diagrams for ``pylint``, but will additionally generate a file ``pylint.checkers.classes.ClassChecker.dot``: - -.. image:: ../media/ClassChecker_diagram.png - :width: 757 - :height: 1452 - :alt: Package diagram generated by pyreverse - :align: center - -.. toctree:: - :maxdepth: 1 - :hidden: - - configuration diff --git a/pylint/__init__.py b/pylint/__init__.py index 74bde8a394..98554d21c8 100644 --- a/pylint/__init__.py +++ b/pylint/__init__.py @@ -46,14 +46,15 @@ def _run_pylint_config(argv: Sequence[str] | None = None) -> None: _PylintConfigRun(argv or sys.argv[1:]) -def run_pyreverse(argv: Sequence[str] | None = None) -> NoReturn: +def run_pyreverse(argv: Sequence[str] | None = None) -> None: """Run pyreverse. argv can be a sequence of strings normally supplied as arguments on the command line """ from pylint.pyreverse.main import Run as PyreverseRun - PyreverseRun(argv or sys.argv[1:]) + args = argv or sys.argv[1:] + PyreverseRun(args).run(args) def run_symilar(argv: Sequence[str] | None = None) -> NoReturn: diff --git a/pylint/pyreverse/inspector.py b/pylint/pyreverse/inspector.py index 8825363fa7..40fb8d6079 100644 --- a/pylint/pyreverse/inspector.py +++ b/pylint/pyreverse/inspector.py @@ -13,7 +13,7 @@ import os import traceback from abc import ABC, abstractmethod -from collections.abc import Callable +from collections.abc import Callable, Sequence from typing import Optional import astroid @@ -346,7 +346,7 @@ def handle(self, node: nodes.AssignAttr, parent: nodes.ClassDef) -> None: def project_from_files( - files: list[str], + files: Sequence[str], func_wrapper: _WrapperFuncT = _astroid_wrapper, project_name: str = "no name", black_list: tuple[str, ...] = constants.DEFAULT_IGNORE_LIST, diff --git a/pylint/pyreverse/main.py b/pylint/pyreverse/main.py index 7d90cfca3e..ac63bddee1 100644 --- a/pylint/pyreverse/main.py +++ b/pylint/pyreverse/main.py @@ -322,7 +322,7 @@ def __init__(self, args: Sequence[str]) -> None: ) check_if_graphviz_supports_format(self.config.output_format) - def run(self, args: list[str]) -> int: + def run(self, args: Sequence[str]) -> int: """Checking arguments and run project.""" if not args: print(self.help()) @@ -345,5 +345,5 @@ def run(self, args: list[str]) -> int: if __name__ == "__main__": - args = sys.argv[1:] - Run(args).run(args) + arguments = sys.argv[1:] + Run(arguments).run(arguments) diff --git a/tests/pyreverse/test_main.py b/tests/pyreverse/test_main.py index e8e46df2c1..c27b52628e 100644 --- a/tests/pyreverse/test_main.py +++ b/tests/pyreverse/test_main.py @@ -164,7 +164,7 @@ def test_verbose(_: mock.MagicMock, capsys: CaptureFixture[str]) -> None: @mock.patch("pylint.pyreverse.main.sys.exit", new=mock.MagicMock()) def test_command_line_arguments_defaults(arg: str, expected_default: Any) -> None: """Test that the default arguments of all options are correct.""" - run = main.Run([TEST_DATA_DIR]) # type: ignore[var-annotated] + run = main.Run([TEST_DATA_DIR]) assert getattr(run.config, arg) == expected_default @@ -191,7 +191,7 @@ def test_class_command( Make sure that we append multiple --class arguments to one option destination. """ - runner = main.Run( # type: ignore[var-annotated] + runner = main.Run( [ "--class", "data.clientmodule_test.Ancestor", From ab500ab5f7cff49cb16062d4b6e0a289e7b6eac6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Wed, 30 Oct 2024 21:47:19 +0100 Subject: [PATCH 09/12] Fix `run_pyreverse` --- pylint/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pylint/__init__.py b/pylint/__init__.py index 98554d21c8..d95c9bc71e 100644 --- a/pylint/__init__.py +++ b/pylint/__init__.py @@ -46,7 +46,7 @@ def _run_pylint_config(argv: Sequence[str] | None = None) -> None: _PylintConfigRun(argv or sys.argv[1:]) -def run_pyreverse(argv: Sequence[str] | None = None) -> None: +def run_pyreverse(argv: Sequence[str] | None = None) -> NoReturn: """Run pyreverse. argv can be a sequence of strings normally supplied as arguments on the command line @@ -54,7 +54,7 @@ def run_pyreverse(argv: Sequence[str] | None = None) -> None: from pylint.pyreverse.main import Run as PyreverseRun args = argv or sys.argv[1:] - PyreverseRun(args).run(args) + sys.exit(PyreverseRun(args).run(args)) def run_symilar(argv: Sequence[str] | None = None) -> NoReturn: From 7fe0473973ad61d7eafe127b52d761041495b924 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Wed, 30 Oct 2024 21:59:10 +0100 Subject: [PATCH 10/12] Fix more tests --- tests/pyreverse/test_main.py | 12 ++++++++---- tests/pyreverse/test_pyreverse_functional.py | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/tests/pyreverse/test_main.py b/tests/pyreverse/test_main.py index c27b52628e..613d80e1b3 100644 --- a/tests/pyreverse/test_main.py +++ b/tests/pyreverse/test_main.py @@ -75,7 +75,8 @@ def test_graphviz_supported_image_format( """Test that Graphviz is used if the image format is supported.""" with pytest.raises(SystemExit) as wrapped_sysexit: # we have to catch the SystemExit so the test execution does not stop - main.Run(["-o", "png", TEST_DATA_DIR]) + args = ["-o", "png", TEST_DATA_DIR] + main.Run(args).run(args) # Check that the right info message is shown to the user assert ( "Format png is not supported natively. Pyreverse will try to generate it using Graphviz..." @@ -97,7 +98,8 @@ def test_graphviz_cant_determine_supported_formats( mock_subprocess.run.return_value.stderr = "..." with pytest.raises(SystemExit) as wrapped_sysexit: # we have to catch the SystemExit so the test execution does not stop - main.Run(["-o", "png", TEST_DATA_DIR]) + args = ["-o", "png", TEST_DATA_DIR] + main.Run(args).run(args) # Check that the right info message is shown to the user assert ( "Unable to determine Graphviz supported output formats." @@ -136,7 +138,8 @@ def test_verbose(_: mock.MagicMock, capsys: CaptureFixture[str]) -> None: """Test the --verbose flag.""" with pytest.raises(SystemExit): # we have to catch the SystemExit so the test execution does not stop - main.Run(["--verbose", TEST_DATA_DIR]) + args = ["--verbose", TEST_DATA_DIR] + main.Run(args).run(args) assert "parsing" in capsys.readouterr().out @@ -178,7 +181,8 @@ def test_command_line_arguments_yes_no( of using it as a flag. """ with pytest.raises(SystemExit) as wrapped_sysexit: - main.Run(["--module-names=yes", TEST_DATA_DIR]) + args = ["--module-names=yes", TEST_DATA_DIR] + main.Run(args).run(args) assert wrapped_sysexit.value.code == 0 diff --git a/tests/pyreverse/test_pyreverse_functional.py b/tests/pyreverse/test_pyreverse_functional.py index 715ad3dada..c1bb50e158 100644 --- a/tests/pyreverse/test_pyreverse_functional.py +++ b/tests/pyreverse/test_pyreverse_functional.py @@ -44,7 +44,7 @@ def test_class_diagrams(testfile: FunctionalPyreverseTestfile, tmp_path: Path) - args += ["--source-roots", source_roots] args.extend(testfile.options["command_line_args"]) args += [str(input_file)] - Run(args) + Run(args).run(args) assert sys_exit.value.code == 0 assert testfile.source.with_suffix(f".{output_format}").read_text( encoding="utf8" From e4ff14084943a4fa63b9ad0263f308a6e5dd36f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Wed, 30 Oct 2024 22:08:41 +0100 Subject: [PATCH 11/12] Be more sensible and just store args --- pylint/__init__.py | 3 +-- pylint/pyreverse/main.py | 12 ++++++------ tests/pyreverse/test_main.py | 12 ++++-------- tests/pyreverse/test_pyreverse_functional.py | 2 +- 4 files changed, 12 insertions(+), 17 deletions(-) diff --git a/pylint/__init__.py b/pylint/__init__.py index d95c9bc71e..d3ddf71f62 100644 --- a/pylint/__init__.py +++ b/pylint/__init__.py @@ -53,8 +53,7 @@ def run_pyreverse(argv: Sequence[str] | None = None) -> NoReturn: """ from pylint.pyreverse.main import Run as PyreverseRun - args = argv or sys.argv[1:] - sys.exit(PyreverseRun(args).run(args)) + sys.exit(PyreverseRun(argv or sys.argv[1:]).run()) def run_symilar(argv: Sequence[str] | None = None) -> NoReturn: diff --git a/pylint/pyreverse/main.py b/pylint/pyreverse/main.py index ac63bddee1..972a46741f 100644 --- a/pylint/pyreverse/main.py +++ b/pylint/pyreverse/main.py @@ -312,7 +312,7 @@ def __init__(self, args: Sequence[str]) -> None: # Parse options insert_default_options() - args = self._parse_command_line_configuration(args) + self.args = self._parse_command_line_configuration(args) if self.config.output_format not in DIRECTLY_SUPPORTED_FORMATS: check_graphviz_availability() @@ -322,17 +322,17 @@ def __init__(self, args: Sequence[str]) -> None: ) check_if_graphviz_supports_format(self.config.output_format) - def run(self, args: Sequence[str]) -> int: + def run(self) -> int: """Checking arguments and run project.""" - if not args: + if not self.args: print(self.help()) return 1 extra_packages_paths = list( - {discover_package_path(arg, self.config.source_roots) for arg in args} + {discover_package_path(arg, self.config.source_roots) for arg in self.args} ) with augmented_sys_path(extra_packages_paths): project = project_from_files( - args, + self.args, project_name=self.config.project, black_list=self.config.ignore_list, verbose=self.config.verbose, @@ -346,4 +346,4 @@ def run(self, args: Sequence[str]) -> int: if __name__ == "__main__": arguments = sys.argv[1:] - Run(arguments).run(arguments) + Run(arguments).run() diff --git a/tests/pyreverse/test_main.py b/tests/pyreverse/test_main.py index 613d80e1b3..bbd65953de 100644 --- a/tests/pyreverse/test_main.py +++ b/tests/pyreverse/test_main.py @@ -75,8 +75,7 @@ def test_graphviz_supported_image_format( """Test that Graphviz is used if the image format is supported.""" with pytest.raises(SystemExit) as wrapped_sysexit: # we have to catch the SystemExit so the test execution does not stop - args = ["-o", "png", TEST_DATA_DIR] - main.Run(args).run(args) + main.Run(["-o", "png", TEST_DATA_DIR]).run() # Check that the right info message is shown to the user assert ( "Format png is not supported natively. Pyreverse will try to generate it using Graphviz..." @@ -98,8 +97,7 @@ def test_graphviz_cant_determine_supported_formats( mock_subprocess.run.return_value.stderr = "..." with pytest.raises(SystemExit) as wrapped_sysexit: # we have to catch the SystemExit so the test execution does not stop - args = ["-o", "png", TEST_DATA_DIR] - main.Run(args).run(args) + main.Run(["-o", "png", TEST_DATA_DIR]).run() # Check that the right info message is shown to the user assert ( "Unable to determine Graphviz supported output formats." @@ -138,8 +136,7 @@ def test_verbose(_: mock.MagicMock, capsys: CaptureFixture[str]) -> None: """Test the --verbose flag.""" with pytest.raises(SystemExit): # we have to catch the SystemExit so the test execution does not stop - args = ["--verbose", TEST_DATA_DIR] - main.Run(args).run(args) + main.Run(["--verbose", TEST_DATA_DIR]).run() assert "parsing" in capsys.readouterr().out @@ -181,8 +178,7 @@ def test_command_line_arguments_yes_no( of using it as a flag. """ with pytest.raises(SystemExit) as wrapped_sysexit: - args = ["--module-names=yes", TEST_DATA_DIR] - main.Run(args).run(args) + main.Run(["--module-names=yes", TEST_DATA_DIR]).run() assert wrapped_sysexit.value.code == 0 diff --git a/tests/pyreverse/test_pyreverse_functional.py b/tests/pyreverse/test_pyreverse_functional.py index c1bb50e158..50e99056dd 100644 --- a/tests/pyreverse/test_pyreverse_functional.py +++ b/tests/pyreverse/test_pyreverse_functional.py @@ -44,7 +44,7 @@ def test_class_diagrams(testfile: FunctionalPyreverseTestfile, tmp_path: Path) - args += ["--source-roots", source_roots] args.extend(testfile.options["command_line_args"]) args += [str(input_file)] - Run(args).run(args) + Run(args).run() assert sys_exit.value.code == 0 assert testfile.source.with_suffix(f".{output_format}").read_text( encoding="utf8" From c4cc8a51d1cef3e6fec2c90b04ee3472f9877676 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Wed, 30 Oct 2024 22:21:43 +0100 Subject: [PATCH 12/12] Fix it once and for all --- doc/whatsnew/fragments/9689.breaking | 7 +++++++ tests/pyreverse/test_main.py | 21 +++++++------------- tests/pyreverse/test_pyreverse_functional.py | 15 +++++++------- 3 files changed, 21 insertions(+), 22 deletions(-) create mode 100644 doc/whatsnew/fragments/9689.breaking diff --git a/doc/whatsnew/fragments/9689.breaking b/doc/whatsnew/fragments/9689.breaking new file mode 100644 index 0000000000..5b175304a5 --- /dev/null +++ b/doc/whatsnew/fragments/9689.breaking @@ -0,0 +1,7 @@ +`pyreverse` `Run` was changed to no longer call `sys.exit()` in its `__init__`. +You should now call `Run(args).run()` which will return the exit code instead. +Having a class that always raised a `SystemExit` exception was considered a bug. + +Normal usage of pyreverse through the CLI will not be affected by this change. + +Refs #9689 diff --git a/tests/pyreverse/test_main.py b/tests/pyreverse/test_main.py index bbd65953de..895892d506 100644 --- a/tests/pyreverse/test_main.py +++ b/tests/pyreverse/test_main.py @@ -73,9 +73,7 @@ def test_graphviz_supported_image_format( mock_writer: mock.MagicMock, capsys: CaptureFixture[str] ) -> None: """Test that Graphviz is used if the image format is supported.""" - with pytest.raises(SystemExit) as wrapped_sysexit: - # we have to catch the SystemExit so the test execution does not stop - main.Run(["-o", "png", TEST_DATA_DIR]).run() + exit_code = main.Run(["-o", "png", TEST_DATA_DIR]).run() # Check that the right info message is shown to the user assert ( "Format png is not supported natively. Pyreverse will try to generate it using Graphviz..." @@ -83,7 +81,7 @@ def test_graphviz_supported_image_format( ) # Check that pyreverse actually made the call to create the diagram and we exit cleanly mock_writer.DiagramWriter().write.assert_called_once() - assert wrapped_sysexit.value.code == 0 + assert exit_code == 0 @mock.patch("pylint.pyreverse.main.Linker", new=mock.MagicMock()) @@ -95,9 +93,7 @@ def test_graphviz_cant_determine_supported_formats( ) -> None: """Test that Graphviz is used if the image format is supported.""" mock_subprocess.run.return_value.stderr = "..." - with pytest.raises(SystemExit) as wrapped_sysexit: - # we have to catch the SystemExit so the test execution does not stop - main.Run(["-o", "png", TEST_DATA_DIR]).run() + exit_code = main.Run(["-o", "png", TEST_DATA_DIR]).run() # Check that the right info message is shown to the user assert ( "Unable to determine Graphviz supported output formats." @@ -105,7 +101,7 @@ def test_graphviz_cant_determine_supported_formats( ) # Check that pyreverse actually made the call to create the diagram and we exit cleanly mock_writer.DiagramWriter().write.assert_called_once() - assert wrapped_sysexit.value.code == 0 + assert exit_code == 0 @mock.patch("pylint.pyreverse.main.Linker", new=mock.MagicMock()) @@ -134,9 +130,7 @@ def test_graphviz_unsupported_image_format(capsys: CaptureFixture) -> None: @pytest.mark.usefixtures("mock_graphviz") def test_verbose(_: mock.MagicMock, capsys: CaptureFixture[str]) -> None: """Test the --verbose flag.""" - with pytest.raises(SystemExit): - # we have to catch the SystemExit so the test execution does not stop - main.Run(["--verbose", TEST_DATA_DIR]).run() + main.Run(["--verbose", TEST_DATA_DIR]).run() assert "parsing" in capsys.readouterr().out @@ -177,9 +171,8 @@ def test_command_line_arguments_yes_no( Make sure that we support --module-names=yes syntax instead of using it as a flag. """ - with pytest.raises(SystemExit) as wrapped_sysexit: - main.Run(["--module-names=yes", TEST_DATA_DIR]).run() - assert wrapped_sysexit.value.code == 0 + exit_code = main.Run(["--module-names=yes", TEST_DATA_DIR]).run() + assert exit_code == 0 @mock.patch("pylint.pyreverse.main.writer") diff --git a/tests/pyreverse/test_pyreverse_functional.py b/tests/pyreverse/test_pyreverse_functional.py index 50e99056dd..ac8dab1b19 100644 --- a/tests/pyreverse/test_pyreverse_functional.py +++ b/tests/pyreverse/test_pyreverse_functional.py @@ -38,14 +38,13 @@ def test_class_diagrams(testfile: FunctionalPyreverseTestfile, tmp_path: Path) - else: source_roots = "" for output_format in testfile.options["output_formats"]: - with pytest.raises(SystemExit) as sys_exit: - args = ["-o", f"{output_format}", "-d", str(tmp_path)] - if source_roots: - args += ["--source-roots", source_roots] - args.extend(testfile.options["command_line_args"]) - args += [str(input_file)] - Run(args).run() - assert sys_exit.value.code == 0 + args = ["-o", f"{output_format}", "-d", str(tmp_path)] + if source_roots: + args += ["--source-roots", source_roots] + args.extend(testfile.options["command_line_args"]) + args += [str(input_file)] + exit_code = Run(args).run() + assert exit_code == 0 assert testfile.source.with_suffix(f".{output_format}").read_text( encoding="utf8" ) == (tmp_path / f"classes.{output_format}").read_text(encoding="utf8")