abulka / pynsource

Reverse engineer Python source code into UML
http://pynsource.com
283 stars 40 forks source link

Support type annotations #75

Closed MShekow closed 4 years ago

MShekow commented 4 years ago

There are type annotations such as PEP-484 or PEP-526 which allow to statically type your Python code. Unfortunately, pynsource does not support those. Would it be possible to support them? In the current state, pynsource's use is somewhat limited for me, because it won't detect even simple associations, such as shown in this example:

class Restaurant:
    pass

class Customer:
    def __init__(self, restaurant: Restaurant):
        self.restaurant = restaurant

Best regards!

abulka commented 4 years ago

Thanks for raising this important enhancement proposal. I've been meaning to add this to Pynsource for a while, just waiting for someone to ask for it I guess.

I've just now added support for detecting type annotations on parameters to methods of classes which looks after the use case you have given.

Aside: Ironically, prior to the new commit, declaring the Restaurant class ahead of the Customer class would trigger Pynsource to already treat self.restaurant as a implicit reference to the Restaurant class - without needing type annotations - simply due to the convention that it is the same name with the first letter in uppercase. But with the new commit, you don't need to rely on this dubious convention I had in place, and the correct association is created, due to the restaurant: Restaurant type annotation.

I've also added type annotation detection to attribute assignments e.g.

class Customer:
    def __init__(self, restaurant):
        self.restaurant: Restaurant = restaurant
        self.fred: Fred

Please let me know if there are any other important use cases where type annotations need to be detected. Bear in mind that Pynsource skips as much Python code (that is being reverse engineered into UML) in its parsing as it can, because traditionally, it has only been interested in 'structure' and there was no need to parse deeply into expressions and bodies of code - saving complexity and time. I'm prepared to start parsing more of the incoming Python code if the use cases are strong enough.

MShekow commented 4 years ago

Thanks for getting back to me about this so quickly.

There a few things I find sub-optimal.

First: types are not shown in the diagrams (neither in the "UML" view, nor in the "PlantUML" view. For instance, I would expect the diagram to show the attribute restaurant: Restaurant and not just restaurant.

Second: the association arrows (e.g. composition) seem to not have labels in the UML view at all, but they do have the label of the type (rather than the name of the variable) in the PlantUML view (I would expect attributes of this type to not even be part of the class-box, e.g. I would have expected that the Customer box has only the __init__ method, and no attributes, with a composition-arrow labelled restaurant that goes from Customer to the Restaurant class box.)

Third: consider the following example:

class Mammal:
    def __init__(self):
        self.some_prop = 1

class Dog(Mammal):
    def __init__(self, can_bark: bool):
        super().__init__()
        self.can_bark = can_bark

class SomeClass:
    def __init__(self, other: Dog):
        self.other = other

The PlantUML markup is less than optimal: image

abulka commented 4 years ago

First: types are not shown in the diagrams (neither in the "UML" view, nor in the "PlantUML" view. For instance, I would expect the diagram to show the attribute restaurant: Restaurant and not just restaurant.

Agreed, this would be a nice feature, have created #77.

Second: the association arrows (e.g. composition) seem to not have labels in the UML view at all, but they do have the label of the type (rather than the name of the variable) in the PlantUML view

Agreed this is not right, have created #78 and disabled line labels in PlantUML view for now, till this is resolved.

Adding labels to lines in UML view would be good too, have created #76.

(I would expect attributes of this type to not even be part of the class-box, e.g. I would have expected that the Customer box has only the init method, and no attributes, with a composition-arrow labelled restaurant that goes from Customer to the Restaurant class box.)

Just because an attribute is visualised as a line, doesn't mean it should disappear from being listed as an attribute of a class. Perhaps your way could be an option at some time in the future.

init() is missing arguments (and their types)

77

There is a funny bool class :) pynsource could have inferred that the type of Mammal.some_prop is int

The latest commit fixes the 'funny bool class' issue, and the following built in classes are no longer visualised:

BUILT_IN_TYPES = ('int', 'float', 'bool', 'str', 'bytes', 'List', 'Set', 'Dict', 'Tuple', 'Optional',
    'Callable', 'Iterator', 'Union', 'Any', 'Mapping', 'MutableMapping', 'Sequence', 'Iterable', 'Set',
    'Match', 'AnyStr', 'IO', 'Callable', 'TypeVar')  # I think I identified them all!

As this issue is initially about type annotations, which has been addressed, I am closing this particular issue.

Thanks for the feedback!