Skip to content

Commit e9f3c0c

Browse files
authored
feat: handle union type and add pre-commit lint hooks (#51)
* ci: add pre-commit hooks and automated tests * build: remove obsolete init.sh file * ci: define a cache for the virtual environment based on the dependencies lock file * build(yapf): upgraded version * feat(inspect): handle unions in type annotations * build: improve code coverage (added tests, removed irrelevant code) * ci: run test without installing the lint dependencies * docs(readme): fix copy-pasted reference to pydoctrace * docs(contributing): add how to activate the pre-commit hooks * ci(ruff): update version to 0.0.285 * ci(jobs): add concurrency to cancel a running job when a new one is triggered * refactor(tests): split union-types tests in different unit tests for readability sake * style(tests): post-review fixes about test code (type annotations, code comments, doc styling, default values) * docs(readme): add a section about the Python versions required by py2puml and pyenv
1 parent 175997a commit e9f3c0c

Some content is hidden

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

69 files changed

+1167
-902
lines changed

.github/workflows/python-package.yml

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
name: Python package
2+
3+
on: [push]
4+
5+
concurrency:
6+
group: ${{ github.workflow }}-${{ github.ref }}
7+
cancel-in-progress: true
8+
9+
env:
10+
MIN_CODE_COVERAGE_PERCENT: 93
11+
12+
jobs:
13+
build:
14+
15+
runs-on: ubuntu-latest
16+
steps:
17+
- uses: actions/checkout@v3
18+
19+
- name: Install Python
20+
uses: actions/setup-python@v4
21+
with:
22+
python-version-file: '.python-version'
23+
24+
- name: Install poetry
25+
uses: abatilo/actions-poetry@v2
26+
with:
27+
poetry-version: 1.5.1
28+
29+
- name: Define a cache for the virtual environment based on the dependencies lock file
30+
uses: actions/cache@v3
31+
with:
32+
path: ./.venv
33+
key: venv-${{ hashFiles('poetry.lock') }}
34+
35+
- name: Install project dependencies
36+
run: poetry install --without lint
37+
38+
- name: Run automated tests (with code coverage)
39+
run: poetry run pytest -v --cov=py2puml --cov-branch --cov-report term-missing --cov-fail-under $MIN_CODE_COVERAGE_PERCENT

.pre-commit-config.yaml

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
repos:
2+
- repo: https://github.com/pre-commit/pre-commit-hooks
3+
rev: v4.4.0
4+
hooks:
5+
- id: check-yaml
6+
- id: check-toml
7+
# trims all whitespace from the end of each line
8+
- id: trailing-whitespace
9+
# ensures that all files end in a newline and only a newline
10+
- id: end-of-file-fixer
11+
# replace "double quotes" by 'single quotes' unless "it's impossible"
12+
- id: double-quote-string-fixer
13+
# prevents large files from being committed (>100kb)
14+
- id: check-added-large-files
15+
args: [--maxkb=100]
16+
# enforce the naming conventions of test scripts
17+
- id: name-tests-test
18+
# tests match test_.*\.py
19+
args: [--pytest-test-first]
20+
exclude: tests.asserts.*|tests.modules.*|tests/py2puml/parsing/mockedinstance.py
21+
22+
- repo: https://github.com/alessandrojcm/commitlint-pre-commit-hook
23+
rev: v9.5.0
24+
hooks:
25+
- id: commitlint
26+
stages: [commit-msg]
27+
additional_dependencies: ['@commitlint/config-angular']
28+
29+
- repo: https://github.com/PyCQA/isort
30+
rev: 5.12.0
31+
hooks:
32+
- id: isort
33+
additional_dependencies: [toml]
34+
35+
- repo: https://github.com/google/yapf
36+
rev: v0.40.0
37+
hooks:
38+
- id: yapf
39+
name: Yapf
40+
additional_dependencies: [toml]
41+
42+
- repo: https://github.com/charliermarsh/ruff-pre-commit
43+
rev: v0.0.285
44+
hooks:
45+
- id: ruff

.python-version

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
3.8.6
1+
3.10.9

CONTRIBUTING.md

+13-79
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,19 @@ version = "major.minor.patch"
6464
It takes time to write comprehensive guidelines.
6565
To save time (my writing time and your reading time), I tried to make it short so my best advice is _go have a look at the production and test codes and try to follow the conventions you draw from what you see_ 🙂
6666

67+
To homogenize code style consistency and enforce code quality, this project uses:
68+
69+
- the `yapf` formatter and the `ruff` linter
70+
- `pre-commit` hooks that are triggered on the Github CI (in pull-requests) and should be activated locally when contributing to the project:
71+
72+
```sh
73+
# installs the dependencies, including pre-commit as a lint dependency
74+
poetry install
75+
76+
# activates the pre-commit hooks
77+
poetry run pre-commit install --hook-type pre-commit --hook-type commit-msg
78+
```
79+
6780
## Unit tests
6881

6982
Pull requests must come with unit tests, either new ones (for feature addtitions), changed ones (for feature changes) or non-regression ones (for bug fixes).
@@ -91,82 +104,3 @@ When manipulating **literal strings**:
91104
python_version = '3.8+'
92105
f'use f-strings to format strings, py2puml use Python {python_version}'
93106
```
94-
95-
### Code formatting
96-
97-
#### Imports
98-
99-
The following guidelines regarding imports are designed to help people understand what a Python module does or deals with:
100-
101-
* place import statements at the top of the file
102-
* explicit the functionalities you import with the `from ... import ...` syntax instead of importing a whole module (it does not tell how this module is being used)
103-
* order the imports by family of modules:
104-
1. the native modules (the types imported from the typing module are often placed first)
105-
1. the dependency modules defined in the [pyproject.toml file](pyproject.toml) (`py2puml` only use development dependencies)
106-
1. the modules of the `py2puml` package
107-
* when they are so many items imported from a module that they do not fit on a single line, wrap them with a pair of parentheses instead of using a backslash
108-
109-
```python
110-
from typing import Dict, List, Tuple
111-
112-
from ast import (
113-
NodeVisitor, arg, expr,
114-
FunctionDef, Assign, AnnAssign,
115-
Attribute, Name, Tuple as AstTuple,
116-
Subscript, get_source_segment
117-
)
118-
from collections import namedtuple
119-
120-
from py2puml.domain.umlclass import UmlAttribute
121-
from py2puml.domain.umlrelation import UmlRelation, RelType
122-
```
123-
124-
#### Code indentation
125-
126-
Most settings in Python-code editors replace tabulation by 4 space characters.
127-
That is what is used for this library as well.
128-
129-
This library follows an opiniated way for formatting the code between **parentheses, brackets or braces**.
130-
Whenever a block of python expressions is surrounded by a pair of parentheses, brackets or braces:
131-
132-
* start the inner content in an indented block (one tabulation is enough) in a new line following the opening parenthesis, bracket or brace
133-
* type the closing parenthesis, bracket or brace in a new line, de-indent so that the inner content is visually enclosed between the opening and closing symbol
134-
* repeat the logic within the inner content block
135-
136-
Some examples:
137-
138-
```python
139-
# function signature and return type
140-
def parse_class_constructor(
141-
class_type: Type,
142-
class_fqn: str, root_module_name: str
143-
) -> Tuple[
144-
List[UmlAttribute], Dict[str, UmlRelation]
145-
]:
146-
constructor = getattr(class_type, '__init__', None)
147-
...
148-
149-
# function call
150-
print(''.join(
151-
py2puml('py2puml/domain', 'py2puml.domain')
152-
))
153-
154-
# using a generator to get the first matching item
155-
definition_module_member = next((
156-
member for member in definition_members
157-
# ensures that the type belongs to the module being parsed
158-
if member[0] == '__module__' and member[1].startswith(root_module_name)
159-
), None)
160-
161-
# dictionary comprehension with a complex filtering expression
162-
def extend_relations(self, target_fqns: List[str]):
163-
self.uml_relations_by_target_fqn.update({
164-
target_fqn: UmlRelation(self.class_fqn, target_fqn, RelType.COMPOSITION)
165-
for target_fqn in target_fqns
166-
if target_fqn.startswith(self.root_fqn) and (
167-
target_fqn not in self.uml_relations_by_target_fqn
168-
)
169-
})
170-
```
171-
172-
I am looking forward to providing the linting settings corresponding to these practices.

README.md

+72-8
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,28 @@
1111

1212
Generate PlantUML class diagrams to document your Python application.
1313

14+
[![pre-commit.ci status](https://results.pre-commit.ci/badge/github/lucsorel/py2puml/main.svg)](https://results.pre-commit.ci/latest/github/lucsorel/py2puml/main)
15+
16+
17+
`py2puml` uses [pre-commit hooks](https://pre-commit.com/) and [pre-commit.ci Continuous Integration](https://pre-commit.ci/) to enforce commit messages, code formatting and linting for quality and consistency sake.
18+
See the [code conventions](#code-conventions) section if you would like to contribute to the project.
19+
20+
1421
# How it works
1522

1623
`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).
1724

1825
`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.
19-
Some parsing features are available only since Python 3.8 (like [ast.get_source_segment](https://docs.python.org/3/library/ast.html#ast.get_source_segment)).
26+
27+
## Minimum Python versions to run py2puml
28+
29+
`p2puml` uses some code-parsing features that are available only since **Python 3.8** (like [ast.get_source_segment](https://docs.python.org/3/library/ast.html#ast.get_source_segment)).
30+
If your codebase uses the `int | float` syntax to define optional types, then you should use Python 3.10 to run `py2puml`.
31+
32+
To sum it up, use at least Python 3.8 to run py2puml, or a higher version if you use syntax features available only in higher versions.
33+
34+
The [.python-version](.python-version) file indicates the Python version used to develop the library.
35+
It is a file used by [pyenv](https://github.com/pyenv/pyenv/) to define the binary used by the project.
2036

2137
## Features
2238

@@ -129,7 +145,7 @@ footer Generated by //py2puml//
129145
@enduml
130146
```
131147

132-
Using PlantUML, this content is rendered as this diagram:
148+
Using PlantUML, this content is rendered as in this diagram:
133149

134150
![py2puml domain UML Diagram](https://www.plantuml.com/plantuml/png/ZPD1Yzim48Nl-XLpNbWRUZHxs2M4rj1DbZGzbIN8zcmgAikkD2wO9F-zigqWEw1L3i6HPgJlFUdfsH3NrDKIslvBQxz9rTHSAAPuZQRb9TuKuCG0PaLU_k5776S1IicDkLcGk9RaRT4wRPA18Ut6vMyXAuqgW-_2q2_N_kwgWh0s1zNL1UeCXA9n_iAcdnTamQEApnHTUvAVjNmXqgBeAAoB-dOnDiH9b1aKJIETYBj8gvai07xb6kTtfiMRDWTUM38loV62feVpYNWUMWOXkVq6tNxyLMuO8g7g8gIn9Nd5uQw2e7zSTZX7HJUqqjUU3L2FWElvJRZti6wDafDeb5i_shWb-QvaXtBVjpuMg-ths_P7li-tcmmUu3J5uEAg-URRUfVlNpQhTGPFPr-EUlD4ws-tr0XWcawNU5ZS2W1nVKJoi_EWEjspSxYmo8jyU7oCF5eMoxNV8_BCM2INJsUxKOp68WdnOWAfl5j56CBkl4cd9H8pzj4qX1g-eaBD2IieUaXJjp1DsJEgolvZ_m40)
135151

@@ -160,9 +176,9 @@ docker start plantumlserver
160176

161177
## Python API
162178

163-
For example, to create the diagram of the classes used by `py2puml`:
179+
For example, to create the diagram of the domain classes used by `py2puml`:
164180

165-
* import the `py2puml` function in your script (see [py2puml/example.py](py2puml/example.py)):
181+
* import the `py2puml` function in your script:
166182

167183
```python
168184
from py2puml.py2puml import py2puml
@@ -172,11 +188,11 @@ if __name__ == '__main__':
172188
print(''.join(py2puml('py2puml/domain', 'py2puml.domain')))
173189

174190
# writes the PlantUML content in a file
175-
with open('py2puml/domain.puml', 'w') as puml_file:
191+
with open('py2puml/py2puml.domain.puml', 'w', encoding='utf8') as puml_file:
176192
puml_file.writelines(py2puml('py2puml/domain', 'py2puml.domain'))
177193
```
178194

179-
* running it (`python3 -m py2puml.example`) outputs the previous PlantUML diagram in the terminal and writes it in a file.
195+
* running it outputs the previous PlantUML diagram in the terminal and writes it in a file.
180196

181197

182198
# Tests
@@ -192,11 +208,12 @@ python3 -m pytest -v
192208
Code coverage (with [missed branch statements](https://pytest-cov.readthedocs.io/en/latest/config.html?highlight=--cov-branch)):
193209

194210
```sh
195-
poetry run pytest -v --cov=py2puml --cov-branch --cov-report term-missing --cov-fail-under 92
211+
poetry run pytest -v --cov=py2puml --cov-branch --cov-report term-missing --cov-fail-under 93
196212
```
197213

198214
# Changelog
199215

216+
* `0.8.0`: added support for union types, and github actions (pre-commit hooks + automated tests)
200217
* `0.7.2`: added the current working directory to the import path to make py2puml work in any directory or in native virtual environment (not handled by poetry)
201218
* `0.7.1`: removed obsolete part of documentation: deeply compound types are now well handled (by version `0.7.0`)
202219
* `0.7.0`: improved the generated PlantUML documentation (added the namespace structure of the code base, homogenized type between inspection and parsing), improved relationships management (handle forward references, deduplicate relationships)
@@ -224,8 +241,55 @@ Unless stated otherwise all works are licensed under the [MIT license](http://sp
224241
* [Julien Jerphanion](https://github.com/jjerphan)
225242
* [Luis Fernando Villanueva Pérez](https://github.com/jonykalavera)
226243

244+
## Pull requests
245+
227246
Pull-requests are welcome and will be processed on a best-effort basis.
228-
Follow the [contributing guide](CONTRIBUTING.md).
247+
248+
Pull requests must follow the guidelines enforced by the `pre-commit` hooks:
249+
250+
- commit messages must follow the Angular conventions enforced by the `commitlint` hook
251+
- code formatting must follow the conventions enforced by the `isort` and `yapf` hooks
252+
- code linting should not detect code smells in your contributions, this is checked by the `ruff` hook
253+
254+
Please also follow the [contributing guide](CONTRIBUTING.md) to ease your contribution.
255+
256+
## Code conventions
257+
258+
The code conventions are described and enforced by [pre-commit hooks](https://pre-commit.com/hooks.html) to maintain consistency across the code base.
259+
The hooks are declared in the [.pre-commit-config.yaml](.pre-commit-config.yaml) file.
260+
261+
Set the git hooks (pre-commit and commit-msg types):
262+
263+
```sh
264+
poetry run pre-commit install --hook-type pre-commit --hook-type commit-msg
265+
```
266+
267+
Before committing, you can check your changes with:
268+
269+
```sh
270+
# put all your changes in the git staging area
271+
git add -A
272+
273+
# all hooks
274+
poetry run pre-commit run --all-files
275+
276+
# a specific hook
277+
poetry run pre-commit run ruff --all-files
278+
```
279+
280+
### Commit messages
281+
282+
Please, follow the [conventions of the Angular team](https://github.com/angular/angular/blob/main/CONTRIBUTING.md#-commit-message-format) for commit messages.
283+
When merging your pull-request, the new version of the project will be derived from the messages.
284+
285+
### Code formatting
286+
287+
This project uses `isort` and `yapf` to format the code.
288+
The guidelines are expressed in their respective sections in the [pyproject.toml](pyproject.toml) file.
289+
290+
### Best practices
291+
292+
This project uses the `ruff` linter, which is configured in its section in the [pyproject.toml](pyproject.toml) file.
229293

230294
# Current limitations
231295

commitlint.config.js

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
module.exports = {
2+
extends: [
3+
'@commitlint/config-angular'
4+
],
5+
rules: {
6+
'header-max-length': [2, 'always', 120],
7+
}
8+
}

init.sh

-24
This file was deleted.

0 commit comments

Comments
 (0)