Skip to content

Commit 046dd48

Browse files
authored
fix #15 prevent from parsing inherited constructors (#16)
* fix #15 prevent from parsing inherited constructors * account for nested classes
1 parent 01dc57c commit 046dd48

File tree

9 files changed

+74
-11
lines changed

9 files changed

+74
-11
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ poetry run python -m pytest -v --cov=py2puml --cov-branch --cov-report term-miss
179179

180180
# Changelog
181181

182+
* `0.5.1`: prevent from parsing inherited constructors
182183
* `0.5.0`: handle instance attributes in class constructors, add code coverage of unit tests
183184
* `0.4.0`: add a simple CLI
184185
* `0.3.1`: inspect sub-folders recursively

py2puml/cli.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
def run():
1010
argparser = ArgumentParser(description='Generate PlantUML class diagrams to document your Python application.')
1111

12-
argparser.add_argument('-v', '--version', action='version', version='py2puml 0.5.0')
12+
argparser.add_argument('-v', '--version', action='version', version='py2puml 0.5.1')
1313
argparser.add_argument('path', metavar='path', type=str, help='the path of the domain')
1414
argparser.add_argument('module', metavar='module', type=str, help='the module of the domain', default=None)
1515

py2puml/parsing/parseclassconstructor.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,17 @@ def parse_class_constructor(
1717
root_module_name: str
1818
) -> Tuple[List[UmlAttribute], Dict[str, UmlRelation]]:
1919
constructor = getattr(class_type, '__init__', None)
20-
if constructor is None or not hasattr(constructor, '__code__'):
20+
# conditions to meet in order to parse the AST of a constructor
21+
if (
22+
# the constructor must be defined
23+
constructor is None
24+
) or (
25+
# the constructor's source code must be available
26+
not hasattr(constructor, '__code__')
27+
) or (
28+
# the constructor must belong to the parsed class (not its parent's one)
29+
not constructor.__qualname__.endswith(f'{class_type.__name__}.__init__')
30+
):
2131
return [], {}
2232

2333
constructor_source: str = dedent(getsource(constructor.__code__))

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "py2puml"
3-
version = "0.5.0"
3+
version = "0.5.1"
44
description = "Generate PlantUML class diagrams to document your Python application."
55
keywords = ["class diagram", "PlantUML", "documentation", "inspection", "AST"]
66
readme = "README.md"
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from .point import Origin
2+
3+
class MetricOrigin(Origin):
4+
unit: str = 'm'
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
2+
class Point:
3+
def __init__(self, x: float=0, y: float=0):
4+
self.x = x
5+
self.y = y
6+
7+
class Origin(Point):
8+
is_origin: bool = True

tests/py2puml/inspection/test_inspectclass.py

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ def test_inspect_module_should_find_static_and_instance_attributes():
2727
coordinates_umlitem: UmlClass = domain_items_by_fqn['tests.modules.withconstructor.Coordinates']
2828
assert len(coordinates_umlitem.attributes) == 2, '2 attributes of Coordinates must be inspected'
2929
x_attribute, y_attribute = coordinates_umlitem.attributes
30-
assert_attribute(x_attribute, 'x', 'float', False)
31-
assert_attribute(y_attribute, 'y', 'float', False)
30+
assert_attribute(x_attribute, 'x', 'float', expected_staticity=False)
31+
assert_attribute(y_attribute, 'y', 'float', expected_staticity=False)
3232

3333
# Point UmlClass
3434
point_umlitem: UmlClass = domain_items_by_fqn['tests.modules.withconstructor.Point']
@@ -57,7 +57,7 @@ def test_inspect_module_should_find_static_and_instance_attributes():
5757
if attribute.name == attribute_name
5858
), None)
5959
assert point_attribute is not None, f'attribute {attribute_name} must be detected'
60-
assert_attribute(point_attribute, attribute_name, atrribute_type, attribute_staticity)
60+
assert_attribute(point_attribute, attribute_name, atrribute_type, expected_staticity=attribute_staticity)
6161

6262
# Coordinates is a component of Point
6363
assert len(domain_relations) == 1, '1 composition'
@@ -67,3 +67,39 @@ def test_inspect_module_should_find_static_and_instance_attributes():
6767
'tests.modules.withconstructor.Coordinates',
6868
RelType.COMPOSITION
6969
)
70+
71+
def test_inspect_module_parse_class_constructor_should_not_process_inherited_constructor():
72+
domain_items_by_fqn: Dict[str, UmlItem] = {}
73+
domain_relations: List[UmlRelation] = []
74+
# inspects the two sub-modules
75+
inspect_module(
76+
import_module('tests.modules.withinheritedconstructor.point'),
77+
'tests.modules.withinheritedconstructor.point',
78+
domain_items_by_fqn, domain_relations
79+
)
80+
inspect_module(
81+
import_module('tests.modules.withinheritedconstructor.metricorigin'),
82+
'tests.modules.withinheritedconstructor.metricorigin',
83+
domain_items_by_fqn, domain_relations
84+
)
85+
86+
assert len(domain_items_by_fqn) == 3, 'three classes must be inspected'
87+
88+
# Point UmlClass
89+
point_umlitem: UmlClass = domain_items_by_fqn['tests.modules.withinheritedconstructor.point.Point']
90+
assert len(point_umlitem.attributes) == 2, '2 attributes of Point must be inspected'
91+
x_attribute, y_attribute = point_umlitem.attributes
92+
assert_attribute(x_attribute, 'x', 'float', expected_staticity=False)
93+
assert_attribute(y_attribute, 'y', 'float', expected_staticity=False)
94+
95+
# Origin UmlClass
96+
origin_umlitem: UmlClass = domain_items_by_fqn['tests.modules.withinheritedconstructor.point.Origin']
97+
assert len(origin_umlitem.attributes) == 1, '1 attribute of Origin must be inspected'
98+
is_origin_attribute = origin_umlitem.attributes[0]
99+
assert_attribute(is_origin_attribute, 'is_origin', 'bool', expected_staticity=True)
100+
101+
# MetricOrigin UmlClass
102+
metric_origin_umlitem: UmlClass = domain_items_by_fqn['tests.modules.withinheritedconstructor.metricorigin.MetricOrigin']
103+
assert len(metric_origin_umlitem.attributes) == 1, '1 attribute of MetricOrigin must be inspected'
104+
unit_attribute = metric_origin_umlitem.attributes[0]
105+
assert_attribute(unit_attribute, 'unit', 'str', expected_staticity=True)

tests/py2puml/inspection/test_inspectdataclass.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,14 @@ def test_inspect_domain_definition_single_class_without_composition():
2828
assert umlclass.name == 'Contact'
2929
attributes = umlclass.attributes
3030
assert len(attributes) == 4, 'class has 4 attributes'
31-
assert_attribute(attributes[0], 'full_name', 'str', False)
32-
assert_attribute(attributes[1], 'age', 'int', False)
33-
assert_attribute(attributes[2], 'weight', 'float', False)
34-
assert_attribute(attributes[3], 'can_twist_tongue', 'bool', False)
31+
assert_attribute(attributes[0], 'full_name', 'str', expected_staticity=False)
32+
assert_attribute(attributes[1], 'age', 'int', expected_staticity=False)
33+
assert_attribute(attributes[2], 'weight', 'float', expected_staticity=False)
34+
assert_attribute(attributes[3], 'can_twist_tongue', 'bool', expected_staticity=False)
3535

3636
assert len(domain_relations) == 0, 'no component must be detected in this class'
3737

38+
3839
def test_inspect_domain_definition_single_class_with_composition():
3940
domain_items_by_fqn: Dict[str, UmlItem] = {}
4041
domain_relations: List[UmlRelation] = []
@@ -61,6 +62,9 @@ def test_parse_inheritance_within_module():
6162
child_glowing_fish: UmlClass = umlitems_by_fqn[0]
6263
assert child_glowing_fish.name == 'GlowingFish'
6364
assert child_glowing_fish.fqn == 'tests.modules.withinheritancewithinmodule.GlowingFish'
65+
assert len(child_glowing_fish.attributes) == 2
66+
assert_attribute(child_glowing_fish.attributes[0], 'glow_for_hunting', 'bool', expected_staticity=False)
67+
assert_attribute(child_glowing_fish.attributes[1], 'glow_for_mating', 'bool', expected_staticity=False)
6468

6569
assert len(domain_relations) == 2, '2 inheritance relations must be inspected'
6670
parent_fish, parent_light = domain_relations

tests/py2puml/test__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
# Ensures the library version is modified in the pyproject.toml file when upgrading it (pull request)
44
def test_version():
5-
assert __version__ == '0.5.0'
5+
assert __version__ == '0.5.1'
66

77
# Description also output in the CLI
88
def test_description():

0 commit comments

Comments
 (0)