Skip to content

Add methods #43

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion init.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@ echo "$(date '+%Y-%m-%d_%H:%M:%S') $(poetry --version) will be used to install d

# installs the project dependencies
echo "$(date '+%Y-%m-%d_%H:%M:%S') poetry ${depsinstall}"
poetry ${depsinstall}
poetry ${depsinstall}
10 changes: 7 additions & 3 deletions py2puml/asserts.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ def assert_py2puml_is_file_content(domain_path: str, domain_module: str, diagram
with open(diagram_filepath, 'r', encoding='utf8') as expected_puml_file:
assert_py2puml_is_stringio(domain_path, domain_module, expected_puml_file)


def assert_py2puml_is_stringio(domain_path: str, domain_module: str, expected_content_stream: StringIO):
# generates the PlantUML documentation
puml_content = list(py2puml(domain_path, domain_module))
Expand All @@ -20,6 +21,9 @@ def assert_py2puml_is_stringio(domain_path: str, domain_module: str, expected_co
def assert_multilines(actual_multilines: List[str], expected_multilines: Iterable[str]):
line_index = 0
for line_index, (actual_line, expected_line) in enumerate(zip(actual_multilines, expected_multilines)):
assert actual_line == expected_line, f'actual and expected contents have changed at line {line_index + 1}: {actual_line=}, {expected_line=}'

assert line_index + 1 == len(actual_multilines), f'actual and expected diagrams have {line_index + 1} lines'
# print(actual_line[:-1])
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Superfluous print statement. Can be removed?

assert (
actual_line == expected_line
), f'actual and expected contents have changed at line {line_index + 1}: {actual_line=}, {expected_line=}'

assert line_index + 1 == len(actual_multilines), f'actual and expected diagrams have {line_index + 1} lines'
8 changes: 7 additions & 1 deletion py2puml/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,13 @@ def run():

argparser.add_argument('-v', '--version', action='version', version='py2puml 0.7.2')
argparser.add_argument('path', metavar='path', type=str, help='the filepath to the domain')
argparser.add_argument('module', metavar='module', type=str, help='the module name of the domain', default=None)
argparser.add_argument(
'module',
metavar='module',
type=str,
help='the module name of the domain',
default=None,
)

args = argparser.parse_args()
print(''.join(py2puml(args.path, args.module)))
4 changes: 3 additions & 1 deletion py2puml/domain/package.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from dataclasses import dataclass, field
from typing import List


@dataclass
class Package:
'''A folder or a python module'''
"""A folder or a python module"""

name: str
children: List['Package'] = field(default_factory=list)
items_number: int = 0
36 changes: 34 additions & 2 deletions py2puml/domain/umlclass.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,47 @@
from typing import List
from dataclasses import dataclass
from dataclasses import dataclass, field
from typing import Dict, List

from py2puml.domain.umlitem import UmlItem


@dataclass
class UmlAttribute:
name: str
type: str
static: bool


@dataclass
class UmlMethod:
name: str
arguments: Dict = field(default_factory=dict)
is_static: bool = False
is_class: bool = False
return_type: str = None

def represent_as_puml(self):
items = []
if self.is_static:
items.append('{static}')
if self.return_type:
items.append(self.return_type)
items.append(f'{self.name}({self.signature})')
return ' '.join(items)

@property
def signature(self):
if self.arguments:
return ', '.join(
[
f'{arg_type} {arg_name}' if arg_type else f'{arg_name}'
for arg_name, arg_type in self.arguments.items()
]
)
return ''


@dataclass
class UmlClass(UmlItem):
attributes: List[UmlAttribute]
methods: List[UmlMethod]
is_abstract: bool = False
4 changes: 3 additions & 1 deletion py2puml/domain/umlenum.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
from typing import List
from dataclasses import dataclass
from typing import List

from py2puml.domain.umlitem import UmlItem


@dataclass
class Member:
name: str
value: str


@dataclass
class UmlEnum(UmlItem):
members: List[Member]
3 changes: 2 additions & 1 deletion py2puml/domain/umlitem.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from dataclasses import dataclass


@dataclass
class UmlItem:
name: str
fqn: str
fqn: str
2 changes: 2 additions & 0 deletions py2puml/domain/umlrelation.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
from dataclasses import dataclass
from enum import Enum, unique


@unique
class RelType(Enum):
COMPOSITION = '*'
INHERITANCE = '<|'


@dataclass
class UmlRelation:
source_fqn: str
Expand Down
4 changes: 1 addition & 3 deletions py2puml/example.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@

if __name__ == '__main__':
# outputs the PlantUML content in the terminal
print(''.join(
py2puml('py2puml/domain', 'py2puml.domain')
))
print(''.join(py2puml('py2puml/domain', 'py2puml.domain')))

# writes the PlantUML content in a file
with open('py2puml/py2puml.domain.puml', 'w', encoding='utf8') as puml_file:
Expand Down
30 changes: 16 additions & 14 deletions py2puml/export/namespace.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,31 @@
from py2puml.domain.package import Package
from py2puml.domain.umlitem import UmlItem


# templating constants
INDENT = ' '
PUML_NAMESPACE_START_TPL = '{indentation}namespace {namespace_name} {{'
PUML_NAMESPACE_END_TPL = '{indentation}}}\n'


def get_or_create_module_package(root_package: Package, domain_parts: List[str]) -> Package:
'''Returns or create the package containing the tail domain part'''
"""Returns or create the package containing the tail domain part"""
package = root_package
for domain_part in domain_parts:
domain_package = next((
sub_package for sub_package in package.children
if sub_package.name == domain_part
), None)
domain_package = next(
(sub_package for sub_package in package.children if sub_package.name == domain_part),
None,
)
if domain_package is None:
domain_package = Package(domain_part)
package.children.append(domain_package)
package = domain_package
return package


def visit_package(package: Package, parent_namespace_names: Tuple[str], indentation_level: int) -> Iterable[str]:
'''
"""
Recursively visits the package and its subpackages to produce the PlantUML documentation about the namespace
'''
"""
package_with_items = package.items_number > 0
# prints the namespace if:
# - it has inner uml_items
Expand All @@ -49,7 +49,7 @@ def visit_package(package: Package, parent_namespace_names: Tuple[str], indentat
# initializes the namespace decalaration but not yield yet: we don't know if it should be closed now or if there is inner content
start_of_namespace_line = PUML_NAMESPACE_START_TPL.format(
indentation=INDENT * indentation_level,
namespace_name='.'.join(namespace_names)
namespace_name='.'.join(namespace_names),
)

parent_names = () if print_namespace else namespace_names
Expand All @@ -72,21 +72,23 @@ def visit_package(package: Package, parent_namespace_names: Tuple[str], indentat
else:
yield PUML_NAMESPACE_END_TPL.format(indentation=start_of_namespace_line)


def build_packages_structure(uml_items: List[UmlItem]) -> Package:
'''
"""
Creates the Package arborescent structure with the given UML items with their fully-qualified module names
'''
"""
root_package = Package(None)
for uml_item in uml_items:
module_package = get_or_create_module_package(root_package, uml_item.fqn.split('.')[:-1])
module_package.items_number += 1

return root_package


def puml_namespace_content(uml_items: List[UmlItem]) -> Iterable[str]:
'''
"""
Yields the documentation about the packages structure in the PlantUML syntax
'''
"""
root_package = Package(None)
# creates the Package arborescent structure with the given UML items with their fully-qualified module names
for uml_item in uml_items:
Expand All @@ -95,4 +97,4 @@ def puml_namespace_content(uml_items: List[UmlItem]) -> Iterable[str]:

# yields the documentation using a visitor pattern approach
for namespace_line in visit_package(root_package, (), 0):
yield f"{namespace_line}"
yield f'{namespace_line}'
26 changes: 20 additions & 6 deletions py2puml/export/puml.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from typing import List, Iterable
from typing import Iterable, List

from py2puml.domain.umlitem import UmlItem
from py2puml.domain.umlclass import UmlClass
from py2puml.domain.umlenum import UmlEnum
from py2puml.domain.umlitem import UmlItem
from py2puml.domain.umlrelation import UmlRelation
from py2puml.export.namespace import puml_namespace_content

Expand All @@ -17,6 +17,7 @@
FEATURE_STATIC = ' {static}'
FEATURE_INSTANCE = ''


def to_puml_content(diagram_name: str, uml_items: List[UmlItem], uml_relations: List[UmlRelation]) -> Iterable[str]:
yield PUML_FILE_START.format(diagram_name=diagram_name)

Expand All @@ -30,13 +31,26 @@ def to_puml_content(diagram_name: str, uml_items: List[UmlItem], uml_relations:
uml_enum: UmlEnum = uml_item
yield PUML_ITEM_START_TPL.format(item_type='enum', item_fqn=uml_enum.fqn)
for member in uml_enum.members:
yield PUML_ATTR_TPL.format(attr_name=member.name, attr_type=member.value, staticity=FEATURE_STATIC)
yield PUML_ATTR_TPL.format(
attr_name=member.name,
attr_type=member.value,
staticity=FEATURE_STATIC,
)
yield PUML_ITEM_END
elif isinstance(uml_item, UmlClass):
uml_class: UmlClass = uml_item
yield PUML_ITEM_START_TPL.format(item_type='abstract class' if uml_item.is_abstract else 'class', item_fqn=uml_class.fqn)
yield PUML_ITEM_START_TPL.format(
item_type='abstract class' if uml_item.is_abstract else 'class',
item_fqn=uml_class.fqn,
)
for uml_attr in uml_class.attributes:
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)
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,
)
for uml_method in uml_class.methods:
yield f' {uml_method.represent_as_puml()}\n'
yield PUML_ITEM_END
else:
raise TypeError(f'cannot process uml_item of type {uml_item.__class__}')
Expand All @@ -46,7 +60,7 @@ def to_puml_content(diagram_name: str, uml_items: List[UmlItem], uml_relations:
yield PUML_RELATION_TPL.format(
source_fqn=uml_relation.source_fqn,
rel_type=uml_relation.type.value,
target_fqn=uml_relation.target_fqn
target_fqn=uml_relation.target_fqn,
)

yield PUML_FILE_FOOTER
Expand Down
Loading