Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create common Printer base class for pyreverse and improve typing. #4731

Merged

Conversation

DudeNr33
Copy link
Collaborator

Steps

  • Write a good description on what the PR does.

Description

This is the first MR which shall split MR #4615 into smaller and more easily reviewable parts.
It involves the necessary refactoring which is needed to prepare for the new features (PlantUML support and colorization).
It also adds type annotations.

Summary of changes:

  • Rename vcgutils.py to printer.py and introduce a Printer base class which defines the common interface for the different backends.
  • Adapt VCGPrinter to adhere to the new printer base class
  • Add a DotPrinter class which essentially implements the features of pylint.graph.DotBackend. The latter will be removed in a future MR, as it is currently also used by the ImportsChecker and I wanted to keep this MR smaller and focused (it is already not very small...).
  • Add type annotations for the Printer class and its children. This also uncovered some problems where mypy correctly complained, so I had to add specialised PackageEntity and ClassEntity child classes in diagrams.py.
  • Added tests for VCG output, which was missing before
  • Updated the test data as the new Printer backends now outputs some more information. The final image produced by Graphviz is still the same.

Type of Changes

Type
🔨 Refactoring

@coveralls
Copy link

coveralls commented Jul 20, 2021

Coverage Status

Coverage increased (+0.3%) to 92.573% when pulling 5f624b7 on DudeNr33:pyreverse-refactoring-printer into 2ad8247 on PyCQA:main.

Copy link
Member

@Pierre-Sassoulas Pierre-Sassoulas left a comment

Choose a reason for hiding this comment

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

Just a small comment, and I did not look in detail but the new typing is 🔥 , thanks! I'll review when this is no longer a draft :)

pylint/pyreverse/utils.py Outdated Show resolved Hide resolved
@DudeNr33 DudeNr33 marked this pull request as ready for review July 21, 2021 03:08
@Pierre-Sassoulas Pierre-Sassoulas added Enhancement ✨ Improvement to a component Maintenance Discussion or action around maintaining pylint or the dev workflow labels Jul 25, 2021
Copy link
Member

@Pierre-Sassoulas Pierre-Sassoulas left a comment

Choose a reason for hiding this comment

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

Hey, thank you for this work on this and sorry for the delay before reviewing. Could we reorganize and move the code around first then do the modification on it ? I'd be more confident if there was a proper diff for the change in the printer.py file, right now everything is considered new by git and it's hard to tell what changed exactly :)

body: Optional[str] = None


class Printer:
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
class Printer:
class Printer(abc.ABC):

It's more explicit than raising NotImplementErrors :)

raise NotImplementedError


class VCGPrinter(Printer):
Copy link
Member

Choose a reason for hiding this comment

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

We can create a new file for VCGPrinter and another one for DotPrinter :) (It would also help with the diff and possible future conflict if vcgutils.py is recognized as the old file for it).

def save(self) -> None:
"""write to disk"""
self.printer.generate(self.file_name)


class DotWriter(DiagramWriter):
Copy link
Member

Choose a reason for hiding this comment

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

Maybe we could start with a first merge request that create the following architecture and move relevant part of the code without modifying anything:

pyreverse:
    writter:
        diagram_writter.py
        dot_writter.py
        vcg_writter.py
    printer:
        dot_printer.py
        vcg_printer.py

What do you think ?

Copy link
Member

Choose a reason for hiding this comment

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

Thank you for reorganizing the code the diff is manageable now :)

@DudeNr33
Copy link
Collaborator Author

Thanks for the feedback. I will implement it when #4745 is merged and I did a rebase.

super().__init__(title=title, node=node)
self.attrs = None
self.methods = None

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Adding the type hints revealed that it was necessary to distinguish between a PackageEntity and a ClassEntity, because the ClassEntity has additional attributes that were dynamically added in the previous code, which confused mypy.

color="black",
)

def get_class_properties(self, obj: ClassEntity) -> NodeProperties:
"""get label and shape for classes.
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

get_values is now get_class_properties.

get_package_properties is currently trivial and the color is hardcoded, but this will change when the new feature for automatic coloring are merged.

dict(arrowstyle="solid", backarrowstyle="none", textcolor="green"),
]
DiagramWriter.__init__(self, config, styles)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The __init__ is now obsolete. It formerly defined the styles to apply to the different types of edges for displaying relationships.
These are now defined in the ARROWS dictionary in the module of the corresponding printer class.

@DudeNr33
Copy link
Collaborator Author

DudeNr33 commented Aug 1, 2021

I implemented most of the feedback.
I reintroduced the module vcgutils.py, which should make it now easier to diff.
As soon as the review is finished, I would suggest to make a last commit where this file is simply renamed to vcg_printer.py.

Regarding the last suggestion (doing a first MR to implement the structure

pyreverse:
    writter:
        diagram_writter.py
        dot_writter.py
        vcg_writter.py
    printer:
        dot_printer.py
        vcg_printer.py

):

If the current cleanup is not enough to properly review the changes, let me know and I can do that.

Copy link
Member

@Pierre-Sassoulas Pierre-Sassoulas left a comment

Choose a reason for hiding this comment

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

Thank you for your work on this, very impressive ! Ad thank you for making the diff manageable. I left some minor comment. We can still do the architecture change later if you feel like it (probably after merging the other MR as the rebase will be hard to do if we do it before).

)

def _inc_indent(self):
"""increment indentation"""
self._indent = " %s" % self._indent
self._indent += " "
Copy link
Member

Choose a reason for hiding this comment

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

👍

@@ -140,12 +158,14 @@ def get_values(self, obj):
)

if func.args.args:
args = [arg for arg in func.args.args if arg.name != "self"]
argument_list = [
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
argument_list = [
arguments: List[str] = [

Personal preference but when naming an iterable I like a plural word so I can do for argument in arguments later on.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I agree, changed it.

EdgeType.ASSOCIATION: dict(
fontcolor="green", arrowtail="none", arrowhead="diamond", style="solid"
),
EdgeType.USES: dict(arrowtail="none", arrowhead="open"),
Copy link
Member

Choose a reason for hiding this comment

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

Maybe we could create enums for fontcolor, arrowtail, style and arrowhead ?

pylint/pyreverse/dot_printer.py Outdated Show resolved Hide resolved
pylint/pyreverse/dot_printer.py Outdated Show resolved Hide resolved
pylint/pyreverse/vcgutils.py Show resolved Hide resolved
pylint/pyreverse/writer.py Outdated Show resolved Hide resolved
def save(self) -> None:
"""write to disk"""
self.printer.generate(self.file_name)


class DotWriter(DiagramWriter):
Copy link
Member

Choose a reason for hiding this comment

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

Thank you for reorganizing the code the diff is manageable now :)

@DudeNr33
Copy link
Collaborator Author

DudeNr33 commented Aug 1, 2021

Thank you very much for your review!
The only part I missed in the last push is the request for creating Enums for fontcolor, arrowhead etc.

I also need to rename the vcgutils.py into vcg_printer.py, so please don't merge yet, will do that in the next few days!

@Pierre-Sassoulas
Copy link
Member

No problem, just request a new review when the code is ready :)

@DudeNr33
Copy link
Collaborator Author

DudeNr33 commented Aug 3, 2021

I thought about implementing enums for arrowstyle, arrowhead and so on a little longer.
In my opinion there is not much benefit from it in this case. We would have to create those enums for each printer separately, as the names and values for the arguments are different. And then they would only be used in the ARROWS dictionary, of which the items are not really individually processed:

# in vcgutils.py
attributes = ARROWS[type_]
if label:
    attributes["label"] = label
self._write_attributes(
    EDGE_ATTRS,
    **attributes,
)

# in dot_printer.py
arrowstyle = ARROWS[type_]
attrs = [f'{prop}="{value}"' for prop, value in arrowstyle.items()]

I think we would just clutter the modules with enum definitions, without gaining much in terms of readability or reusability.

vcgutils.py has been renamed to vcg_printer.py.

Copy link
Member

@Pierre-Sassoulas Pierre-Sassoulas left a comment

Choose a reason for hiding this comment

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

Thank you for making a first step easier to review :) !

@Pierre-Sassoulas Pierre-Sassoulas merged commit 7bb5043 into pylint-dev:main Aug 3, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Enhancement ✨ Improvement to a component Maintenance Discussion or action around maintaining pylint or the dev workflow
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants