diff --git a/app.py b/app.py index 8f2613b..e5dc0ea 100644 --- a/app.py +++ b/app.py @@ -8,7 +8,7 @@ dataset_controller = DatasetController.get_instance() -dataset_name = 'No project selected' if dataset_controller.current_dataset is None else dataset_controller.current_dataset.dataset_name +dataset_name = 'No project selected' if dataset_controller.current_dataset is None else dataset_controller.current_dataset.name app = Flask(__name__) app.config['SECRET_KEY'] = 'test_key' @@ -134,3 +134,5 @@ def test_all_objects(): # TODO tidy up the logs # TODO file explorer for load dataset # TODO make sure it runs from docker also +# TODO fetch Variables, FunctionArgs, Imports +# TODO add pystruct commands for --uml, --report, --structure diff --git a/pystruct/objects/imports_data_objects.py b/pystruct/objects/imports_data_objects.py index 84f0799..2630d40 100644 --- a/pystruct/objects/imports_data_objects.py +++ b/pystruct/objects/imports_data_objects.py @@ -5,7 +5,44 @@ from pystruct.objects.data_objects import DataframeObjectABC, HTMLTableObjectABC, HTMLObjectABC from pystruct.objects.metric_obj import IsScriptFile from pystruct.objects.python_object import PObject +from pystruct.python.code_structure import ClassMethodObj from pystruct.reports.import_graph import CollectImportsVisitor +from pystruct.visitors.visitor import TreeNodeVisitor + + +class ClassMethodsRawDataframe(DataframeObjectABC): + class CollectClassMethodsVisitor(TreeNodeVisitor): + def __init__(self): + self._class_methods_list = [] + + def visit_class_method(self, node): + class_name = node.parent.data.name + method = node.data.name.split('.')[-1] + self._class_methods_list.append({'class': class_name, 'method': method}) + + def to_dataframe(self): + return pd.DataFrame(self._class_methods_list, columns=['class', 'method']) + + def build(self): + pobj = PObject().python_source_object() + class_methods = ClassMethodsRawDataframe.CollectClassMethodsVisitor() + pobj.use_visitor(class_methods) + return class_methods.to_dataframe() + + +class ClassMethodsEnrichedDataframe(DataframeObjectABC): + def build(self): + df = ClassMethodsRawDataframe().dataframe() + self.enrich_df(df) + return df + + def enrich_df(self, df): + df['visibility'] = df['method'].apply(lambda method: 'private' if method[:2] == '__' + else 'protected' if method[:1] == '_' else 'public') + + +if __name__ == '__main__': + ClassMethodsEnrichedDataframe().dataframe() class ImportsRawDataframe(DataframeObjectABC): diff --git a/pystruct/python/code_structure.py b/pystruct/python/code_structure.py index d2bd709..0c78544 100644 --- a/pystruct/python/code_structure.py +++ b/pystruct/python/code_structure.py @@ -14,6 +14,10 @@ class ModuleObj(PythonCodeObj): type = "module" +class ImportObj(PythonCodeObj): + type = "import" + + class ClassObj(CompoundStatementCodeMixin): type = "class" diff --git a/pystruct/python/python_obj_factory.py b/pystruct/python/python_obj_factory.py index 908e504..3a2c08e 100644 --- a/pystruct/python/python_obj_factory.py +++ b/pystruct/python/python_obj_factory.py @@ -28,6 +28,11 @@ def module(self, name, code, parent=None): module_obj = ModuleObj(name, path, code) return TreeNode(parent, data=module_obj) + def import_node(self, name, code, parent=None): + path = parent.data.path + import_obj = ImportObj(name, path, code) + return TreeNode(parent, data=import_obj) + def class_node(self, name, code, parent=None): path = parent.data.path class_obj = ClassObj(name, path, code) @@ -50,6 +55,8 @@ def node_dict_to_object(self, name, node_dict): return DirectoryObj(name=name, path=None) elif node_dict['type'] == "module": return ModuleObj(name, None, node_dict['code']) + elif node_dict['type'] == "import": + return ImportObj(name, None, node_dict['code']) elif node_dict['type'] == "class": return ClassObj(name, None, node_dict['code']) elif node_dict['type'] == "function": diff --git a/pystruct/utils/storage.py b/pystruct/utils/storage.py index 031645a..c57ed90 100644 --- a/pystruct/utils/storage.py +++ b/pystruct/utils/storage.py @@ -2,7 +2,7 @@ from io import StringIO import shutil -from git import Repo +# from git import Repo from pystruct.utils.python_utils import MultiSingleton from pystruct.utils.logs import log_disk_ops diff --git a/pystruct/visitors/init_visitor.py b/pystruct/visitors/init_visitor.py index b3ed1e9..60eb50e 100644 --- a/pystruct/visitors/init_visitor.py +++ b/pystruct/visitors/init_visitor.py @@ -6,7 +6,7 @@ from pystruct.utils.python_file_utils import is_python_file from pystruct.visitors.visitor import TreeNodeVisitor - +# https://python-ast-explorer.com/ class PythonObjInitializer(TreeNodeVisitor): def __init__(self, obj_factory): @@ -33,8 +33,12 @@ def visit_directory(self, node): def visit_module(self, node): log_cyan(f".Visiting module {node}", verbosity=3) - asts_to_fetch = {ast.ClassDef: self._obj_factory.class_node, - ast.FunctionDef: self._obj_factory.function} + asts_to_fetch = { + # ast.Import: self._obj_factory.import_node, + # ast.ImportFrom: self._obj_factory.import_node, + ast.ClassDef: self._obj_factory.class_node, + ast.FunctionDef: self._obj_factory.function + } list_objs = [] diff --git a/pystruct/visitors/visitor.py b/pystruct/visitors/visitor.py index 93b2f1f..73dff8b 100644 --- a/pystruct/visitors/visitor.py +++ b/pystruct/visitors/visitor.py @@ -20,6 +20,8 @@ def visit(self, node): self.visit_directory(node) elif node.data.type == "module": self.visit_module(node) + elif node.data.type == "import": + self.visit_import(node) elif node.data.type == "class": self.visit_class(node) elif node.data.type == "function": @@ -35,6 +37,9 @@ def visit_directory(self, node): def visit_module(self, node): pass + def visit_import(self, node): + pass + def visit_class(self, node): pass diff --git a/tests/python/test_python_source_obj.py b/tests/python/test_python_source_obj.py index 9aacdc3..42011a6 100644 --- a/tests/python/test_python_source_obj.py +++ b/tests/python/test_python_source_obj.py @@ -19,7 +19,7 @@ def a_class_method(self): """ -expected_dict_str = r"""{"pystruct": {"type": "directory", "code": null, "branches": ["pystruct.a"]}, "pystruct.a": {"type": "module", "code": "\nimport one_package\nfrom another_package import that_module\n\ndef a_function(some, arguments):\n var = \"something\"\n return var\n\nclass a_class:\n def a_class_method(self):\n return 1\n\n", "branches": ["pystruct.a.a_function", "pystruct.a.a_class"]}, "pystruct.a.a_function": {"type": "function", "code": "def a_function(some, arguments):\n var = \"something\"\n return var", "branches": []}, "pystruct.a.a_class": {"type": "class", "code": "class a_class:\n def a_class_method(self):\n return 1", "branches": ["pystruct.a.a_class.a_class_method"]}, "pystruct.a.a_class.a_class_method": {"type": "class_method", "code": "def a_class_method(self):\n return 1", "branches": []}}""" +# expected_dict_str = r"""{"pystruct": {"type": "directory", "code": null, "branches": ["pystruct.a"]}, "pystruct.a": {"type": "module", "code": "\nimport one_package\nfrom another_package import that_module\n\ndef a_function(some, arguments):\n var = \"something\"\n return var\n\nclass a_class:\n def a_class_method(self):\n return 1\n\n", "branches": ["pystruct.a.a_function", "pystruct.a.a_class"]}, "pystruct.a.a_function": {"type": "function", "code": "def a_function(some, arguments):\n var = \"something\"\n return var", "branches": []}, "pystruct.a.a_class": {"type": "class", "code": "class a_class:\n def a_class_method(self):\n return 1", "branches": ["pystruct.a.a_class.a_class_method"]}, "pystruct.a.a_class.a_class_method": {"type": "class_method", "code": "def a_class_method(self):\n return 1", "branches": []}}""" class TestPythonSourceObj(unittest.TestCase): @@ -29,6 +29,7 @@ def setUp(self): f.write(a_py_file_content) def test_create_from_project_source(self): + expected_dict_str = open('/tests/res_files/example_PObject.json').read() expected_dict = json.loads(expected_dict_str.replace('pystruct', os.path.basename(self.tmp_dir.name))) pobj = PythonSourceObj.from_dict(expected_dict) @@ -37,6 +38,8 @@ def test_create_from_project_source(self): self.assertEqual(test_dict, expected_dict) def test_create_from_dict(self): + expected_dict_str = open('/tests/res_files/example_PObject.json').read() + pobj = PythonSourceObj.from_dict(self.tmp_dir.name) test_dict = pobj.to_dict() @@ -45,5 +48,41 @@ def test_create_from_dict(self): self.assertEqual(test_dict, expected_dict) +class TestPythonSourceObjIntegration(unittest.TestCase): + def setUp(self) -> None: + path = "../res_files/example_project" + self.pso = PythonSourceObj.from_project_source(path) + self.pso_dict = self.pso.to_dict() + self.expected_dict = json.loads(open('../res_files/example_PObject.json').read()) + self.maxDiff = None + + def test_directories(self): + type = 'directory' + example_obj = {k: v for k, v in self.pso_dict.items() if v['type'] == type} + expected_obj = {k: v for k, v in self.expected_dict.items() if v['type'] == type} + self.assertDictEqual(example_obj, expected_obj) + + def test_modules(self): + type = 'module' + example_obj = {k: v for k, v in self.pso_dict.items() if v['type'] == type} + expected_obj = {k: v for k, v in self.expected_dict.items() if v['type'] == type} + self.assertDictEqual(example_obj, expected_obj) + + def test_functions(self): + type = 'function' + example_obj = {k: v for k, v in self.pso_dict.items() if v['type'] == type} + expected_obj = {k: v for k, v in self.expected_dict.items() if v['type'] == type} + self.assertDictEqual(example_obj, expected_obj) + + def test_classes(self): + type = 'class' + example_obj = {k: v for k, v in self.pso_dict.items() if v['type'] == type} + expected_obj = {k: v for k, v in self.expected_dict.items() if v['type'] == type} + self.assertDictEqual(example_obj, expected_obj) + + def test_result(self): + self.assertDictEqual(self.pso_dict, self.expected_dict) + + if __name__ == '__main__': unittest.main() diff --git a/tests/res_files/example_PObject.json b/tests/res_files/example_PObject.json new file mode 100644 index 0000000..2ce7d4f --- /dev/null +++ b/tests/res_files/example_PObject.json @@ -0,0 +1,34 @@ +{ + "example_project": { + "type": "directory", + "code": null, + "branches": [ + "example_project.example_module1" + ] + }, + "example_project.example_module1": { + "type": "module", + "code": "import one_package\nfrom another_package import that_module\n\ndef a_function(some, arguments):\n var = \"something\"\n return var\n\nclass a_class:\n def a_class_method(self):\n return 1\n", + "branches": [ + "example_project.example_module1.a_function", + "example_project.example_module1.a_class" + ] + }, + "example_project.example_module1.a_function": { + "type": "function", + "code": "def a_function(some, arguments):\n var = \"something\"\n return var", + "branches": [] + }, + "example_project.example_module1.a_class": { + "type": "class", + "code": "class a_class:\n def a_class_method(self):\n return 1", + "branches": [ + "example_project.example_module1.a_class.a_class_method" + ] + }, + "pystruct.a.a_class.a_class_method": { + "type": "class_method", + "code": "def a_class_method(self):\n return 1", + "branches": [] + } +} \ No newline at end of file diff --git a/tests/res_files/example_project/example_module1.py b/tests/res_files/example_project/example_module1.py new file mode 100644 index 0000000..9b96928 --- /dev/null +++ b/tests/res_files/example_project/example_module1.py @@ -0,0 +1,10 @@ +import one_package +from another_package import that_module + +def a_function(some, arguments): + var = "something" + return var + +class a_class: + def a_class_method(self): + return 1