Skip to content

Commit dd74bbd

Browse files
committed
Implement return_type
Implement return_type for methods based on type hinting (not working with compound type). Correct test cases. Fix bug where self was not included in method signature. Remove useless import statements. Refactor puml generation for class methods using UmlMethod.represent_as_puml
1 parent 5710696 commit dd74bbd

File tree

7 files changed

+52
-28
lines changed

7 files changed

+52
-28
lines changed

py2puml/domain/umlclass.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ class UmlAttribute:
1010
type: str
1111
static: bool
1212

13+
1314
@dataclass
1415
class UmlMethod:
1516
name: str
@@ -18,10 +19,19 @@ class UmlMethod:
1819
is_class: bool = False
1920
return_type: str = None
2021

22+
def represent_as_puml(self):
23+
items = []
24+
if self.is_static:
25+
items.append('{static}')
26+
if self.return_type:
27+
items.append(self.return_type)
28+
items.append(f'{self.name}({self.signature})')
29+
return ' '.join(items)
30+
2131
@property
2232
def signature(self):
2333
if self.arguments:
24-
return ', '.join([f'{arg_type} {arg_name}' for arg_name, arg_type in self.arguments.items()])
34+
return ', '.join([f'{arg_type} {arg_name}' if arg_type else f'{arg_name}' for arg_name, arg_type in self.arguments.items()])
2535
return ''
2636

2737

py2puml/export/puml.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
PUML_FILE_END = '@enduml\n'
1313
PUML_ITEM_START_TPL = '{item_type} {item_fqn} {{\n'
1414
PUML_ATTR_TPL = ' {attr_name}: {attr_type}{staticity}\n'
15-
PUML_METHOD_TPL = ' {staticity} {name}({signature})\n'
1615
PUML_ITEM_END = '}\n'
1716
PUML_RELATION_TPL = '{source_fqn} {rel_type}-- {target_fqn}\n'
1817

@@ -40,7 +39,7 @@ def to_puml_content(diagram_name: str, uml_items: List[UmlItem], uml_relations:
4039
for uml_attr in uml_class.attributes:
4140
yield PUML_ATTR_TPL.format(attr_name=uml_attr.name, attr_type=uml_attr.type, staticity=FEATURE_STATIC if uml_attr.static else FEATURE_INSTANCE)
4241
for uml_method in uml_class.methods:
43-
yield PUML_METHOD_TPL.format(name=uml_method.name, signature=uml_method.signature, staticity=FEATURE_STATIC if uml_method.is_static else FEATURE_INSTANCE)
42+
yield f' {uml_method.represent_as_puml()}\n'
4443
yield PUML_ITEM_END
4544
else:
4645
raise TypeError(f'cannot process uml_item of type {uml_item.__class__}')

py2puml/parsing/astvisitors.py

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,16 @@
11

22
from typing import Dict, List, Tuple, Type
3-
from inspect import getsource, unwrap
43
from ast import (
54
NodeVisitor, arg, expr,
65
FunctionDef, Assign, AnnAssign,
76
Attribute, Name, Subscript, get_source_segment
87
)
98
from collections import namedtuple
10-
from textwrap import dedent
11-
from importlib import import_module
129

1310
from py2puml.domain.umlclass import UmlAttribute, UmlMethod
1411
from py2puml.domain.umlrelation import UmlRelation, RelType
1512
from py2puml.parsing.compoundtypesplitter import CompoundTypeSplitter, SPLITTING_CHARACTERS
16-
from py2puml.parsing.moduleresolver import ModuleResolver, NamespacedType
13+
from py2puml.parsing.moduleresolver import ModuleResolver
1714

1815
Variable = namedtuple('Variable', ['id', 'type_expr'])
1916

@@ -35,8 +32,7 @@ def visit_arg(self, node: arg):
3532
if self.class_self_id is None and not self.skip_self:
3633
self.class_self_id = variable.id
3734
# other arguments are constructor parameters
38-
else:
39-
self.variables.append(variable)
35+
self.variables.append(variable)
4036

4137

4238
class AssignedVariablesCollector(NodeVisitor):
@@ -80,6 +76,21 @@ def visit_FunctionDef(self, node: FunctionDef):
8076
self.uml_methods.append(method_visitor.uml_method)
8177

8278

79+
class ReturnTypeVisitor(NodeVisitor):
80+
81+
def __init__(self, *args, **kwargs):
82+
super().__init__(*args, **kwargs)
83+
84+
def visit_Name(self, node):
85+
return node.id
86+
87+
def visit_Constant(self, node):
88+
return node.value
89+
90+
def visit_Subscript(self, node):
91+
return node.value.id
92+
93+
8394
class MethodVisitor(NodeVisitor):
8495
"""
8596
Node visitor subclass used to walk the abstract syntax tree of a method class and identify method arguments.
@@ -90,12 +101,8 @@ class MethodVisitor(NodeVisitor):
90101
def __init__(self, *args, **kwargs):
91102
super().__init__(*args, **kwargs)
92103
self.variables_namespace: List[Variable] = []
93-
self.class_self_id: str
94104
self.uml_method: UmlMethod
95105

96-
def generic_visit(self, node):
97-
NodeVisitor.generic_visit(self, node)
98-
99106
def visit_FunctionDef(self, node: FunctionDef):
100107
decorators = [decorator.id for decorator in node.decorator_list]
101108
is_static = 'staticmethod' in decorators
@@ -104,20 +111,23 @@ def visit_FunctionDef(self, node: FunctionDef):
104111
variables_collector.visit(node)
105112
self.variables_namespace = variables_collector.variables
106113

107-
if node.name == '__init__':
108-
self.class_self_id: str = variables_collector.class_self_id
109-
self.generic_visit(node) #Only visit child nodes for constructor
110-
111114
self.uml_method = UmlMethod(name=node.name, is_static=is_static, is_class=is_class)
115+
112116
for argument in variables_collector.variables:
117+
if argument.id == variables_collector.class_self_id:
118+
self.uml_method.arguments[argument.id] = None
113119
if argument.type_expr:
114120
if hasattr(argument.type_expr, 'id'):
115121
self.uml_method.arguments[argument.id] = argument.type_expr.id
116122
else:
117-
self.uml_method.arguments[argument.id] = f'SUBscript {argument.type_expr.value.id}'
123+
self.uml_method.arguments[argument.id] = f'Subscript {argument.type_expr.value.id}' #FIXME
118124
else:
119125
self.uml_method.arguments[argument.id] = None
120126

127+
if node.returns is not None:
128+
return_visitor = ReturnTypeVisitor()
129+
self.uml_method.return_type = return_visitor.visit(node.returns)
130+
121131

122132
class ConstructorVisitor(NodeVisitor):
123133
'''

py2puml/py2puml.domain.puml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@ class py2puml.domain.umlitem.UmlItem {
2727
}
2828
class py2puml.domain.umlclass.UmlMethod {
2929
name: str
30-
signature: str
30+
arguments: Dict
31+
is_static: bool
32+
is_class: bool
33+
return_type: str
3134
}
3235
class py2puml.domain.umlenum.Member {
3336
name: str

tests/modules/withmethods/withmethods.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from __future__ import annotations
2-
from typing import List, Tuple
2+
from typing import Tuple
33
from math import pi
44

55
from tests.modules import withenum
@@ -17,7 +17,7 @@ class Point:
1717
origin = Coordinates(0, 0)
1818

1919
@staticmethod
20-
def from_values(x: int, y: str) -> 'Point':
20+
def from_values(x: int, y: str) -> Point:
2121
return Point(x, y)
2222

2323
def get_coordinates(self) -> Tuple[float, str]:

tests/puml_files/with_methods.puml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class tests.modules.withmethods.withmethods.Point {
1414
{static} Point from_values(int x, str y)
1515
Tuple[float, str] get_coordinates(self)
1616
__init__(self, int x, str y)
17+
int do_something(self, posarg_nohint, str posarg_hint, posarg_default)
1718
}
1819
class tests.modules.withmethods.withinheritedmethods.ThreeDimensionalPoint {
1920
z: float

tests/py2puml/parsing/test_astvisitors.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,14 @@ def test_SignatureVariablesCollector_collect_arguments():
3636
collector.visit(constructor_ast)
3737

3838
assert collector.class_self_id == 'me'
39-
assert len(collector.variables) == 6, 'all the arguments must be detected'
40-
assert_Variable(collector.variables[0], 'an_int', 'int', constructor_source)
41-
assert_Variable(collector.variables[1], 'an_untyped', None, constructor_source)
42-
assert_Variable(collector.variables[2], 'a_compound_type', 'Tuple[float, Dict[str, List[bool]]]', constructor_source)
43-
assert_Variable(collector.variables[3], 'a_default_string', 'str', constructor_source)
44-
assert_Variable(collector.variables[4], 'args', None, constructor_source)
45-
assert_Variable(collector.variables[5], 'kwargs', None, constructor_source)
39+
assert len(collector.variables) == 7, 'all the arguments must be detected'
40+
assert_Variable(collector.variables[0], 'me', None, constructor_source)
41+
assert_Variable(collector.variables[1], 'an_int', 'int', constructor_source)
42+
assert_Variable(collector.variables[2], 'an_untyped', None, constructor_source)
43+
assert_Variable(collector.variables[3], 'a_compound_type', 'Tuple[float, Dict[str, List[bool]]]', constructor_source)
44+
assert_Variable(collector.variables[4], 'a_default_string', 'str', constructor_source)
45+
assert_Variable(collector.variables[5], 'args', None, constructor_source)
46+
assert_Variable(collector.variables[6], 'kwargs', None, constructor_source)
4647

4748
@mark.parametrize(
4849
'class_self_id,assignment_code,annotation_as_str,self_attributes,variables', [

0 commit comments

Comments
 (0)