5

I am having trouble generating UMLs with pyreverse, in particular with composition relationships when classes are not part of the same module, and when using absolute imports.

To illustrate the problem, I have the following two modules a.py and b.py in the same package:

a.py:

from b import B


class A:
    def __init__(self, b):
        self.b: B = b

b.py:

class B:
    pass

When I run a pyreverse command in a terminal from the package, I get the following UML. It does not show the composition relationship between the two classes A and B:

enter image description here

However, when I do a relative import from .b import B in a.py, I get the expected result:

enter image description here

It seems like pyreverse does not recognize in the first case that classes B are the same. To resolve the problem, I have tried to add the absolute path of the package to the environment variable PYTHONPATH. However, this did not resolve the problem.

Does anybody know how I can make pyreverse generate the right relationships in the UMLs when classes are defined in different modules, and when using absolute imports?

I am using python 3.8.8 and pylint version 2.12.2.

GsB
  • 75
  • 4
  • I would guess you rather should report a bug to the authors of pyreverse. – qwerty_so Feb 10 '22 at 10:44
  • the fact that it works for a relative path suggests that there must be an issue with finding the right place or creating the path (issue with path trailers, with path separators, with unexported path variable, with caps on caps sensitive Os, bug, …). Does this help: https://stackoverflow.com/a/66046536/3723423 ? – Christophe Feb 10 '22 at 12:24
  • Thanks @Christophe - I properly set the path (and confirmed that the imports work) – GsB Feb 11 '22 at 18:37

1 Answers1

3

Edited answer following additional experimentation

I'm not a python expert, but after a couple more experiments I think I get the information that you need

First experiments : no package

In my first experiment, I used several modules that were not in a package. It appeared when using different ways to do the imports that pyreverse shows only the classes of the modules that are mentioned on the command line.

While I initially assumed that the issue was related to a stricter import syntax, it turned out that in reality it just worked as designed and documented in the man page: pyreverse shows in the diagram only the classes of the modules listed in the pyreverse command line.

So with a little project with, using almost your definitions in files main.py,a.py and b.py the easy workaround to get all the classes in the diagram was to use pyresverse main.py a.py b.py on a single command line. It generates the diagram:

enter image description here

It appears however that the result also depends on the PYTHONPATH, the current directory from where you call pyreverse and the path used to specify the module, as these elements can influence the finding of the right import.

Additional experiments : packages

I renamed then main.py into __init__.py to make a package. I can then use pyreverse providing only the directory name of the package (or . if it's the current working directory). Then all the files of the packages are processed for making the class diagram, withoout a need to enumerate them.

Using pyreverse on the package from within the package directory works with your import syntax and produced a similar diagram as above. However, running pyreverse from its parent directory produces 2 classes side by side without relationship. This means that we are dealing with two different classes B one of which was not found. Using the relative import solved the issue.

I found it by the way useful to add option -m y to disambiguate the class names by adding the module name:

enter image description here

I could verify experimentally: whenever I get unlinked classes, launching the module in python from the same current working directory as pyreverse caused an import error.

Christophe
  • 68,716
  • 7
  • 72
  • 138
  • Thank you for your answer... just a clarification: what did you mean by "An easy workaround is then to use pyresverse, with all the three files on a single command line." ? I am running pyreverse on all files already and when using absolute imports, I cannot generate the right UML. Were you able to generate the UML above with absolute import? – GsB Feb 11 '22 at 18:38
  • @GsB I have to come back on Monday about this: I made several tests and I think that I made this one using `from b …` syntax and that `import b` with only worked with b.B. The tests are on my desktop and I’m on the road. – Christophe Feb 11 '22 at 23:15
  • @GsB I confirm that I generated this diagram by adding a.py and b.py in one single pyreverse command, keeping your initial import syntax. When I execute it on a.py only, I get the left side of your first model. – Christophe Feb 14 '22 at 08:10
  • Thank you for the experimentation and the detailed explanation. It does seem like the issue was where `pyreverse` command was running from, and I was able to successfully generate the UML diagrams when running the command from the proper directory, as long as the absolute imports start from that directory. – GsB Feb 17 '22 at 01:18
  • 1
    Thanks Christophe for this very detailed analysis and experiment. In theory, `pyreverse -S a.py` should be enough to see both classes and the relationship between them in the diagram, without having to manually set the PYTHONPATH, as `pyreverse` extends `sys.path` itself. However due to a bug this did not work as intended. This will be fixed in V2.14.0 of `pylint` (with which `pyreverse` is shipped). – dudenr33 May 29 '22 at 09:48