-
Notifications
You must be signed in to change notification settings - Fork 36
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
add methods support. #30
base: main
Are you sure you want to change the base?
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,15 @@ | ||
|
||
from inspect import isabstract | ||
from inspect import isabstract, signature | ||
import inspect | ||
from typing import Type, List, Dict | ||
|
||
from re import compile | ||
from dataclasses import dataclass | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll check. |
||
|
||
from py2puml.domain.umlitem import UmlItem | ||
from py2puml.domain.umlclass import UmlClass, UmlAttribute | ||
from py2puml.domain.umlclass import UmlClass, UmlAttribute, UmlMethod | ||
from py2puml.domain.umlrelation import UmlRelation, RelType | ||
from py2puml.parsing.parseclassconstructor import parse_class_constructor | ||
from py2puml.utils import inspect_domain_definition | ||
|
||
CONCRETE_TYPE_PATTERN = compile("^<(?:class|enum) '([\\.|\\w]+)'>$") | ||
|
||
|
@@ -33,20 +33,12 @@ def handle_inheritance_relation( | |
) | ||
|
||
def inspect_static_attributes( | ||
class_type: Type, | ||
class_type_fqn: str, | ||
definition_attrs: List[UmlAttribute], | ||
class_type: Type, | ||
root_module_name: str, | ||
domain_items_by_fqn: Dict[str, UmlItem], | ||
domain_relations: List[UmlRelation] | ||
) -> List[UmlAttribute]: | ||
definition_attrs: List[UmlAttribute] = [] | ||
uml_class = UmlClass( | ||
name=class_type.__name__, | ||
fqn=class_type_fqn, | ||
attributes=definition_attrs, | ||
is_abstract=isabstract(class_type) | ||
) | ||
domain_items_by_fqn[class_type_fqn] = uml_class | ||
): | ||
# inspect_domain_definition(class_type) | ||
type_annotations = getattr(class_type, '__annotations__', None) | ||
if type_annotations is not None: | ||
|
@@ -58,7 +50,7 @@ def inspect_static_attributes( | |
if attr_class.__module__.startswith(root_module_name): | ||
attr_type = attr_class.__name__ | ||
domain_relations.append( | ||
UmlRelation(uml_class.fqn, f'{attr_class.__module__}.{attr_class.__name__}', RelType.COMPOSITION) | ||
UmlRelation(class_type_fqn, f'{attr_class.__module__}.{attr_class.__name__}', RelType.COMPOSITION) | ||
) | ||
else: | ||
attr_type = concrete_type | ||
|
@@ -73,7 +65,7 @@ def inspect_static_attributes( | |
if getattr(component_class, '__name__', None) is not None | ||
] | ||
domain_relations.extend([ | ||
UmlRelation(uml_class.fqn, f'{component_class.__module__}.{component_class.__name__}', RelType.COMPOSITION) | ||
UmlRelation(class_type_fqn, f'{component_class.__module__}.{component_class.__name__}', RelType.COMPOSITION) | ||
for component_class in component_classes | ||
if component_class.__module__.startswith(root_module_name) | ||
]) | ||
|
@@ -83,7 +75,37 @@ def inspect_static_attributes( | |
uml_attr = UmlAttribute(attr_name, attr_type, static=True) | ||
definition_attrs.append(uml_attr) | ||
|
||
return definition_attrs | ||
|
||
def inspect_methods( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this function needs unit-testing, once we agree on the implementation details |
||
definition_methods, class_type, | ||
): | ||
no_dunder = lambda x: not (x[0].startswith('__') or x[0].endswith('__')) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. just excluding magic methods There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please, make no_dunder a proper function that must be unit-tested. And please, give more meaningful names to variables (x -> member, for instance) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 😰 ups. lol I was really hacking it together. sure thing. also, I tested this with some other projects and it totally shows more than what we want to see. need to look a lot further into determining declared methods only. |
||
methods = filter(no_dunder, inspect.getmembers(class_type, callable)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. from inspect import getmembers |
||
for name, method in methods: | ||
signature = inspect.signature(method) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. from inspect import signature |
||
uml_method = UmlMethod( | ||
name=name, | ||
signature=str(signature), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it is a convenient way to get the whole signature, but:
However, I don't like how I implemented the the way attributes detected by inspection (dataclasses, class attributes) are typed: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
it does not guarantee it for sure. I should just iterate the structure we can rely on and build it from ther
It might. yeah
I'll make sure to take a closer look there. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. for now, let's leave the signature format like that, it is not how UML users expect it, but it is readable to python users |
||
) | ||
definition_methods.append(uml_method) | ||
|
||
|
||
def handle_class_type( | ||
class_type: Type, | ||
class_type_fqn: str, | ||
domain_items_by_fqn: Dict[str, UmlItem], | ||
) -> UmlClass: | ||
definition_attrs: List[UmlAttribute] = [] | ||
definition_methods: List[UmlMethod] = [] | ||
uml_class = UmlClass( | ||
name=class_type.__name__, | ||
fqn=class_type_fqn, | ||
attributes=definition_attrs, | ||
methods=definition_methods, | ||
is_abstract=isabstract(class_type) | ||
) | ||
domain_items_by_fqn[class_type_fqn] = uml_class | ||
return uml_class | ||
|
||
def inspect_class_type( | ||
class_type: Type, | ||
|
@@ -92,30 +114,30 @@ def inspect_class_type( | |
domain_items_by_fqn: Dict[str, UmlItem], | ||
domain_relations: List[UmlRelation] | ||
): | ||
attributes = inspect_static_attributes( | ||
class_type, class_type_fqn, root_module_name, | ||
domain_items_by_fqn, domain_relations | ||
uml_class = handle_class_type(class_type, class_type_fqn, domain_items_by_fqn) | ||
inspect_static_attributes( | ||
class_type_fqn, uml_class.attributes, class_type, root_module_name, domain_relations | ||
) | ||
inspect_methods(uml_class.methods, class_type) | ||
instance_attributes, compositions = parse_class_constructor(class_type, class_type_fqn, root_module_name) | ||
attributes.extend(instance_attributes) | ||
uml_class.attributes.extend(instance_attributes) | ||
domain_relations.extend(compositions.values()) | ||
|
||
handle_inheritance_relation(class_type, class_type_fqn, root_module_name, domain_relations) | ||
|
||
def inspect_dataclass_type( | ||
class_type: Type[dataclass], | ||
class_type: Type, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👌 |
||
class_type_fqn: str, | ||
root_module_name: str, | ||
domain_items_by_fqn: Dict[str, UmlItem], | ||
domain_relations: List[UmlRelation] | ||
): | ||
for attribute in inspect_static_attributes( | ||
class_type, | ||
class_type_fqn, | ||
root_module_name, | ||
domain_items_by_fqn, | ||
domain_relations | ||
): | ||
uml_class = handle_class_type(class_type, class_type_fqn, domain_items_by_fqn) | ||
inspect_static_attributes( | ||
class_type_fqn, uml_class.attributes, class_type, root_module_name, domain_relations | ||
) | ||
inspect_methods(uml_class.methods, class_type) | ||
for attribute in uml_class.attributes: | ||
attribute.static = False | ||
|
||
handle_inheritance_relation(class_type, class_type_fqn, root_module_name, domain_relations) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,5 +16,6 @@ def inspect_namedtuple_type( | |
attributes=[ | ||
UmlAttribute(tuple_field, 'Any', False) | ||
for tuple_field in namedtuple_type._fields | ||
] | ||
], | ||
methods=[], | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. technically namedtuples can have methods. should we support it? from typing import NamedTuple
class Employee(NamedTuple):
"""Represents an employee."""
name: str
id: int = 3
def foo(self) -> str:
return f'<Employee {self.name}, id={self.id}>'
emp = Employee('Hello')
print(emp)
print(emp.foo())
print(emp._fields)
``` There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if it is easy to distinguish added methods (like the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. let's see after I figure it out for regular classes and then we get back to this one to do it or split it. |
||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
guidelines favor named imports instead of module imports: named imports gives meaning to what is happening in the module, module imports clutters the module's memory footprint