Skip to content

Commit 01dc57c

Browse files
authored
7 detect instance variables in constructor (#14)
* upgraded to python 3.8 to have get_source_segment, WIP of constructor's code parsing * fixed documentation link to poetry, closes #13 * handle non/typed and tuple assignment (to improve) * properly handle type aliasing * handle composition relationships * refactored inspection and AST parsing code, bump to version 0.5.0 * added code-coverage plugin for unit tests * WIP added UmlAttribute staticity. Some attributes are detected twice * skipped subscriptable assignment and fixed attribute naming * updated documentation (inspection and AST parsing) * code clean-up * added test case with multiple assignments * removed obsolete test case * fixed unit tests + code refactoring * added unit tests on astvisitors * fixed variable name * fixes after @jjherphan code review * code review of @jjerphan and @doyou89 (thanks!)
1 parent 3174add commit 01dc57c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+1360
-398
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
__pycache__/
33
.venv/
44
*.egg-info/
5-
*.puml
65
dist/
76
.vscode/
7+
# data generated by pytest-cov
8+
.coverage

.python-version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
3.7.6
1+
3.8.6

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2020 Luc Sorel-Giffo
3+
Copyright (c) 2020-2021 Luc Sorel-Giffo
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

README.md

Lines changed: 60 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -9,44 +9,46 @@
99
<h1>Python to PlantUML</h1>
1010
</div>
1111

12-
Generate Plantuml diagrams to document your python code
12+
Generate PlantUML class diagrams to document your Python application.
1313

1414
# How it works
1515

16-
## Features
17-
18-
From a given path corresponding to a folder containing python code, `py2puml` loads each file as a module and generate a class diagram with the [PlantUML](https://plantuml.com/en/class-diagram) using:
16+
`py2puml` produces a class diagram [PlantUML script](https://plantuml.com/en/class-diagram) representing classes properties (static and instance attributes) and their relations (composition and inheritance relationships).
1917

20-
* inspection to detect the classes to document (see the [inspect](https://docs.python.org/3/library/inspect.html) module)
21-
* annotations (the python type hinting syntax) to detect the attributes and their types (see the [typing](https://docs.python.org/3/library/typing.html) module)
22-
* fields for classes derived from namedtuples
23-
* composition and inheritance relationships are drawn only between the domain classes (this is designed on purpose, for documentation sake)
18+
`py2puml` internally uses code [inspection](https://docs.python.org/3/library/inspect.html) (also called *reflexion* in other programming languages) and [abstract tree parsing](https://docs.python.org/3/library/ast.html) to retrieve relevant information.
2419

25-
## Current limitations
20+
## Features
2621

27-
* type hinting is optional when writing Python code and discarded when it is executed, as mentionned in the [typing official documentation](https://docs.python.org/3/library/typing.html). The quality of the diagram output by `py2puml` depends on the reliability with which the type annotations were written
22+
From a given path corresponding to a folder containing Python code, `py2puml` processes each file as a module and generates a [PlantUML script](https://plantuml.com/en/class-diagram) of its classe-like definitions using:
2823

29-
> The Python runtime does not enforce function and variable type annotations. They can be used by third party tools such as type checkers, IDEs, linters, etc.
24+
* **[inspection](https://docs.python.org/3/library/inspect.html)** and [type annotations](https://docs.python.org/3/library/typing.html) to detect:
25+
* static class attributes and [dataclass](https://docs.python.org/3/library/dataclasses.html) fields
26+
* fields of [namedtuples](https://docs.python.org/3/library/collections.html#collections.namedtuple)
27+
* members of [enumerations](https://docs.python.org/3/library/enum.html)
28+
* composition and inheritance relationships (between your domain classes only, for documentation sake)
3029

31-
* complex type hints with more than one level of genericity are not properly handled for the moment: `List[MyClass]` or `Dict[str, MyClass]` are handled properly, `Dict[str, List[MyClass]]` is not. If your domain classes (also called business objects or DTOs) have attributes with complex type hints, it may be a code smell indicating that you should write a class which would better represent the business logic. But I may improve this part of the library as well 😀
30+
* parsing [abstract syntax trees](https://docs.python.org/3/library/ast.html#ast.NodeVisitor) to detect the instance attributes defined in `__init__` constructors
3231

33-
* `py2puml` outputs diagrams in PlantUML syntax, which can be saved in text files along your python code and versioned with them. To generate image files, use the PlantUML runtime or a docker image (see [think/plantuml](https://hub.docker.com/r/think/plantuml))
32+
`py2puml` outputs diagrams in PlantUML syntax, which can be:
33+
* versioned along your code with a unit-test ensuring its consistency (see the [test_py2puml.py's test_py2puml_model_on_py2uml_domain](tests/py2puml/test_py2puml.py) example)
34+
* generated and hosted along other code documentation (better option: generated documentation should not be versioned with the codebase)
3435

35-
* `py2puml` uses features of python 3 (generators for example) and thus won't work with python 2 runtimes. It relies on native python modules and uses no 3rd-party library, except [pytest](https://docs.pytest.org/en/latest/) as a development dependency for running the unit-tests
36+
To generate image files, use the PlantUML runtime, a docker image of the runtime (see [think/plantuml](https://hub.docker.com/r/think/plantuml)) or of a server (see the CLI documentation below)
3637

37-
If you like tools around PlantUML, you may also be interested in this [lucsorel/plantuml-file-loader](https://github.com/lucsorel/plantuml-file-loader) project: A webpack loader which converts PlantUML files into images during the webpack processing (useful to [include PlantUML diagrams in your slides](https://github.com/lucsorel/markdown-image-loader/blob/master/README.md#web-based-slideshows) with RevealJS or RemarkJS).
38+
If you like tools related with PlantUML, you may also be interested in this [lucsorel/plantuml-file-loader](https://github.com/lucsorel/plantuml-file-loader) project:
39+
a webpack loader which converts PlantUML files into images during the webpack processing (useful to [include PlantUML diagrams in your slides](https://github.com/lucsorel/markdown-image-loader/blob/master/README.md#web-based-slideshows) with RevealJS or RemarkJS).
3840

3941
# Install
4042

41-
Install from PyPI:
43+
Install from [PyPI](https://pypi.org/project/py2puml/):
4244

4345
* with `pip`:
4446

4547
```sh
4648
pip install py2puml
4749
```
4850

49-
* with [poetry](https://pipenv.readthedocs.io/en/latest/):
51+
* with [poetry](https://python-poetry.org/docs/):
5052

5153
```sh
5254
poetry add py2puml
@@ -64,25 +66,27 @@ pipenv install py2puml
6466

6567
Once `py2puml` is installed at the system level, an eponymous command is available in your environment shell.
6668

67-
For example, to create the diagram of the classes used by `py2puml`, one can use:
69+
For example, to create the diagram of the classes used by `py2puml`, run:
70+
6871
```sh
6972
py2puml py2puml/domain py2puml.domain
7073
```
7174

72-
This will output the following PlantUML script:
75+
This outputs the following PlantUML script:
7376

7477
```plantuml
7578
@startuml
7679
class py2puml.domain.umlclass.UmlAttribute {
7780
name: str
7881
type: str
82+
static: bool
7983
}
8084
class py2puml.domain.umlclass.UmlClass {
8185
attributes: List[UmlAttribute]
8286
}
8387
class py2puml.domain.umlitem.UmlItem {
8488
name: str
85-
fqdn: str
89+
fqn: str
8690
}
8791
class py2puml.domain.umlenum.Member {
8892
name: str
@@ -92,12 +96,12 @@ class py2puml.domain.umlenum.UmlEnum {
9296
members: List[Member]
9397
}
9498
enum py2puml.domain.umlrelation.RelType {
95-
COMPOSITION: *
96-
INHERITANCE: <|
99+
COMPOSITION: * {static}
100+
INHERITANCE: <| {static}
97101
}
98102
class py2puml.domain.umlrelation.UmlRelation {
99-
source_fqdn: str
100-
target_fqdn: str
103+
source_fqn: str
104+
target_fqn: str
101105
type: RelType
102106
}
103107
py2puml.domain.umlclass.UmlClass *-- py2puml.domain.umlclass.UmlAttribute
@@ -110,7 +114,7 @@ py2puml.domain.umlrelation.UmlRelation *-- py2puml.domain.umlrelation.RelType
110114

111115
Using PlantUML, this script renders this diagram:
112116

113-
![py2puml UML Diagram](https://www.plantuml.com/plantuml/png/ZP91IyGm48Nl-HKvBsmF7iiUTbaA1jnMQZs9I7OxIY19Qp8H5jV_xZIse5GsFULrQBvvCozRZz9XC9gTjFIUz-URdhwojZDIsOnah6UFHkyGdJe61Fx9EBVIGCuzEj9uxaVzbSRi1n4HSWBwdDyfZq-_cpnVOIa4Cw04dJCph--jJPa16qns07C4Dxl_8NM0HG1oKD0P2IR2fa5-qCC8mu__t7UW9QhEPZNeXhON6VlgS5yzY4PKPSvNL13bRL6BPbVkYvnlBdC_SnvvgaSTcRuBxWGlSIbJMjAz0SRItm17BzGc6TzglLxqL5WYlCs5GAbkBB5_CdCzuoKk4Y6pPJkFNj9niotObkhi6m00)
117+
![py2puml UML Diagram](http://www.plantuml.com/plantuml/png/ZPB1Qy8m5CRl-IlUMR277Oi7HOGLfXrTTnfZfFLj59AqIru7elxlUuqQ1ewcftmcvlTzUL-NZgIbNYjHA-aST8U7Zdyb-rRBnYGi_NxogjMAo3PLJmX70M2anXGSMTPqw89c7ZLr2bNRAd6EKzU3y4HvuxiKdXf7RtyztqTO3Q4UK1clTza-lusN8_VHz3hPegxGtbt_aQh7IG0EiE7L4xI7tTvnGGyl6FxuptsBYeVMcgH0LV8iFMETRv_pbwpCybqACpXU1dlcasptk2coShLRRr9OdC9HI3ZYm2cBg_OkhkrjZHzXIW0axHTIs0drNhEnIRJDsNm-wKCIaIuN9mR5t4Ia3muptlca5ECcOjh4VPPu_MA9Pi_xlm00)
114118

115119
For a full overview of the CLI, run:
116120

@@ -128,14 +132,13 @@ Pipe the result of the CLI with a PlantUML server for instantaneous documentatio
128132

129133
```sh
130134
# runs a local PlantUML server from a docker container:
131-
docker run -d -p 1234:8080 --name plantumlserver plantuml/plantuml-server:jetty
135+
docker run -d --rm -p 1234:8080 --name plantumlserver plantuml/plantuml-server:jetty
132136

133137
py2puml py2puml/domain py2puml.domain | curl -X POST --data-binary @- http://localhost:1234/svg/ --output - | display
134138

135-
# stops the container when you don't need it anymore, restarts it later, removes it
139+
# stops the container when you don't need it anymore, restarts it later
136140
docker stop plantumlserver
137141
docker start plantumlserver
138-
docker rm plantumlserver
139142
```
140143

141144
## Python API
@@ -154,6 +157,7 @@ print(''.join(py2puml('py2puml/domain', 'py2puml.domain')))
154157
with open('py2puml/domain.puml', 'w') as puml_file:
155158
puml_file.writelines(py2puml('py2puml/domain', 'py2puml.domain'))
156159
```
160+
157161
* running it (`python3 -m py2puml.example`) will output the previous PlantUML diagram in the terminal and write it in a file.
158162

159163

@@ -167,13 +171,20 @@ poetry run python -m pytest -v
167171
python3 -m pytest -v
168172
```
169173

174+
Code coverage (with missed [branch statements](https://pytest-cov.readthedocs.io/en/latest/config.html?highlight=--cov-branch)):
175+
176+
```sh
177+
poetry run python -m pytest -v --cov=py2puml --cov-branch --cov-report term-missing --cov-fail-under 90
178+
```
179+
170180
# Changelog
171181

182+
* `0.5.0`: handle instance attributes in class constructors, add code coverage of unit tests
172183
* `0.4.0`: add a simple CLI
173184
* `0.3.1`: inspect sub-folders recursively
174-
* `0.3.0`: handle classes derived from namedtuples (attribute types are `any`)
175-
* `0.2.0`: handle inheritance relationships and enums. Unit tested
176-
* `0.1.3`: first release, handle all module of a folder and compositions of domain classes
185+
* `0.3.0`: handle classes derived from namedtuples (attribute types are `Any`)
186+
* `0.2.0`: handle inheritance relationships and enums
187+
* `0.1.3`: first release, handle all modules of a folder and compositions of domain classes
177188

178189
# Licence
179190

@@ -187,6 +198,24 @@ Unless stated otherwise all works are licensed under the [MIT license](http://sp
187198

188199
Pull-requests are welcome and will be processed on a best-effort basis.
189200

201+
# Current limitations
202+
203+
* regarding **inspection**
204+
205+
* type hinting is optional when writing Python code and discarded when it is executed, as mentionned in the [typing official documentation](https://docs.python.org/3/library/typing.html). The quality of the diagram output by `py2puml` depends on the reliability with which the type annotations were written
206+
207+
> The Python runtime does not enforce function and variable type annotations. They can be used by third party tools such as type checkers, IDEs, linters, etc.
208+
209+
* complex type hints with more than one level of genericity are not properly handled for the moment: `List[MyClass]` or `Dict[str, MyClass]` are handled properly, `Dict[str, List[MyClass]]` is not.
210+
If your domain classes (also called business objects or DTOs) have attributes with complex type hints, it may be a code smell indicating that you should write a class which would better represent the business logic.
211+
But I may improve this part of the library as well 😀
212+
213+
* regarding the detection of instance attributes with **AST parsing**:
214+
* only constructors are visited, attributes assigned in other functions won't be documented
215+
* attribute types are inferred from type annotations:
216+
* of the attribute itself
217+
* of the variable assigned to the attribute: a signature parameter or a locale variable
218+
* to avoid side-effects, no code is executed nor interpreted
190219

191220
# Alternatives
192221

0 commit comments

Comments
 (0)