Code Monkey home page Code Monkey logo

py2puml's People

Contributors

doyou89 avatar jjerphan avatar jonykalavera avatar justkiddingcode avatar lucsorel avatar pre-commit-ci[bot] avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

py2puml's Issues

add namespace structure in the plantuml result

Using PlantUML namespaces enables:

  • to group UML items by packages and modules (already the case)
  • to nest sub-packages within their parent package

Py2puml currently outputs this diagram:

Whereas the nested namespaces could be output, improving the understanding of the codebase organization:

The attribute assigned in constructor appears more than once

When an attribute is assigned more than once in class constructor

class DupAttr:
    def __init__(self, name=None):
        self.name = name
        if not self.name:
            self.name = ''

The attribute(here "name") appears several times.

@startuml
class umlclass.DupAttr {
  name: None
  name: None
}
@enduml

use class member arrows to start relationship between a class field of a type and its type UML class

#3636 Arrows from/to class fields

Right now, PlantUML would describe the composition relationship between a person and its address like this (an arrow between the 2 classes):

@startuml
class Address {
 street: str
 zipcode: str
 city: str
}

class Person {
 name: str
 address: Address
}

Person -> Address
@enduml

But it would be interesting to have the relationship starting from the Person.address field:

@startuml
class Address {
 street: str
 zipcode: str
 city: str
}

class Person {
 name: str
 address: Address
}

Person::address -> Address
@enduml

Although the rendering is not so relevant on this example.

Caution:

  1. what behavior do we want when several fields of a class link to the same class? One specific line per field? One general line between the 2 classes? Readability matters.

Deduplicate Attributes

Hi @lucsorel, given the following example where the class's attribute can have different values depending on an argument, the uml output shows two copies of the same attribute (hands). Not really a big deal since I rarely have if else statements in my constructor but would be good to know if it can be avoided.

class Human(Mammals, BiPedal):
    def __init__(self, long_hands: bool):
        Mammals.__init__(self)
        BiPedal.__init__(self)

        if long_hands:
            self.hands: Hands = Hands(length=4)
        else:
            self.hands: Hands = Hands(length=2)

    def eat(self) -> None:
        pass

    def move(self) -> None:
        pass

image

Refactor codebase to use AST

py2puml uses internally a mix of code inspection and Abstract Syntax Tree parsing. The whole code based should be refactored to use AST parsing only as it is more robust and straightforward.

cannot use types defined in preceding classes as type annotations

Dear Developers,
I have encountered the following error when I used py2puml.

I was trying to draw uml diagram of the deepmd package with the command,

py2puml deepmd/train deepmd.train

I have the error messages:

Traceback (most recent call last):
  File "/data/ybzhuang/conda/env/dpff/bin/py2puml", line 8, in <module>
    sys.exit(run())
  File "/data/ybzhuang/conda/env/dpff/lib/python3.9/site-packages/py2puml/cli.py", line 17, in run
    print(''.join(py2puml(args.path, args.module)))
  File "/data/ybzhuang/conda/env/dpff/lib/python3.9/site-packages/py2puml/py2puml.py", line 12, in py2puml
    inspect_package(
  File "/data/ybzhuang/conda/env/dpff/lib/python3.9/site-packages/py2puml/inspection/inspectpackage.py", line 20, in inspect_package
    inspect_module(
  File "/data/ybzhuang/conda/env/dpff/lib/python3.9/site-packages/py2puml/inspection/inspectmodule.py", line 60, in inspect_module
    inspect_domain_definition(definition_type, root_module_name, domain_items_by_fqn, domain_relations)
  File "/data/ybzhuang/conda/env/dpff/lib/python3.9/site-packages/py2puml/inspection/inspectmodule.py", line 47, in inspect_domain_definition
    inspect_class_type(
  File "/data/ybzhuang/conda/env/dpff/lib/python3.9/site-packages/py2puml/inspection/inspectclass.py", line 99, in inspect_class_type
    attributes = inspect_static_attributes(
  File "/data/ybzhuang/conda/env/dpff/lib/python3.9/site-packages/py2puml/inspection/inspectclass.py", line 78, in inspect_static_attributes
    attr_type, full_namespaced_definitions = shorten_compound_type_annotation(attr_raw_type, module_resolver)
  File "/data/ybzhuang/conda/env/dpff/lib/python3.9/site-packages/py2puml/parsing/astvisitors.py", line 215, in shorten_compound_type_annotation
    raise ValueError(f'Could not resolve type {compound_type_part} in module {module_resolver.module}: it needs to be imported explicitely.')
ValueError: Could not resolve type deepmd.train.run_options.HVD in module <module 'deepmd.train.run_options' from '/data/ybzhuang/repositories/deepmd-kit/deepmd/train/run_options.py'>: it needs to be imported explicitely.

How should I bypass this implicit-typing errors?

extract methods

First: awesome package, thx a lot ^^

Second: am I not finding the option, or is is currently not possible to extract the methods of classes?

Best regards
mskoenz

TypeError when trying to run on current directory

I installed with python -m pip install py2puml

I run py2puml . . in a directory containing my python files.

I get the following error:

TypeError: the 'package' argument is required to perform a relative import for '..bk'

Tried many things but cannot get it to work.

py2puml fails where python -m py2puml succeeds

I have examples files that are stored according to this hierarchy

test
   __init__.py
   test.py

I struggled to run the .exe as I would get either ModuleNotFoundError: No module named 'test.test' or an empty file depending on the arguments provided:

 py2puml.exe test\test test
@startuml test
!pragma useIntermediatePackages false

footer Generated by //py2puml//
@enduml

it turned out that py2puml.exe test test doesn't work as expected but python -m py2puml test test does.

Would it be possible for you to have a look at it?

edit: I am on windows Windows 10 Enterprise v22H2 with python 3.11.6

Document that component attribute must be typed to infer composition relationship

Hi,

I tested a simple composition relationship (Dolphin and DorsalFin) and it doesn't seem to work. Only inheritance relationship is captured in the resulting plantUML. Any idea how the composition relationship can be captured?

class Mammals:
    def __init__(self):
        pass

    def eat(self):
        pass

    def move(self):
        pass

class Dolphin(Mammals):
    def __init__(self):
        super(Dolphin, self).__init__()
        self.fin = DorsalFin(length=1)

    def move(self):
        pass


class DorsalFin:
    def __init__(self, length):
        self.length = length

    def wiggle(self):
        pass

handle namedtuples

  • the _fields attribute enables to detect classes defined with the collections.namedtuple factory method, its keys are the class attributes
  • the attributes are not typed: specify any or don't specify one
from collections import namedtuple

Circle = namedtuple('Circle', ['x', 'y', 'radius'], defaults=[1])

# Circle._fields: ('x', 'y', 'radius')
# Circle._fields_defaults: {'radius': 1}

ValueError raised when compound types have digits

Hey,

Here's a simple example that yield a ValueError:

class int64:
    def __init__(self, value: int) -> None:
        self.value: int = value

class Main:
    def __init__(self):
        self.a: list[int64] = [int64(0)] * 5
ValueError: list[int64] seems to be an invalid type annotation

I think fixing it is as simple as adding 0-9 in the compiled regex

Kinda critical for applications using numpy, as numpy types usually contain digits (i.e: numpy.typing.NDArray[numpy.float64])

`ModuleNotFoundError` when library folder uses a library folder on the same level

Hi,

I have following file structure:

project/
โ””โ”€โ”€ my_model/
    โ”œโ”€โ”€ __init__.py
    โ”œโ”€โ”€ module_model_main.py
    โ”œโ”€โ”€ module_model_1.py
    โ””โ”€โ”€ etcetera
โ””โ”€โ”€ template/
    โ””โ”€โ”€ utility/
        โ”œโ”€โ”€ __init__.py
        โ”œโ”€โ”€ module_utility_1.py
        โ””โ”€โ”€ etcetera
    โ”œโ”€โ”€ __init__.py
    โ”œโ”€โ”€ module_template_1.py
    โ””โ”€โ”€ etcetera
โ”œโ”€โ”€ __init__.py
โ””โ”€โ”€ etcetera

my_model/module_model_main.py imports modules from:

  • template/module_template_1.py (and etcetera)
  • template/utility/module_utility_1.py (and etcetera)
  • my_model/module_model_1.py (and etcetera)

where the latter imports from template/module_template_1.py (and etcetera) as well.

When running the code at the bottom of this post (from the project/ folder), I get a specific ModuleNotFoundError:

File "[...]\my_model\module_model_main.py", line 17, in <module>
    from my_model.module_model_1 import ClassModel1
  File "[...]\my_model\module_model_1.py", line 1, in <module>        
    from template.module_template_1 import ClassTemplate1
  File "[...]\template\module_template_1.py", line 9, in <module>
    from utility.module_utility_1 import function_utility
ModuleNotFoundError: No module named 'utility'

At the highest execution level in my_model/module_model_main.py where the error also occurs, py2puml already succeeded past the following imports:

from template.module_template_1 import ClassTemplate1
from template.module_template_2 import ClassTemplate2
from template.module_template_3 import ClassTemplate3
from template.module_template_4 import ClassTemplate4
from template.module_template_5 import ClassTemplate5
from template.utility.module_utility_1 import ClassUtility1

As such, it errors on from my_model.module_model_1 import ClassModel1. How can it be that py2puml recognized template.utility with the explicit imports but not within this line? Can it be because it is on the same level? Is there a solution?


Executed code:

from py2puml.py2puml import py2puml
import findspark

findspark.init()  # Needed to allow finding pyspark as a module

print(''.join(
    py2puml('my_model/', 'my_model')
))

Can run py2puml only on py2puml

Hi,

I'm able to execute your example, but was not able to run it without errors on my own code.

So, I made a small experiment. I placed a minimal class called myminiclass.py with the below content within the folder py2puml/domain

class MyMiniClass(object):
    def __init__(self, instance_name: str):
        self.name = instance_name

    def get_name(self) -> str:
        return self.name

The effect is now when running py2puml py2puml/domain py2puml.domain I get the error below:

Traceback (most recent call last):
  File "C:\Users\myuser\.conda\envs\myenv\lib\runpy.py", line 197, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "C:\Users\myuser\.conda\envs\myenv\lib\runpy.py", line 87, in _run_code
    exec(code, run_globals)
  File "C:\Users\myuser\.conda\envs\myenv\Scripts\py2puml.exe\__main__.py", line 7, in <module>     
  File "C:\Users\myuser\.conda\envs\myenv\lib\site-packages\py2puml\cli.py", line 17, in run        
    print(''.join(py2puml(args.path, args.module)))
  File "C:\Users\myuser\.conda\envs\myenv\lib\site-packages\py2puml\py2puml.py", line 17, in py2puml
    domain_item_module: ModuleType = import_module(name)
  File "C:\Users\myuser\.conda\envs\myenv\lib\importlib\__init__.py", line 127, in import_module    
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1030, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1007, in _find_and_load
  File "<frozen importlib._bootstrap>", line 984, in _find_and_load_unlocked
ModuleNotFoundError: No module named 'py2puml.domain.myminiclass'

When I delete the file everything is working again for the example. Any ideas / suggestions?

Update:

I have one own python package which I installed in my conda environment. For this I'm able to run py2puml as long as I'm in the conda environment it is installed in. But when I create a new conda environment without it I get the same kind of error again.

support optional typing

py2puml fails to inspect a dataclass using Optional as follows:

from dataclasses import dataclass
from typing import Optional
@dataclass
class MyData:
  data: Optional[bool]

ValueError: Could not resolve type typing.Union in module <module 'tests.modules.withoptional' from '.../py2puml/tests/modules/withoptional.py'>: it needs to be imported explicitly.

It is because Optional[bool] is dynamically converted into Union[bool, None] without adding both typing.Union and typing.Nonetype in the module wrapped by the ModuleResolver.

simplify the namespace of classes

With recent versions of PlantUML, the namespace section at the beginning of diagram files is not necessary anymore. Moreover, the visual grouping of namespaces (see namespace py2puml.domain {) can be replaced by the !pragma useIntermediatePackages false directive.

@startuml py2puml.domain
' [REMOVE]: this 'namespace' section is no longer necessary
namespace py2puml.domain {
  namespace package {}
  namespace umlclass {}
  namespace umlitem {}
  namespace umlenum {}
  namespace umlrelation {}
}

' [ADD]: the following directive makes sure packages are grouped for readability sake
!pragma useIntermediatePackages false

' the contents can start here
class py2puml.domain.package.Package {
  name: str
  children: List[Package]
  items_number: int
}
' [...]
class py2puml.domain.umlrelation.UmlRelation {
  source_fqn: str
  target_fqn: str
  type: RelType
}
py2puml.domain.package.Package *-- py2puml.domain.package.Package
[...]
py2puml.domain.umlrelation.UmlRelation *-- py2puml.domain.umlrelation.RelType
footer Generated by //py2puml//
@enduml

That means that:

  • py2puml/export/namespace.py can be removed from the codebase (and unit tests too)
  • the writing of the diagram file can be a process streamed as the code inspection goes

It is always good news when some code can be removed ๐Ÿ˜ƒ

Docstring support

I have a feature request for this project that I would like to propose. I think, the addition of this feature would greatly enhance the project's functionality and usability.

Description

The proposed feature is to incorporate a standardized function, such as doc, that enables retrieving the docstrings for classes and methods and seamlessly inserting them into the UML diagram. This addition would significantly contribute to clarifying the purpose and functionality of classes within the UML representation. It would provide developers with a convenient and consistent way to access the documentation, resulting in improved understanding and maintainability.

Previous Status

Furthermore, I am unsure whether this feature has been requested previously. If it has, I kindly request information regarding its current status or any ongoing efforts to implement it.

Make Py2Puml work with Annotated (e.g. such that pydantic dataclasses work with automatic range checks)

Hello!

I am an eager py2puml user and thanks to py2puml it is possible to create complex data structures without losing the overview!

I recently switched to use pydantic dataclasses instead of the "normal" dataclasses, because pydantic has a HUGE list of advantages over the standard dataclasses. The main advantage for me is:

  1. The syntax is the same as the standard dataclass
  2. You can add numeric ranges that are automatically checked.

The following example works already:

from pydantic.dataclasses import dataclass

@dataclass
class A:
    number: int

Beautiful! Now, let's add a constraint to that:

from pydantic.dataclasses import dataclass
from pydantic import Field

@dataclass
class A:
    positiveNumber: int = Field(gt=0)

Also that works! Although it's a pydantic specific feature, py2puml can still create the puml because pydantic uses the normal dataclass in the background. That is great!

But now, pydantic also provides types directly, like PositiveInt. Lets try it:

from pydantic.dataclasses import dataclass
from pydantic import Field, PositiveInt

@dataclass
class A:
    positiveNumber: PositiveInt

This crashes!

File "C:\src\pkvgkv\.venv\Lib\site-packages\py2puml\parsing\compoundtypesplitter.py", line 60, in __init__
    raise ValueError(f'{compound_type_annotation} seems to be an invalid type annotation')      
ValueError: typing.Annotated[int, Gt(gt=0)] seems to be an invalid type annotation

Why is this a problem? I could just use the first method? Not in this example:

from pydantic.dataclasses import dataclass
from pydantic import Field, PositiveInt

@dataclass
class A:
    positiveNumbers: list[PositiveInt]

Here I have to use the PositiveInt from pydantic, otherwise I can't constraint the elements of the list :(

In the background, pydantic creates these types with the following vanilla python functionality:

from annotated_types import Gt
from typing_extensions import Annotated

PositiveInt = Annotated[int, Gt(0)]

This means, if the following example compiles (without any pydantic code), then the pydantic code would also compile:

from annotated_types import Gt
from typing_extensions import Annotated


@dataclass
class A:
    positiveNumber: Annotated[int, Gt(0)]

This means, if py2puml would support "Annotated" from typing_extensions, then we could use all the nice pydantic features with robust dataclasses and automatic range checking and everything and STILL have the great py2puml overview!

For me, it is not so important that the puml also shows the range etc. It would be totally sufficient as first goal to still compile and show that it is an int.

So:

  1. Goal: py2puml should still compile the puml even when "Annotated" is used.
  2. Goal: py2puml could additionally show the information inside the Annotated in the puml

That would be amazing!

Update 1: Quick and dirty solution:

I implemented a quick and dirty solution locally. In the compoundtypesplitter.py I imported

from re import sub as re_sub

and then in line 58 I added:

def __init__(self, compound_type_annotation: str, module_name: str):
    ## NEW CODE:
    # Iterate over the annotation, replace all occurances of typing.Annotated[type, extraInfo]
    # with type (drop the extra info for now to achieve goal 1)
    while('typing.Annotated' in compound_type_annotation):
        compound_type_annotation = re_sub(r'typing\.Annotated\[(.*?)\,(.*?)]', r'\1', compound_type_annotation)
    
    # END OF NEW CODE    
    resolved_type_annotations = ...

This resolves goal 1 for me, but I think this is not a nice solution. Maybe the mapping of the typing.Annotated string to the internal type should not be done inside the CompoundTypeSplitter, as the CompoundTypeSplitter should already recieve the "clean" input. Also it may be luck that the CompoundTypeSplitter recieved the string in the first place, as the typing.Annotation itself is not a compound.

Anyway, this solution even works with more complicated pydantic structures like:

from pydantic import PositiveInt

@dataclass
class A:
    number: Optional[list[list[PositiveInt]]]

Maybe you could take this example as idea and implement it correctly at the correct place? :)

Update 2: "Less dirty" solution created as PR:

I implemented now a version that is a bit less dirty (e.g. separate function) and it also not only works for pydantic types but also for custom types, e.g. these two variants will both work:

from pydantic import Field, PositiveInt
from typing_extensions import Annotated

@dataclass
class A:
    number: list[Optional[PositiveInt]]

CrazyIntWithCustomConstraints = Annotated[int, Field(ge=2, lt=25)]

@dataclass
class B:
    number: list[Optional[CrazyIntWithCustomConstraints]]

Please check #66 , thanks :)

ModuleNotFoundError: No module named 'withrootnotincwd'

Dear @lucsorel, @doyou89, @jonykalavera, @jjerphan,

While trying to generate UML content for "withrootnotincwd" module that is provided under /tests/modules/withrootnotincwd, I am encountering a ModuleNotFoundError.

Error below:

py2puml/asserts.py:15: in assert_py2puml_is_stringio
    puml_content = list(py2puml(domain_path, domain_module))
py2puml/py2puml.py:12: in py2puml
    inspect_package(
py2puml/inspection/inspectpackage.py:19: in inspect_package
    domain_item_module: ModuleType = import_module(name)
/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/importlib/__init__.py:127: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
<frozen importlib._bootstrap>:1014: in _gcd_import
    ???
<frozen importlib._bootstrap>:991: in _find_and_load
    ???
<frozen importlib._bootstrap>:975: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:671: in _load_unlocked
    ???
<frozen importlib._bootstrap_external>:783: in exec_module
    ???
<frozen importlib._bootstrap>:219: in _call_with_frames_removed
    ???
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

    from dataclasses import dataclass
    
>   from withrootnotincwd.point import Point
E   ModuleNotFoundError: No module named 'withrootnotincwd'

tests/modules/withrootnotincwd/segment.py:3: ModuleNotFoundError

Note: This was identified by pytest.

After noticing this error, I tried running py2puml via CLI and encounter the same error:

(venv) supratiks-mbp:py2puml supratiksarkar$ py2puml tests/modules/withrootnotincwd/ tests.modules.withrootnotincwd
Traceback (most recent call last):
  File "/Users/supratiksarkar/Desktop/Prod/venv/bin/py2puml", line 8, in <module>
    sys.exit(run())
  File "/Users/supratiksarkar/Desktop/Prod/venv/lib/python3.8/site-packages/py2puml/cli.py", line 24, in run
    print(''.join(py2puml(args.path, args.module)))
  File "/Users/supratiksarkar/Desktop/Prod/venv/lib/python3.8/site-packages/py2puml/py2puml.py", line 12, in py2puml
    inspect_package(
  File "/Users/supratiksarkar/Desktop/Prod/venv/lib/python3.8/site-packages/py2puml/inspection/inspectpackage.py", line 19, in inspect_package
    domain_item_module: ModuleType = import_module(name)
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/importlib/__init__.py", line 127, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1014, in _gcd_import
  File "<frozen importlib._bootstrap>", line 991, in _find_and_load
  File "<frozen importlib._bootstrap>", line 975, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 671, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 783, in exec_module
  File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
  File "/Users/supratiksarkar/Desktop/Prod/py2puml/tests/modules/withrootnotincwd/segment.py", line 3, in <module>
    from withrootnotincwd.point import Point
ModuleNotFoundError: No module named 'withrootnotincwd'

In order to fix this error, I invested the segments.py file under test/modules/withrootnotincwd and notice that changing the following line:

from withrootnotincwd.point import Point

to

from .point import Point

fixes the issue.

Output after fixing the file:

(venv) supratiks-mbp:py2puml supratiksarkar$ py2puml tests/modules/withrootnotincwd/ tests.modules.withrootnotincwd
@startuml tests.modules.withrootnotincwd
namespace tests.modules.withrootnotincwd {
  namespace point {}
  namespace segment {}
}
class tests.modules.withrootnotincwd.point.Point {
  x: float
  y: float
}
class tests.modules.withrootnotincwd.segment.Segment {
  a: Point
  b: Point
}
tests.modules.withrootnotincwd.segment.Segment *-- tests.modules.withrootnotincwd.point.Point
footer Generated by //py2puml//
@enduml

(venv) supratiks-mbp:py2puml supratiksarkar$

Can you please take a look at it?

Thank you so much!

Add support for SQLAlchemy models

Issue to add support for SQLAlchemy models. This will include considerations about 1-1, 1-* and - relationships. Additionally, it should also consider inheritance.

[Bug] parsed an inherited constructor from another module (namespace error)

Problem

A keyword parameter of the parent class have an annotation, but the type of this parameter does not revealed in the file of child class.

A.py

class SystemSession:
    pass

class BaseShell:
    def __init__(self, session: SystemSession = None):
        self.session = session
        self.is_started = False

B.py

from .A import BaseShell

class Nfvo(BaseShell):
    pass

Error Message

Traceback (most recent call last):
  File "/home/doyou89/miniconda3/envs/jupyter/lib/python3.8/runpy.py", line 194, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/home/doyou89/miniconda3/envs/jupyter/lib/python3.8/runpy.py", line 87, in _run_code
    exec(code, run_globals)
  File "/p4_ws/doyou89.jung/workspace/projects/github/py2puml/py2puml/__main__.py", line 4, in <module>
    run()
  File "/p4_ws/doyou89.jung/workspace/projects/github/py2puml/py2puml/cli.py", line 17, in run
    print(''.join(py2puml(args.path, args.module)))
  File "/p4_ws/doyou89.jung/workspace/projects/github/py2puml/py2puml/py2puml.py", line 18, in py2puml
    inspect_module(
  File "/p4_ws/doyou89.jung/workspace/projects/github/py2puml/py2puml/inspection/inspectmodule.py", line 61, in inspect_module
    inspect_domain_definition(definition_type, root_module_name, domain_items_by_fqn, domain_relations)
  File "/p4_ws/doyou89.jung/workspace/projects/github/py2puml/py2puml/inspection/inspectmodule.py", line 46, in inspect_domain_definition
    inspect_class_type(
  File "/p4_ws/doyou89.jung/workspace/projects/github/py2puml/py2puml/inspection/inspectclass.py", line 97, in inspect_class_type
    instance_attributes, compositions = parse_class_constructor(class_type, class_type_fqn, root_module_name)
  File "/p4_ws/doyou89.jung/workspace/projects/github/py2puml/py2puml/parsing/parseclassconstructor.py", line 29, in parse_class_constructor
    visitor.visit(constructor_ast)
  File "/home/doyou89/miniconda3/envs/jupyter/lib/python3.8/ast.py", line 371, in visit
    return visitor(node)
  File "/p4_ws/doyou89.jung/workspace/projects/github/py2puml/py2puml/parsing/astvisitors.py", line 98, in generic_visit
    NodeVisitor.generic_visit(self, node)
  File "/home/doyou89/miniconda3/envs/jupyter/lib/python3.8/ast.py", line 379, in generic_visit
    self.visit(item)
  File "/home/doyou89/miniconda3/envs/jupyter/lib/python3.8/ast.py", line 371, in visit
    return visitor(node)
  File "/p4_ws/doyou89.jung/workspace/projects/github/py2puml/py2puml/parsing/astvisitors.py", line 109, in visit_FunctionDef
    self.generic_visit(node)
  File "/p4_ws/doyou89.jung/workspace/projects/github/py2puml/py2puml/parsing/astvisitors.py", line 98, in generic_visit
    NodeVisitor.generic_visit(self, node)
  File "/home/doyou89/miniconda3/envs/jupyter/lib/python3.8/ast.py", line 379, in generic_visit
    self.visit(item)
  File "/home/doyou89/miniconda3/envs/jupyter/lib/python3.8/ast.py", line 371, in visit
    return visitor(node)
  File "/p4_ws/doyou89.jung/workspace/projects/github/py2puml/py2puml/parsing/astvisitors.py", line 149, in visit_Assign
    self.extend_relations(full_namespaced_definitions)
  File "/p4_ws/doyou89.jung/workspace/projects/github/py2puml/py2puml/parsing/astvisitors.py", line 82, in extend_relations
    self.uml_relations_by_target_fqn.update({
  File "/p4_ws/doyou89.jung/workspace/projects/github/py2puml/py2puml/parsing/astvisitors.py", line 85, in <dictcomp>
    if target_fqn.startswith(self.root_fqn) and target_fqn not in self.uml_relations_by_target_fqn
AttributeError: 'NoneType' object has no attribute 'startswith'

Cause

In the parent class "BaseShell", session's type is "SystemSession".
But this "SystemSession" does not exist in the namespace of B.py

Doesn't work with python 3.7

pyproject.toml says python = "^3.7", but ast.get_source_segment has been introduced in python 3.8. It doesn't work with 3.7:

  File "/usr/local/lib/python3.7/dist-packages/py2puml/parsing/astvisitors.py", line 4, in <module>
    from ast import (
ImportError: cannot import name 'get_source_segment' from 'ast' (/usr/lib/python3.7/ast.py)

support union types in type annotations

When using Union types annotations, e.g. int | float the script crashes with an error

File "/xxxx/py2puml/parsing/astvisitors.py", line 215, in shorten_compound_type_annotation

ValueError: Could not resolve type int | float in module

It'd be useful to be able to support this.

Newbie question: how to run py2puml in certain hierarchy?

I humbly apologize for an absolute newbie question, but I seem to be unable to get py2puml to produce any usable result (apart from empty "@startuml/@enduml"):

As a work in progress, I have following file structure:
C:\Folder_A\Folder_AA\Folder_Project\module1.py
C:\Folder_A\Folder_AA\Folder_Project\module2.py
C:\Folder_A\Folder_AA\Folder_Project\subfolder\module3.py

What am I to enter into path and module parameters to get an output of all classes used in all three modules?

Generic path handling for CLI

In my first attempt to use the py2puml CLI, I encountered an import problem similar to #21. When I tried to use a relative path for the path argument, the fix from #40 didn't help because the source directory was not in the current directory.

I wrote a small patch for cli.py that fixed the problem for me:

--- a/py2puml/cli.py
+++ b/py2puml/cli.py
@@ -9,11 +9,6 @@ from py2puml.py2puml import py2puml
 
 
 def run():
-    # adds the current working directory to the system path so that py2puml can import them
-    current_working_directory = str(Path.cwd().resolve())
-    if current_working_directory not in path:
-        path.append(current_working_directory)
-
     argparser = ArgumentParser(description='Generate PlantUML class diagrams to document your Python application.')
 
     argparser.add_argument('-v', '--version', action='version', version='py2puml 0.7.2')
@@ -21,4 +16,10 @@ def run():
     argparser.add_argument('module', metavar='module', type=str, help='the module name of the domain', default=None)
 
     args = argparser.parse_args()
+
+    # add the directory containing the package to the system path so that py2puml can import it
+    directory = str(Path(args.path).parent.resolve())
+    if directory not in path:
+        path.append(directory)
+
     print(''.join(py2puml(args.path, args.module)))

Obviously I'm not aware of all the use cases, but I wonder if the CLI argument list could be simplified: If the order of arguments was changed, may be the path argument might not be required if the package was installed or the source directory (with the package's name) was contained in the current directory. If the current directory happened to be a project directory (i.e. contains setup.py or equivalent), may be the project could be the default and no argument would be required at all.

Question: event handling/interactive usage of outputs

I look for some interactive way to create such diagrams (with context menu options on nodes, drag / drop) etc.
So I need events to handle commands.
Is it possible/intended to generate such event handlers on the py2uml outputs?

Thanks

should also parse __init__.py files

I am trying to analyze an inherited project, with a structure:

    package
        subpackage
            __init__.py     # base classes and interfaces
            classfile.py   # derived classes

If I run: py2puml package/subpackage package.subpackage it will find any classes defined in classfile.py, but will not list any classes from the __init__.py

Since the entire project is organized this way, and it's not my code, I am unable to use py2puml on it.

Package.py is not throw error.

Hi there,
I've tried the Pynguin to generated the test cases for Package.py
And there is one test case that mark as expected fail by Pynguin.

import pytest
import package as module_0
import dataclasses as module_1
@pytest.mark.xfail(strict=True)
def test_case_0():
    module_0.Package(123, [], 123)

And after tested with Pytest, it is throw the error that this case was mark as XPASS(Unexpected pass)

So I checked the Package.py function and found out that the there's no raise type error in there, that's why the Pynguin expected the test cases as failed.

from dataclasses import dataclass, field
from typing import List

@dataclass
class Package:
    '''A folder or a python module'''
    name: str
    children: List['Package'] = field(default_factory=list)
    items_number: int = 0
    # def __post_init__(self):
    #     if not isinstance(self.name, str):
    #         raise TypeError("Name must be a string.")    
    # suggestion #
    

Thank you.

typing.Literal recognition is unsupported

Hi!

First of all, really liked the idea of the project. Auto-generating plantuml documentation straight from Python code is quite nice.

I've attempted to check out py2puml on my project, but happened to encounter this error:

ValueError: typing.Literal['val1', 'val2'] seems to be an invalid type annotation

I'm assuming py2puml is currently unable to recognize literals (which essentially are a form of enums), and based on this PR it also can't yet work with Pydantic models, which subjectively is even more concerning.

Is there a chance for the missing type (and potentially other types I haven't gotten an issue with yet) to be added to py2puml?

Again, really like the idea, can see myself using this very often if it works out :)

support typing for typing.NamedTuple named tuples

Classes created with collections.namedtuple carry no type annotations.

from collections import namedtuple

Function = namedtuple('Function', ['name', 'module_Path'])

py2puml would generate the following documentation:

@startuml
class Function {
  name: Any
  module_path: Any
}
@enduml

However, it is also possible to create named tuple classes with typing.NamedTuple and specify type annotations:

from typing import NamedTuple, Tuple
class Function(NamedTuple):
    '''
    Models a function (calling or being called):
    - it has a name
    - it is hosted in a module, modeled by its path as a tuple of strings
    '''
    name: str
    module_path: Tuple[str]

The expected PlantUML documentation would then be:

@startuml
class Function {
  name: str
  module_path: Tuple[str]
}
@enduml

handle decorated constructors (use the .__wrapped__ constructor)

Hello, I'm getting an AttributeError and I don't quite know how to fix the traversed code.

Issue

py2puml works great, but I'm trying to use it to clean up a very imperfect codebase and I've had to make some adjustments to the codebase for successful py2puml output (which is fine). I don't know what I need to adjust to fix the current error though.

running py2puml manim/mobject manim.mobject on manim@c69f6eb yields:

Traceback

  File "/Users/citrus/Library/Caches/pypoetry/virtualenvs/manim-fDwhioqp-py3.9/bin/py2puml", line 8, in <module>
    sys.exit(run())
  File "/Users/citrus/Library/Caches/pypoetry/virtualenvs/manim-fDwhioqp-py3.9/lib/python3.9/site-packages/py2puml/cli.py", line 17, in run
    print(''.join(py2puml(args.path, args.module)))
  File "/Users/citrus/Library/Caches/pypoetry/virtualenvs/manim-fDwhioqp-py3.9/lib/python3.9/site-packages/py2puml/py2puml.py", line 18, in py2puml
    inspect_module(
  File "/Users/citrus/Library/Caches/pypoetry/virtualenvs/manim-fDwhioqp-py3.9/lib/python3.9/site-packages/py2puml/inspection/inspectmodule.py", line 59, in inspect_module
    inspect_domain_definition(definition_type, root_module_name, domain_items_by_fqn, domain_relations)
  File "/Users/citrus/Library/Caches/pypoetry/virtualenvs/manim-fDwhioqp-py3.9/lib/python3.9/site-packages/py2puml/inspection/inspectmodule.py", line 46, in inspect_domain_definition
    inspect_class_type(
  File "/Users/citrus/Library/Caches/pypoetry/virtualenvs/manim-fDwhioqp-py3.9/lib/python3.9/site-packages/py2puml/inspection/inspectclass.py", line 97, in inspect_class_type
    instance_attributes, compositions = parse_class_constructor(class_type, class_type_fqn, root_module_name)
  File "/Users/citrus/Library/Caches/pypoetry/virtualenvs/manim-fDwhioqp-py3.9/lib/python3.9/site-packages/py2puml/parsing/parseclassconstructor.py", line 39, in parse_class_constructor
    visitor.visit(constructor_ast)
  File "/usr/local/Cellar/[email protected]/3.9.9/Frameworks/Python.framework/Versions/3.9/lib/python3.9/ast.py", line 407, in visit
    return visitor(node)
  File "/Users/citrus/Library/Caches/pypoetry/virtualenvs/manim-fDwhioqp-py3.9/lib/python3.9/site-packages/py2puml/parsing/astvisitors.py", line 98, in generic_visit
    NodeVisitor.generic_visit(self, node)
  File "/usr/local/Cellar/[email protected]/3.9.9/Frameworks/Python.framework/Versions/3.9/lib/python3.9/ast.py", line 415, in generic_visit
    self.visit(item)
  File "/usr/local/Cellar/[email protected]/3.9.9/Frameworks/Python.framework/Versions/3.9/lib/python3.9/ast.py", line 407, in visit
    return visitor(node)
  File "/Users/citrus/Library/Caches/pypoetry/virtualenvs/manim-fDwhioqp-py3.9/lib/python3.9/site-packages/py2puml/parsing/astvisitors.py", line 108, in visit_FunctionDef
    self.generic_visit(node)
  File "/Users/citrus/Library/Caches/pypoetry/virtualenvs/manim-fDwhioqp-py3.9/lib/python3.9/site-packages/py2puml/parsing/astvisitors.py", line 98, in generic_visit
    NodeVisitor.generic_visit(self, node)
  File "/usr/local/Cellar/[email protected]/3.9.9/Frameworks/Python.framework/Versions/3.9/lib/python3.9/ast.py", line 415, in generic_visit
    self.visit(item)
  File "/usr/local/Cellar/[email protected]/3.9.9/Frameworks/Python.framework/Versions/3.9/lib/python3.9/ast.py", line 407, in visit
    return visitor(node)
  File "/Users/citrus/Library/Caches/pypoetry/virtualenvs/manim-fDwhioqp-py3.9/lib/python3.9/site-packages/py2puml/parsing/astvisitors.py", line 98, in generic_visit
    NodeVisitor.generic_visit(self, node)
  File "/usr/local/Cellar/[email protected]/3.9.9/Frameworks/Python.framework/Versions/3.9/lib/python3.9/ast.py", line 415, in generic_visit
    self.visit(item)
  File "/usr/local/Cellar/[email protected]/3.9.9/Frameworks/Python.framework/Versions/3.9/lib/python3.9/ast.py", line 407, in visit
    return visitor(node)
  File "/Users/citrus/Library/Caches/pypoetry/virtualenvs/manim-fDwhioqp-py3.9/lib/python3.9/site-packages/py2puml/parsing/astvisitors.py", line 126, in visit_Assign
    variables_collector = AssignedVariablesCollector(self.class_self_id, None)
AttributeError: 'ConstructorVisitor' object has no attribute 'class_self_id'

Success

I have success with

  • py2puml manim/mobjects/svg manim.mobjects.svg
    after deleting manim/mobject/svg/opengl_tex_mobject.py and manim/mobject/svg/opengl_text_mobject.py
  • py2puml manim/mobject/types manim.mobject.types

so maybe that narrows the issue down slightly ๐Ÿ˜….

Docstrings

Proposal

I think it would be great to add the ability to get the docstrings for classes and or methods and insert them into the uml. This would go a long way in clarifying what classes are actually for in the uml.

Solution

The __doc__ attribute can be used to get the docstrings without much trouble.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.