From 2c483e0c9a31a86bec8b4520871dbbd927a4c0d7 Mon Sep 17 00:00:00 2001 From: Dinis Cruz Date: Tue, 28 Jan 2025 21:27:22 +0000 Subject: [PATCH] added two new MGraph__Export methods: to__dot_types and to__dot_schema couple more fixes to MGraph__Json__Edit so that now we cover all possible ways data can be added to a MGraph__Json document --- mgraph_ai/mgraph/actions/MGraph__Export.py | 86 +++++++++++ .../json/actions/MGraph__Json__Edit.py | 17 ++- .../json/actions/MGraph__Json__Export.py | 2 + .../json/actions/MGraph__Json__Screenshot.py | 12 +- .../json/actions/test_MGraph__Json__Edit.py | 137 ++++++++++++++---- 5 files changed, 224 insertions(+), 30 deletions(-) diff --git a/mgraph_ai/mgraph/actions/MGraph__Export.py b/mgraph_ai/mgraph/actions/MGraph__Export.py index 9540303..87126c3 100644 --- a/mgraph_ai/mgraph/actions/MGraph__Export.py +++ b/mgraph_ai/mgraph/actions/MGraph__Export.py @@ -72,6 +72,92 @@ def to__dot(self) -> str: lines.append('}') return '\n'.join(lines) + def to__dot_types(self) -> str: # Export as DOT graph showing node structure + lines = ['digraph {', + ' graph [fontname="Arial", ranksep=0.8]', # Basic graph styling + ' node [fontname="Arial"]', # Default node font + ' edge [fontname="Arial", fontsize=10]' # Edge styling + ] + + def fix_value(value: str) -> str: + return value.replace('Schema__MGraph__', '').replace('_', ' ') + + with self.data() as _: + # Output nodes with their IDs and type labels + for node in _.nodes(): + node_id = node.node_id + node_type = fix_value(node.node.data.node_type.__name__) + + node_attrs = ['shape=box' , # Visual styling + 'style="rounded,filled"' , # Enable both rounded corners and fill + 'fillcolor=lightblue' , # Node color + f'label="{node_type}"' ] # Show both type and ID + + if node.node_data: # Add node data if present + for field_name, field_value in node.node_data.__dict__.items(): + node_attrs.append(f'{field_name}="{field_value}"') + + attrs_str = f' [{", ".join(node_attrs)}]' + lines.append(f' "{node_id}"{attrs_str}') + + for edge in _.edges(): # Output edges using node IDs + edge_id = edge.edge_id + edge_type = fix_value(edge.edge.data.edge_type.__name__) + from_id = edge.from_node_id() + to_id = edge.to_node_id() + + lines.append(f' "{from_id}" -> "{to_id}" [label=" {edge_type}"]') + + lines.append('}') + return '\n'.join(lines) + + def to__dot_schema(self) -> str: # Export as DOT graph showing types + lines = ['digraph {', + ' graph [fontname="Arial", ranksep=0.8]', # Basic graph styling + ' node [fontname="Arial"]', # Default node font + ' edge [fontname="Arial", fontsize=10]' # Edge styling + ] + + def fix_value(value: str) -> str: + return value.replace('Schema__MGraph__', '').replace('_', ' ') + + # Track unique nodes and edges + unique_nodes = set() + unique_edges = set() + + with self.data() as _: + # First pass: collect unique node types + for node in _.nodes(): + node_type = fix_value(node.node.data.node_type.__name__) + if node_type not in unique_nodes: + unique_nodes.add(node_type) + node_attrs = ['shape=box', # Visual styling + 'style="rounded,filled"', # Enable both rounded corners and fill + 'fillcolor=lightblue'] # Node color + + if node.node_data: # Add node data if present + for field_name, field_value in node.node_data.__dict__.items(): + node_attrs.append(f'{field_name}="{field_value}"') + + attrs_str = f' [{", ".join(node_attrs)}]' + lines.append(f' "{node_type}"{attrs_str}') + + # Second pass: collect unique edge relationships + for edge in _.edges(): + edge_type = fix_value(edge.edge.data.edge_type.__name__) + from_type = fix_value(edge.from_node().node.data.node_type.__name__) + to_type = fix_value(edge.to_node().node.data.node_type.__name__) + + # Create a unique identifier for this type of connection + edge_key = (from_type, to_type, edge_type) + + if edge_key not in unique_edges: + unique_edges.add(edge_key) + lines.append(f' "{from_type}" -> "{to_type}" [label=" {edge_type}"]') + + lines.append('}') + return '\n'.join(lines) + def to__graphml(self) -> str: # Export as GraphML graphml_ns = "http://graphml.graphdrawing.org/xmlns" # Define namespace diff --git a/mgraph_ai/providers/json/actions/MGraph__Json__Edit.py b/mgraph_ai/providers/json/actions/MGraph__Json__Edit.py index 0d7c223..92d74d5 100644 --- a/mgraph_ai/providers/json/actions/MGraph__Json__Edit.py +++ b/mgraph_ai/providers/json/actions/MGraph__Json__Edit.py @@ -1,4 +1,6 @@ from mgraph_ai.providers.json.actions.MGraph__Json__Data import MGraph__Json__Data +from mgraph_ai.providers.json.schemas.Schema__MGraph__Json__Node__Dict import Schema__MGraph__Json__Node__Dict +from mgraph_ai.providers.json.schemas.Schema__MGraph__Json__Node__List import Schema__MGraph__Json__Node__List from mgraph_ai.providers.json.schemas.Schema__MGraph__Json__Node__Property__Data import Schema__MGraph__Json__Node__Property__Data from mgraph_ai.providers.json.schemas.Schema__MGraph__Json__Node__Value import Schema__MGraph__Json__Node__Value from mgraph_ai.providers.json.schemas.Schema__MGraph__Json__Node__Value__Data import Schema__MGraph__Json__Node__Value__Data @@ -9,8 +11,21 @@ class MGraph__Json__Edit(MGraph__Edit): data_type : type[MGraph__Json__Data] + def add_dict(self, node_id=None): # todo: add check that node_id is either a property or a list + node_dict = Schema__MGraph__Json__Node__Dict() + new_node = self.add_node(node_dict) + if node_id: + self.new_edge(from_node_id=node_id, to_node_id= new_node.node_id) + return new_node + + def add_list(self, node_id=None): # todo: add check that node_id is either a property or a list + node_dict = Schema__MGraph__Json__Node__List() + new_node = self.add_node(node_dict) + if node_id: + self.new_edge(from_node_id=node_id, to_node_id= new_node.node_id) + return new_node + def add_property(self, property_name, value=None, node_id=None): - print() node_property_data = Schema__MGraph__Json__Node__Property__Data(name = property_name ) node_property = Schema__MGraph__Json__Node__Property (node_data = node_property_data) new_node = self.add_node(node_property) diff --git a/mgraph_ai/providers/json/actions/MGraph__Json__Export.py b/mgraph_ai/providers/json/actions/MGraph__Json__Export.py index 6e02d51..b078cfb 100644 --- a/mgraph_ai/providers/json/actions/MGraph__Json__Export.py +++ b/mgraph_ai/providers/json/actions/MGraph__Json__Export.py @@ -33,6 +33,8 @@ def process_dict_node(self, node_id: Obj_Id, index: MGraph__Index) -> Dict[str, if value_edges: value_node_id = index.edge_to_nodes()[value_edges[0]][1] result[property_name] = self.process_node(value_node_id, index) + else: + result[property_name] = None return result def process_list_node(self, node_id: Obj_Id, index: MGraph__Index) -> List[Any]: diff --git a/mgraph_ai/providers/json/actions/MGraph__Json__Screenshot.py b/mgraph_ai/providers/json/actions/MGraph__Json__Screenshot.py index f0cd131..0b2b09f 100644 --- a/mgraph_ai/providers/json/actions/MGraph__Json__Screenshot.py +++ b/mgraph_ai/providers/json/actions/MGraph__Json__Screenshot.py @@ -40,7 +40,17 @@ def dot(self): return screenshot_bytes def dot__just_ids(self): - dot_code = self.export().to__dot() + dot_code = self.export().to__dot() + screenshot_bytes = self.create_screenshot__from__dot_code(dot_code) + return screenshot_bytes + + def dot__just_types(self): + dot_code = self.export().to__dot_types() + screenshot_bytes = self.create_screenshot__from__dot_code(dot_code) + return screenshot_bytes + + def dot__schema(self): + dot_code = self.export().to__dot_schema() screenshot_bytes = self.create_screenshot__from__dot_code(dot_code) return screenshot_bytes diff --git a/tests/unit/providers/json/actions/test_MGraph__Json__Edit.py b/tests/unit/providers/json/actions/test_MGraph__Json__Edit.py index e9df1c0..aca2511 100644 --- a/tests/unit/providers/json/actions/test_MGraph__Json__Edit.py +++ b/tests/unit/providers/json/actions/test_MGraph__Json__Edit.py @@ -1,13 +1,14 @@ -from unittest import TestCase - -from osbot_utils.utils.Dev import pprint - -from osbot_utils.helpers.Obj_Id import Obj_Id -from mgraph_ai.providers.json.domain.Domain__MGraph__Json__Node import Domain__MGraph__Json__Node -from mgraph_ai.providers.json.domain.Domain__MGraph__Json__Node__Dict import Domain__MGraph__Json__Node__Dict -from mgraph_ai.providers.json.models.Model__MGraph__Json__Node__Dict import Model__MGraph__Json__Node__Dict -from mgraph_ai.providers.json.schemas.Schema__MGraph__Json__Node__Dict import Schema__MGraph__Json__Node__Dict -from mgraph_ai.providers.json.MGraph__Json import MGraph__Json +from unittest import TestCase +from mgraph_ai.providers.json.schemas.Schema__MGraph__Json__Node__List import Schema__MGraph__Json__Node__List +from mgraph_ai.providers.json.schemas.Schema__MGraph__Json__Node__Property import Schema__MGraph__Json__Node__Property +from mgraph_ai.providers.json.schemas.Schema__MGraph__Json__Node__Value import Schema__MGraph__Json__Node__Value +from osbot_utils.utils.Json import json__equals__list_and_set +from osbot_utils.helpers.Obj_Id import Obj_Id +from mgraph_ai.providers.json.domain.Domain__MGraph__Json__Node import Domain__MGraph__Json__Node +from mgraph_ai.providers.json.domain.Domain__MGraph__Json__Node__Dict import Domain__MGraph__Json__Node__Dict +from mgraph_ai.providers.json.models.Model__MGraph__Json__Node__Dict import Model__MGraph__Json__Node__Dict +from mgraph_ai.providers.json.schemas.Schema__MGraph__Json__Node__Dict import Schema__MGraph__Json__Node__Dict +from mgraph_ai.providers.json.MGraph__Json import MGraph__Json class test_MGraph__Json__Edit(TestCase): @@ -48,31 +49,111 @@ def test_add_property(self): assert type(new_property_2 ) is Domain__MGraph__Json__Node assert type(value_node ) is Domain__MGraph__Json__Node - assert self.mgraph_json.export().to_dict() == {'1234': 'xyz', 'abc': '12345'} # confirm values set correctly - def test_add_property_to_property(self): + def test_add_list(self): + assert self.mgraph_json.export().to_dict() is None with self.mgraph_json.edit() as _: root_property_node = _.add_root_property_node() - new_property_1 = _.add_property('parent', node_id=root_property_node.node_id) - new_property_2 = _.add_property('child' , node_id=new_property_1.node_id ) + new_property_1 = _.add_property('abc', node_id=root_property_node.node_id) + export__step_1 = self.mgraph_json.export().to_dict() - assert type(new_property_1) == Domain__MGraph__Json__Node - assert type(new_property_2) == Domain__MGraph__Json__Node - # print() - # print('root_property_node', root_property_node.node_id) - # print('new_property_1 ', new_property_1.node_id ) - # print('new_property_2 ', new_property_2.node_id ) - # pprint(new_property_1.node.json()) - pprint(new_property_2.node.json()) + new_list_1 = _.add_list(node_id=new_property_1.node_id ) + export__step_2 = self.mgraph_json.export().to_dict() + + new_value_1 = _.add_value(value='an_value', node_id=new_list_1.node_id) + export__step_3 = self.mgraph_json.export().to_dict() + + new_list_2 = _.add_list(node_id=new_list_1.node_id ) + export__step_4 = self.mgraph_json.export().to_dict() + + new_dict_1 = _.add_dict(node_id=new_list_1.node_id ) + export__step_5 = self.mgraph_json.export().to_dict() + + new_property_2 = _.add_property('abc' , node_id=new_dict_1.node_id , value='a') + export__step_6 = self.mgraph_json.export().to_dict() + + new_property_3 = _.add_property('def' , node_id=root_property_node.node_id ) + export__step_7 = self.mgraph_json.export().to_dict() + + new_dict_2 = _.add_dict(node_id=new_property_3.node_id) + export__step_8 = self.mgraph_json.export().to_dict() + + root_node_id = _.data().root_node_id() + new_list_3 = _.add_list(node_id=root_node_id) - # with self.mgraph_json.index() as _: - # _.print() + export__step_9 = self.mgraph_json.export().to_dict() # this will have no effect on the output + new_value_2 = _.add_value('a', node_id=root_node_id) + export__step_10 = self.mgraph_json.export().to_dict() # this will have no effect on the output (I just wanted to have a full schema :) ) - #self.mgraph_json.screenshot().save().dot__just_ids() - #self.mgraph_json.screenshot().save().dot() + assert type(root_property_node ) is Domain__MGraph__Json__Node__Dict + assert type(new_property_1.node.data) is Schema__MGraph__Json__Node__Property + assert type(new_list_1.node.data ) is Schema__MGraph__Json__Node__List + assert type(new_value_1.node.data ) is Schema__MGraph__Json__Node__Value + assert type(new_list_2.node.data ) is Schema__MGraph__Json__Node__List + assert type(new_dict_1.node.data ) is Schema__MGraph__Json__Node__Dict + assert type(new_property_2.node.data) is Schema__MGraph__Json__Node__Property + assert type(new_property_3.node.data) is Schema__MGraph__Json__Node__Property + assert type(new_dict_2.node.data ) is Schema__MGraph__Json__Node__Dict + assert type(new_list_3.node.data ) is Schema__MGraph__Json__Node__List + assert type(new_value_2.node.data ) is Schema__MGraph__Json__Node__Value - assert self.mgraph_json.export().to_dict() == {'parent': None} # BUG should be {'parent': { 'child': None }} + assert export__step_1 == {'abc': None} + assert export__step_2 == {'abc': []} + assert export__step_3 == {'abc': ['an_value']} + assert json__equals__list_and_set(export__step_4, {'abc': ['an_value', [] ] }) is True + assert json__equals__list_and_set(export__step_5, {'abc': ['an_value', [], {} ] }) is True + assert json__equals__list_and_set(export__step_6, {'abc': ['an_value', [], {'abc': 'a' }] }) is True + assert json__equals__list_and_set(export__step_7, {'abc': ['an_value', [], {'abc': 'a' }], 'def': None}) is True + assert json__equals__list_and_set(export__step_8, {'abc': ['an_value', [], {'abc': 'a' }], 'def': {} }) is True + assert export__step_8 == export__step_9 + assert export__step_9 == export__step_10 - #pprint(self.mgraph_json.export().to_dict()) \ No newline at end of file + + + def test_add_property__just_one(self): + with self.mgraph_json.edit() as _: + root_property_node = _.add_root_property_node() + new_property_1 = _.add_property('abc' , node_id=root_property_node.node_id ) # add property with no value + + assert type(new_property_1) is Domain__MGraph__Json__Node + + assert self.mgraph_json.export().to_dict() == {'abc': None} + + def test_add_dict_to_property(self): + with self.mgraph_json.edit() as _: + root_property_node = _.add_root_property_node() + export__step_1 = self.mgraph_json.export().to_dict() + new_property_1 = _.add_property('parent' , node_id=root_property_node.node_id) + export__step_2 = self.mgraph_json.export().to_dict() + new_dict_1 = _.add_dict (node_id=new_property_1.node_id ) + export__step_3 = self.mgraph_json.export().to_dict() + new_property_2 = _.add_property('without_value', node_id=new_dict_1.node_id ) + export__step_4 = self.mgraph_json.export().to_dict() + new_property_3 = _.add_property('with_value' , node_id=new_dict_1.node_id , value='an-value') + export__step_5 = self.mgraph_json.export().to_dict() + + assert type(new_property_1) == Domain__MGraph__Json__Node + assert type(new_property_2) == Domain__MGraph__Json__Node + assert type(new_property_3) == Domain__MGraph__Json__Node + + assert export__step_1 == {} + assert export__step_2 == {'parent': None } + assert export__step_3 == {'parent': {} } + assert export__step_4 == {'parent': {'without_value': None }} + assert export__step_5 == {'parent': {'with_value' : 'an-value', 'without_value': None }} + + assert self.mgraph_json.export().to_dict() == {'parent': {'with_value' : 'an-value', + 'without_value': None }} + + # self.mgraph_json.screenshot().save().dot__just_ids () + # self.mgraph_json.screenshot().save().dot__just_types() + # self.mgraph_json.screenshot().save().dot__schema() + # self.mgraph_json.screenshot().save().dot() + + # def test_show(self): + # test_data = {'a': {'b':123}} + # self.mgraph_json.load().from_data(test_data) + # self.mgraph_json.screenshot().save().dot__just_types() + # assert self.mgraph_json.export().to_dict() == test_data \ No newline at end of file