From ce5e47829ee5225ffb12969fa720c3cf4c696eba Mon Sep 17 00:00:00 2001 From: Ali Date: Mon, 1 Apr 2024 09:45:15 -0400 Subject: [PATCH 01/20] initial commit --- tests/nodes/primary_nodes/test_project.py | 25 +++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/nodes/primary_nodes/test_project.py b/tests/nodes/primary_nodes/test_project.py index 3812a1e60..c6fd8e90a 100644 --- a/tests/nodes/primary_nodes/test_project.py +++ b/tests/nodes/primary_nodes/test_project.py @@ -1,6 +1,9 @@ +import copy import json import uuid +import pytest + import cript from tests.utils.integration_test_helper import ( delete_integration_node_helper, @@ -95,3 +98,25 @@ def test_integration_project(cript_api, simple_project_node): # ========= test delete ========= delete_integration_node_helper(cript_api=cript_api, node_to_delete=simple_project_node) + + +@pytest.mark.skip(reason="api") +def test_save_project_node(cript_api, simple_project_node, complex_project_node): + """ + pytest nodes/primary_nodes/test_project.py::test_save_project_node + """ + + # with cript.API(host="https://lb-stage.mycriptapp.org/") as api: + # with open("new_project.json") as json_handle: + # proj_json = json.load(json_handle) + + # proj = cript_api.load_nodes_from_json(nodes_json=proj_json) + # Modify deep in the tree + proj = copy.deepcopy(complex_project_node) + material_to_modify = proj.collection[0].inventory[0].material[0] + material_to_modify.name = "this is sure to be a new name" + + # Delete a node + proj.material[0].property = [] + + cript_api.save(proj) From 82f0cf34bed7af033563f7681277ddf877659722 Mon Sep 17 00:00:00 2001 From: Ali Date: Mon, 1 Apr 2024 09:52:32 -0400 Subject: [PATCH 02/20] push changes --- tests/nodes/primary_nodes/test_project.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/nodes/primary_nodes/test_project.py b/tests/nodes/primary_nodes/test_project.py index c6fd8e90a..3c6a250d5 100644 --- a/tests/nodes/primary_nodes/test_project.py +++ b/tests/nodes/primary_nodes/test_project.py @@ -100,7 +100,7 @@ def test_integration_project(cript_api, simple_project_node): delete_integration_node_helper(cript_api=cript_api, node_to_delete=simple_project_node) -@pytest.mark.skip(reason="api") +# @pytest.mark.skip(reason="api") def test_save_project_node(cript_api, simple_project_node, complex_project_node): """ pytest nodes/primary_nodes/test_project.py::test_save_project_node @@ -119,4 +119,4 @@ def test_save_project_node(cript_api, simple_project_node, complex_project_node) # Delete a node proj.material[0].property = [] - cript_api.save(proj) + cript_api.save_new(proj) From b656fdee2ccd0b8806cbc94eef4617ad92d02ef5 Mon Sep 17 00:00:00 2001 From: Ali Date: Mon, 1 Apr 2024 09:56:50 -0400 Subject: [PATCH 03/20] initial commit trying testing --- src/cript/api/api.py | 92 +++++++++++++++++++++++++++++++++++++++++ src/cript/nodes/core.py | 26 +++++++++++- 2 files changed, 117 insertions(+), 1 deletion(-) diff --git a/src/cript/api/api.py b/src/cript/api/api.py index dfa69333b..0892a0ca4 100644 --- a/src/cript/api/api.py +++ b/src/cript/api/api.py @@ -31,6 +31,9 @@ from cript.api.utils.web_file_downloader import download_file_from_url from cript.api.valid_search_modes import SearchModes from cript.nodes.primary_nodes.project import Project +from cript.nodes.util import load_nodes_from_json + +# from cript.nodes.core import Project # Do not use this directly! That includes devs. # Use the `_get_global_cached_api for access. @@ -47,6 +50,25 @@ def _get_global_cached_api(): return _global_cached_api +class LastModifiedDict(dict): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._order = list(self.keys()) + + def __setitem__(self, key, value): + super().__setitem__(key, value) + if key in self._order: + self._order.remove(key) + self._order.append(key) + + def keys_sorted_by_last_modified(self): + order = [] + for key in self._order: + if key in self: + order.append(key) + return order + + class API: """ ## Definition @@ -403,6 +425,76 @@ def api_version(self): return self._api_version def save(self, project: Project) -> None: + # Member function of API + ################################################################################ + + # In this mockup, we can't actually do the search: + # print("old_node_paginator = self.search(node_type=cript.Project, search_mode=cript.SearchModes.UUID, value_to_search=project)") + # print("old_node_paginator.auto_load_nodes = False") + + # node_type=cript.Project this needs to be dynamically loaded with locals ? + + old_node_paginator = self.search(node_type=Project, search_mode=SearchModes.UUID, value_to_search=project) + old_node_paginator.auto_load_nodes = False + try: + # print("old_node_json = next(old_node_paginator)") + + # # Just for this mock-up we load from the file instead + # with open("new_project.json") as json_handle: + # old_node_json = json_handle.read() + old_node_json = next(old_node_paginator) + + except StopIteration: # New Project do POST instead + # Do the POST request call. + return # Return here, since we are done after Posting + + # This is where a patch is needed + + # Load the project in a separate cache + # old_project, old_uuid_map = cript.load_nodes_from_json(nodes_json=old_node_json, _use_uuid_cache={}) + old_project, old_uuid_map = load_nodes_from_json(nodes_json=old_node_json, _use_uuid_cache={}) + + # Check if there is any difference between old and new. + # if project.deep_equal(project, old_project): + if project.deep_equal(old_project): + return # No save necessary, since nothing changed + + delete_uuid = [] + + patch_map = LastModifiedDict() + + # Iterate the new project in DFS + for node in project: + try: + old_node = old_uuid_map[node.uuid] + except KeyError: + # This node only exists in the new new project, + # But it has a parent, that will patch it in, so we don't need to do anything + pass + + # Let's see if we need to delete any children, that existed in the old node, but don't exit in the new node. + node_child_map = {child.uuid: child for child in node.find_children({}, search_depth=1)} + old_child_map = {child.uuid: child for child in old_node.find_children({}, search_depth=1)} + for old_uuid in old_child_map: + if old_uuid not in node_child_map: + if old_uuid not in delete_uuid: + delete_uuid += [old_uuid] + + # Next we check if the current new node needs a patch + # like # project.deep_equal(old_project) or shallow_equal(node, old_node): + if not node.shallow_equal(old_node): + patch_map[node.uuid] = node + + for uuid in reversed(patch_map.keys_sorted_by_last_modified()): + node = patch_map[uuid] + print(f"Doing API PATCH for {node.uuid}") + + for uuid in delete_uuid: + print(f"Doing API Delete for {uuid}") + + ################################################################################ + + def save_new(self, project: Project) -> None: """ This method takes a project node, serializes the class into JSON and then sends the JSON to be saved to the API. diff --git a/src/cript/nodes/core.py b/src/cript/nodes/core.py index 0a74fa02d..68edb19e4 100644 --- a/src/cript/nodes/core.py +++ b/src/cript/nodes/core.py @@ -68,6 +68,30 @@ def node_type_snake_case(self): snake_case = re.sub(r"(? Date: Mon, 1 Apr 2024 10:55:04 -0400 Subject: [PATCH 04/20] unexpected behavior for load node from json --- src/cript/api/api.py | 6 +++--- tests/nodes/primary_nodes/test_project.py | 25 +++++++++++++++++++++-- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/cript/api/api.py b/src/cript/api/api.py index 0892a0ca4..e9121df4a 100644 --- a/src/cript/api/api.py +++ b/src/cript/api/api.py @@ -424,7 +424,7 @@ def api_prefix(self): def api_version(self): return self._api_version - def save(self, project: Project) -> None: + def save_new(self, project: Project) -> None: # Member function of API ################################################################################ @@ -434,7 +434,7 @@ def save(self, project: Project) -> None: # node_type=cript.Project this needs to be dynamically loaded with locals ? - old_node_paginator = self.search(node_type=Project, search_mode=SearchModes.UUID, value_to_search=project) + old_node_paginator = self.search(node_type=Project, search_mode=SearchModes.UUID, value_to_search=str(project.uuid)) old_node_paginator.auto_load_nodes = False try: # print("old_node_json = next(old_node_paginator)") @@ -494,7 +494,7 @@ def save(self, project: Project) -> None: ################################################################################ - def save_new(self, project: Project) -> None: + def save(self, project: Project) -> None: """ This method takes a project node, serializes the class into JSON and then sends the JSON to be saved to the API. diff --git a/tests/nodes/primary_nodes/test_project.py b/tests/nodes/primary_nodes/test_project.py index 3c6a250d5..9d975beaa 100644 --- a/tests/nodes/primary_nodes/test_project.py +++ b/tests/nodes/primary_nodes/test_project.py @@ -100,7 +100,7 @@ def test_integration_project(cript_api, simple_project_node): delete_integration_node_helper(cript_api=cript_api, node_to_delete=simple_project_node) -# @pytest.mark.skip(reason="api") +@pytest.mark.skip(reason="api") def test_save_project_node(cript_api, simple_project_node, complex_project_node): """ pytest nodes/primary_nodes/test_project.py::test_save_project_node @@ -112,11 +112,32 @@ def test_save_project_node(cript_api, simple_project_node, complex_project_node) # proj = cript_api.load_nodes_from_json(nodes_json=proj_json) # Modify deep in the tree - proj = copy.deepcopy(complex_project_node) + print("------------\nstarting") + proj0 = copy.deepcopy(complex_project_node) + proj_json = proj0.get_json().json + cript_api.save_new(proj0) + + proj = cript.load_nodes_from_json(nodes_json=json.dumps(proj_json)) + print("\n----proj_loaded") + print(proj) + + print("------------\n1111111") + print(type(proj)) + print("why is this returning a string and not a node?") + quit() + print(proj.get_json().json) material_to_modify = proj.collection[0].inventory[0].material[0] + print("\n\nproj.collection[0].inventory[0].material[0]") + print(proj.collection[0].inventory[0].material[0]) material_to_modify.name = "this is sure to be a new name" # Delete a node proj.material[0].property = [] cript_api.save_new(proj) + + # now we need to reload the test in + + print("------------\n2222222") + print(proj.get_json().json) + quit() From fbabd64a2bf633f017890ffc545b38444264af31 Mon Sep 17 00:00:00 2001 From: Ali Date: Mon, 1 Apr 2024 11:00:03 -0400 Subject: [PATCH 05/20] commit -m --- src/cript/api/api.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/cript/api/api.py b/src/cript/api/api.py index e9121df4a..3e1382384 100644 --- a/src/cript/api/api.py +++ b/src/cript/api/api.py @@ -485,12 +485,12 @@ def save_new(self, project: Project) -> None: if not node.shallow_equal(old_node): patch_map[node.uuid] = node - for uuid in reversed(patch_map.keys_sorted_by_last_modified()): - node = patch_map[uuid] + for uuid_ in reversed(patch_map.keys_sorted_by_last_modified()): + node = patch_map[uuid_] print(f"Doing API PATCH for {node.uuid}") - for uuid in delete_uuid: - print(f"Doing API Delete for {uuid}") + for uuid_ in delete_uuid: + print(f"Doing API Delete for {uuid_}") ################################################################################ From 83c6f9ddd17bb29e8e92111949252dbd2218b9e5 Mon Sep 17 00:00:00 2001 From: Ali Date: Mon, 1 Apr 2024 11:43:45 -0400 Subject: [PATCH 06/20] changes to test proj --- tests/nodes/primary_nodes/test_project.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/nodes/primary_nodes/test_project.py b/tests/nodes/primary_nodes/test_project.py index 9d975beaa..b64de8795 100644 --- a/tests/nodes/primary_nodes/test_project.py +++ b/tests/nodes/primary_nodes/test_project.py @@ -5,6 +5,7 @@ import pytest import cript +from cript.nodes.util import load_nodes_from_json from tests.utils.integration_test_helper import ( delete_integration_node_helper, save_integration_node_helper, @@ -115,10 +116,14 @@ def test_save_project_node(cript_api, simple_project_node, complex_project_node) print("------------\nstarting") proj0 = copy.deepcopy(complex_project_node) proj_json = proj0.get_json().json + print("type proj_json") + print(type(proj_json)) cript_api.save_new(proj0) + print("---- finished save --- now node") + # proj = cript.load_nodes_from_json(nodes_json=json.dumps(proj_json)) - proj = cript.load_nodes_from_json(nodes_json=json.dumps(proj_json)) - print("\n----proj_loaded") + proj = load_nodes_from_json(nodes_json=proj_json) + print("\n----proj loaded") print(proj) print("------------\n1111111") From 5ec01c5f692474db07011738b65daba0b6659110 Mon Sep 17 00:00:00 2001 From: Ali Date: Mon, 1 Apr 2024 13:18:34 -0400 Subject: [PATCH 07/20] test seems to work for project now we will extend this testing to all nodes and attributes and deeper nested objects --- tests/nodes/primary_nodes/test_project.py | 42 ++++++++++++++++------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/tests/nodes/primary_nodes/test_project.py b/tests/nodes/primary_nodes/test_project.py index b64de8795..66379d1d2 100644 --- a/tests/nodes/primary_nodes/test_project.py +++ b/tests/nodes/primary_nodes/test_project.py @@ -115,7 +115,11 @@ def test_save_project_node(cript_api, simple_project_node, complex_project_node) # Modify deep in the tree print("------------\nstarting") proj0 = copy.deepcopy(complex_project_node) - proj_json = proj0.get_json().json + + proj_json = proj0.get_expanded_json() # .get_json().json + + # proj2, proj_cache = cript.load_nodes_from_json(nodes_json=proj_json, _use_uuid_cache={}) + print("type proj_json") print(type(proj_json)) cript_api.save_new(proj0) @@ -123,26 +127,38 @@ def test_save_project_node(cript_api, simple_project_node, complex_project_node) # proj = cript.load_nodes_from_json(nodes_json=json.dumps(proj_json)) proj = load_nodes_from_json(nodes_json=proj_json) - print("\n----proj loaded") - print(proj) + print("\n----proj.collection[0].inventory[0].material[0]") + print(proj.collection[0].inventory[0].material[0].get_json().json) print("------------\n1111111") - print(type(proj)) - print("why is this returning a string and not a node?") - quit() - print(proj.get_json().json) - material_to_modify = proj.collection[0].inventory[0].material[0] - print("\n\nproj.collection[0].inventory[0].material[0]") - print(proj.collection[0].inventory[0].material[0]) + # print(type(proj)) + # print("why is this returning a string and not a node?") + proj_loaded, proj_cache = cript.load_nodes_from_json(nodes_json=proj0.get_expanded_json(), _use_uuid_cache={}) + # print("\n----proj_loaded") + # print(proj_loaded) + # quit() + # print(proj_loaded.get_json().json) + material_to_modify = proj_loaded.collection[0].inventory[0].material[0] + print("\n\n proj_loaded.collection[0].inventory[0].material[0]") + print(proj_loaded.collection[0].inventory[0].material[0].get_json().json) material_to_modify.name = "this is sure to be a new name" # Delete a node - proj.material[0].property = [] + proj_loaded.material[0].property = [] - cript_api.save_new(proj) + cript_api.save_new(proj_loaded) # now we need to reload the test in print("------------\n2222222") - print(proj.get_json().json) + # print(proj_loaded.get_json().json["property"]) + # print(proj_loaded2.get_json().json["property"]) + + proj_loaded2, proj_cache = cript.load_nodes_from_json(nodes_json=proj_loaded.get_expanded_json(), _use_uuid_cache={}) + print("\n\n proj_loaded2.collection[0].inventory[0].material[0]") + print(proj_loaded2.collection[0].inventory[0].material[0].get_json().json) + + print("------------\n333333333") + # print(proj_loaded2.get_json().json["property"]) + # print(proj_loaded2.get_json().json["property"]) quit() From be4827305655067ddbf735ec31699d747d35dd82 Mon Sep 17 00:00:00 2001 From: Ali Date: Mon, 1 Apr 2024 15:51:06 -0400 Subject: [PATCH 08/20] added some --- src/cript/api/api.py | 20 ++++++++- tests/nodes/primary_nodes/test_project.py | 49 +++++++++-------------- tests/utils/integration_test_helper.py | 5 +++ 3 files changed, 42 insertions(+), 32 deletions(-) diff --git a/src/cript/api/api.py b/src/cript/api/api.py index 3e1382384..7568f47c1 100644 --- a/src/cript/api/api.py +++ b/src/cript/api/api.py @@ -424,7 +424,7 @@ def api_prefix(self): def api_version(self): return self._api_version - def save_new(self, project: Project) -> None: + def save(self, project: Project) -> None: # Member function of API ################################################################################ @@ -445,7 +445,12 @@ def save_new(self, project: Project) -> None: old_node_json = next(old_node_paginator) except StopIteration: # New Project do POST instead + # Do the POST request call. + data = project.get_json().json + response = self._capsule_request(url_path="/project/", method="POST", data=data) + print("----700----") + print(response.json) return # Return here, since we are done after Posting # This is where a patch is needed @@ -485,16 +490,27 @@ def save_new(self, project: Project) -> None: if not node.shallow_equal(old_node): patch_map[node.uuid] = node + url_path = f"/{self.node_type}/{self.uuid}" for uuid_ in reversed(patch_map.keys_sorted_by_last_modified()): node = patch_map[uuid_] + + # patch capsule request goes here, we are going down the list in reversed order print(f"Doing API PATCH for {node.uuid}") + # either link if found or patch json to parent + data = node.get_jason().json + self._capsule_request(url_path=url_path, method="PATCH", data=json.dumps(data)) for uuid_ in delete_uuid: + # do the delete + # *unlinking here + # actually here we are able to send list of uuids to be deleted - optimize later print(f"Doing API Delete for {uuid_}") + unlink_payload = {"uuid": str(uuid_)} + self._capsule_request(url_path=url_path, method="DELETE", data=json.dumps(unlink_payload)) ################################################################################ - def save(self, project: Project) -> None: + def save_old(self, project: Project) -> None: """ This method takes a project node, serializes the class into JSON and then sends the JSON to be saved to the API. diff --git a/tests/nodes/primary_nodes/test_project.py b/tests/nodes/primary_nodes/test_project.py index 66379d1d2..70ffa4b79 100644 --- a/tests/nodes/primary_nodes/test_project.py +++ b/tests/nodes/primary_nodes/test_project.py @@ -101,46 +101,39 @@ def test_integration_project(cript_api, simple_project_node): delete_integration_node_helper(cript_api=cript_api, node_to_delete=simple_project_node) -@pytest.mark.skip(reason="api") -def test_save_project_node(cript_api, simple_project_node, complex_project_node): +@pytest.mark.skip(reason="api, and we overrode save_new to save ") +def test_save_project_change_material(cript_api, simple_project_node, complex_project_node): """ - pytest nodes/primary_nodes/test_project.py::test_save_project_node + WIP still + pytest nodes/primary_nodes/test_project.py::test_save_project_change_material """ # with cript.API(host="https://lb-stage.mycriptapp.org/") as api: # with open("new_project.json") as json_handle: # proj_json = json.load(json_handle) - # proj = cript_api.load_nodes_from_json(nodes_json=proj_json) # Modify deep in the tree + print("------------\nstarting") - proj0 = copy.deepcopy(complex_project_node) + proj0 = copy.deepcopy(complex_project_node) proj_json = proj0.get_expanded_json() # .get_json().json - - # proj2, proj_cache = cript.load_nodes_from_json(nodes_json=proj_json, _use_uuid_cache={}) - - print("type proj_json") - print(type(proj_json)) cript_api.save_new(proj0) - print("---- finished save --- now node") - # proj = cript.load_nodes_from_json(nodes_json=json.dumps(proj_json)) + + print("---- finished save --- now load node") proj = load_nodes_from_json(nodes_json=proj_json) - print("\n----proj.collection[0].inventory[0].material[0]") + + print("\n----proj.collection[0].inventory[0].material[0]\n") + print(proj.collection[0].inventory[0].material[0].get_json().json) - print("------------\n1111111") - # print(type(proj)) - # print("why is this returning a string and not a node?") proj_loaded, proj_cache = cript.load_nodes_from_json(nodes_json=proj0.get_expanded_json(), _use_uuid_cache={}) - # print("\n----proj_loaded") - # print(proj_loaded) - # quit() - # print(proj_loaded.get_json().json) + material_to_modify = proj_loaded.collection[0].inventory[0].material[0] - print("\n\n proj_loaded.collection[0].inventory[0].material[0]") + print("\n\n proj_loaded.collection[0].inventory[0].material[0]\n") print(proj_loaded.collection[0].inventory[0].material[0].get_json().json) + material_to_modify.name = "this is sure to be a new name" # Delete a node @@ -150,15 +143,11 @@ def test_save_project_node(cript_api, simple_project_node, complex_project_node) # now we need to reload the test in - print("------------\n2222222") - # print(proj_loaded.get_json().json["property"]) - # print(proj_loaded2.get_json().json["property"]) - proj_loaded2, proj_cache = cript.load_nodes_from_json(nodes_json=proj_loaded.get_expanded_json(), _use_uuid_cache={}) - print("\n\n proj_loaded2.collection[0].inventory[0].material[0]") + print("\n\n proj_loaded2.collection[0].inventory[0].material[0]\n") print(proj_loaded2.collection[0].inventory[0].material[0].get_json().json) - print("------------\n333333333") - # print(proj_loaded2.get_json().json["property"]) - # print(proj_loaded2.get_json().json["property"]) - quit() + print("asserting") + print("") + print(proj_loaded2.collection[0].inventory[0].material[0].name) + assert proj_loaded2.collection[0].inventory[0].material[0].name == "this is sure to be a new name" diff --git a/tests/utils/integration_test_helper.py b/tests/utils/integration_test_helper.py index 591d70258..9e5bf5a71 100644 --- a/tests/utils/integration_test_helper.py +++ b/tests/utils/integration_test_helper.py @@ -129,6 +129,11 @@ def delete_integration_node_helper(cript_api: cript.API, node_to_delete: UUIDBas value_to_search=str(node_to_delete.uuid), ) + print("--------------------") + print("my_node_paginator") + print(my_node_paginator) + print(dir(my_node_paginator)) + # be sure API returned at least one result for the node that we searched for assert len(my_node_paginator.current_page_results) == 1 From 77aa7ef05eca0ed02a9c1b1e75782bcf8a21bbc0 Mon Sep 17 00:00:00 2001 From: Ludwig Schneider Date: Mon, 1 Apr 2024 14:57:09 -0500 Subject: [PATCH 09/20] remove old paginator use --- tests/utils/integration_test_helper.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/tests/utils/integration_test_helper.py b/tests/utils/integration_test_helper.py index 9e5bf5a71..998f4a456 100644 --- a/tests/utils/integration_test_helper.py +++ b/tests/utils/integration_test_helper.py @@ -128,14 +128,8 @@ def delete_integration_node_helper(cript_api: cript.API, node_to_delete: UUIDBas search_mode=cript.SearchModes.UUID, value_to_search=str(node_to_delete.uuid), ) - - print("--------------------") - print("my_node_paginator") - print(my_node_paginator) - print(dir(my_node_paginator)) - # be sure API returned at least one result for the node that we searched for - assert len(my_node_paginator.current_page_results) == 1 + next(my_node_paginator) # DELETE the node from the API cript_api.delete(node=node_to_delete) @@ -144,4 +138,5 @@ def delete_integration_node_helper(cript_api: cript.API, node_to_delete: UUIDBas deleted_node_paginator = cript_api.search(node_type=node_to_delete, search_mode=cript.SearchModes.UUID, value_to_search=str(node_to_delete.uuid)) # be sure API responded with empty results - assert len(deleted_node_paginator.current_page_results) == 0 + with pytest.raises(StopIteration): + next(deleted_node_paginator) From 5be22be603deb830c9ec88e3a9857a7bd3c8fa89 Mon Sep 17 00:00:00 2001 From: Ali Date: Tue, 2 Apr 2024 13:00:56 -0400 Subject: [PATCH 10/20] changes to make it node agnostic --- src/cript/api/api.py | 48 ++++++++++++++++------- tests/nodes/primary_nodes/test_project.py | 6 +-- 2 files changed, 36 insertions(+), 18 deletions(-) diff --git a/src/cript/api/api.py b/src/cript/api/api.py index 7568f47c1..feed5a731 100644 --- a/src/cript/api/api.py +++ b/src/cript/api/api.py @@ -11,6 +11,7 @@ import requests from beartype import beartype +import cript.nodes.primary_nodes as PrimaryNodes from cript.api.api_config import _API_TIMEOUT from cript.api.data_schema import DataSchema from cript.api.exceptions import ( @@ -30,11 +31,10 @@ ) from cript.api.utils.web_file_downloader import download_file_from_url from cript.api.valid_search_modes import SearchModes +from cript.nodes.primary_nodes.primary_base_node import PrimaryBaseNode from cript.nodes.primary_nodes.project import Project from cript.nodes.util import load_nodes_from_json -# from cript.nodes.core import Project - # Do not use this directly! That includes devs. # Use the `_get_global_cached_api for access. _global_cached_api = None @@ -424,7 +424,8 @@ def api_prefix(self): def api_version(self): return self._api_version - def save(self, project: Project) -> None: + # def save(self, project: Project) -> None: #changed bc we need it to be node agnostic + def save(self, new_node: PrimaryBaseNode) -> None: # Member function of API ################################################################################ @@ -432,9 +433,17 @@ def save(self, project: Project) -> None: # print("old_node_paginator = self.search(node_type=cript.Project, search_mode=cript.SearchModes.UUID, value_to_search=project)") # print("old_node_paginator.auto_load_nodes = False") - # node_type=cript.Project this needs to be dynamically loaded with locals ? + # node_type=Project this needs to be dynamically loaded with locals ? + + # NOTE: Needed to make it node agnostic + node_class_name = new_node.node_type.capitalize() + NodeClass = getattr(PrimaryNodes, node_class_name) - old_node_paginator = self.search(node_type=Project, search_mode=SearchModes.UUID, value_to_search=str(project.uuid)) + # print(node_class_name) + # print(NodeClass) + # print("----------+++++-----------") + + old_node_paginator = self.search(node_type=NodeClass, search_mode=SearchModes.UUID, value_to_search=str(new_node.uuid)) old_node_paginator.auto_load_nodes = False try: # print("old_node_json = next(old_node_paginator)") @@ -445,15 +454,17 @@ def save(self, project: Project) -> None: old_node_json = next(old_node_paginator) except StopIteration: # New Project do POST instead + # Do the POST request call. only on project + # or else its a patch handled by previous node - # Do the POST request call. - data = project.get_json().json + # if new_node.node_type == "project" + data = new_node.get_json().json response = self._capsule_request(url_path="/project/", method="POST", data=data) print("----700----") print(response.json) return # Return here, since we are done after Posting - # This is where a patch is needed + # This is where a patch is needed (we do it below) # Load the project in a separate cache # old_project, old_uuid_map = cript.load_nodes_from_json(nodes_json=old_node_json, _use_uuid_cache={}) @@ -461,7 +472,8 @@ def save(self, project: Project) -> None: # Check if there is any difference between old and new. # if project.deep_equal(project, old_project): - if project.deep_equal(old_project): + + if new_node.deep_equal(old_project): return # No save necessary, since nothing changed delete_uuid = [] @@ -469,7 +481,7 @@ def save(self, project: Project) -> None: patch_map = LastModifiedDict() # Iterate the new project in DFS - for node in project: + for node in new_node: try: old_node = old_uuid_map[node.uuid] except KeyError: @@ -490,19 +502,25 @@ def save(self, project: Project) -> None: if not node.shallow_equal(old_node): patch_map[node.uuid] = node - url_path = f"/{self.node_type}/{self.uuid}" + # now patch and delete + + # here its project but really it should be a primary base node + + url_path = f"/{new_node.node_type}/{new_node.uuid}" + print("url_path") + print(url_path) + for uuid_ in reversed(patch_map.keys_sorted_by_last_modified()): node = patch_map[uuid_] - # patch capsule request goes here, we are going down the list in reversed order print(f"Doing API PATCH for {node.uuid}") # either link if found or patch json to parent - data = node.get_jason().json + data = node.get_json().json + # first level serach will also include attributes? self._capsule_request(url_path=url_path, method="PATCH", data=json.dumps(data)) for uuid_ in delete_uuid: - # do the delete - # *unlinking here + # do the delete *unlinking here # actually here we are able to send list of uuids to be deleted - optimize later print(f"Doing API Delete for {uuid_}") unlink_payload = {"uuid": str(uuid_)} diff --git a/tests/nodes/primary_nodes/test_project.py b/tests/nodes/primary_nodes/test_project.py index 70ffa4b79..5bed9953d 100644 --- a/tests/nodes/primary_nodes/test_project.py +++ b/tests/nodes/primary_nodes/test_project.py @@ -104,7 +104,7 @@ def test_integration_project(cript_api, simple_project_node): @pytest.mark.skip(reason="api, and we overrode save_new to save ") def test_save_project_change_material(cript_api, simple_project_node, complex_project_node): """ - WIP still + pytest nodes/primary_nodes/test_project.py::test_save_project_change_material """ @@ -118,7 +118,7 @@ def test_save_project_change_material(cript_api, simple_project_node, complex_pr proj0 = copy.deepcopy(complex_project_node) proj_json = proj0.get_expanded_json() # .get_json().json - cript_api.save_new(proj0) + cript_api.save(proj0) print("---- finished save --- now load node") @@ -139,7 +139,7 @@ def test_save_project_change_material(cript_api, simple_project_node, complex_pr # Delete a node proj_loaded.material[0].property = [] - cript_api.save_new(proj_loaded) + cript_api.save(proj_loaded) # now we need to reload the test in From 1e5ed2223c99c5cc7c3545d49b3b4976cff7a099 Mon Sep 17 00:00:00 2001 From: Ali Date: Tue, 2 Apr 2024 13:01:52 -0400 Subject: [PATCH 11/20] serach --- src/cript/api/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cript/api/api.py b/src/cript/api/api.py index feed5a731..63fcd0f47 100644 --- a/src/cript/api/api.py +++ b/src/cript/api/api.py @@ -516,7 +516,7 @@ def save(self, new_node: PrimaryBaseNode) -> None: print(f"Doing API PATCH for {node.uuid}") # either link if found or patch json to parent data = node.get_json().json - # first level serach will also include attributes? + # first level search will also include attributes? self._capsule_request(url_path=url_path, method="PATCH", data=json.dumps(data)) for uuid_ in delete_uuid: From f145b9707389246759caf4684e9e7a3d4ca48f1e Mon Sep 17 00:00:00 2001 From: Ali Date: Tue, 2 Apr 2024 17:30:47 -0400 Subject: [PATCH 12/20] bit of a strange bug with inventory --- tests/nodes/primary_nodes/test_project.py | 37 +++-------------------- 1 file changed, 5 insertions(+), 32 deletions(-) diff --git a/tests/nodes/primary_nodes/test_project.py b/tests/nodes/primary_nodes/test_project.py index 5bed9953d..4d7a80794 100644 --- a/tests/nodes/primary_nodes/test_project.py +++ b/tests/nodes/primary_nodes/test_project.py @@ -108,46 +108,19 @@ def test_save_project_change_material(cript_api, simple_project_node, complex_pr pytest nodes/primary_nodes/test_project.py::test_save_project_change_material """ - # with cript.API(host="https://lb-stage.mycriptapp.org/") as api: - # with open("new_project.json") as json_handle: - # proj_json = json.load(json_handle) - # proj = cript_api.load_nodes_from_json(nodes_json=proj_json) # Modify deep in the tree - - print("------------\nstarting") - proj0 = copy.deepcopy(complex_project_node) proj_json = proj0.get_expanded_json() # .get_json().json cript_api.save(proj0) - - print("---- finished save --- now load node") - - proj = load_nodes_from_json(nodes_json=proj_json) - - print("\n----proj.collection[0].inventory[0].material[0]\n") - - print(proj.collection[0].inventory[0].material[0].get_json().json) - - proj_loaded, proj_cache = cript.load_nodes_from_json(nodes_json=proj0.get_expanded_json(), _use_uuid_cache={}) - + # --- finished save --- now load node + # making sure this is a different object loaded , instead of comparing the same object + proj_loaded, proj_cache = cript.load_nodes_from_json(nodes_json=proj_json, _use_uuid_cache={}) material_to_modify = proj_loaded.collection[0].inventory[0].material[0] - print("\n\n proj_loaded.collection[0].inventory[0].material[0]\n") - print(proj_loaded.collection[0].inventory[0].material[0].get_json().json) - material_to_modify.name = "this is sure to be a new name" - # Delete a node proj_loaded.material[0].property = [] - cript_api.save(proj_loaded) - # now we need to reload the test in - - proj_loaded2, proj_cache = cript.load_nodes_from_json(nodes_json=proj_loaded.get_expanded_json(), _use_uuid_cache={}) - print("\n\n proj_loaded2.collection[0].inventory[0].material[0]\n") - print(proj_loaded2.collection[0].inventory[0].material[0].get_json().json) - - print("asserting") - print("") - print(proj_loaded2.collection[0].inventory[0].material[0].name) + proj_loaded2, proj2_cache = cript.load_nodes_from_json(nodes_json=proj_loaded.get_expanded_json(), _use_uuid_cache={}) + # asserting assert proj_loaded2.collection[0].inventory[0].material[0].name == "this is sure to be a new name" From cfc7e64cbb42cf67cdc4fbb75c364800527a59d8 Mon Sep 17 00:00:00 2001 From: Ali Date: Tue, 2 Apr 2024 17:41:31 -0400 Subject: [PATCH 13/20] bit of a strange bug with inventory --- src/cript/api/api.py | 13 ++++++++----- tests/nodes/primary_nodes/test_project.py | 1 - 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/cript/api/api.py b/src/cript/api/api.py index 63fcd0f47..982e88b13 100644 --- a/src/cript/api/api.py +++ b/src/cript/api/api.py @@ -457,11 +457,14 @@ def save(self, new_node: PrimaryBaseNode) -> None: # Do the POST request call. only on project # or else its a patch handled by previous node - # if new_node.node_type == "project" - data = new_node.get_json().json - response = self._capsule_request(url_path="/project/", method="POST", data=data) - print("----700----") - print(response.json) + print("--------900---------") + print(new_node.node_type.lower()) + if new_node.node_type.lower() == "project": + data = new_node.get_json().json + response = self._capsule_request(url_path="/project/", method="POST", data=data) + print("----700----") + print(response.json()) + return # Return here, since we are done after Posting # This is where a patch is needed (we do it below) diff --git a/tests/nodes/primary_nodes/test_project.py b/tests/nodes/primary_nodes/test_project.py index 4d7a80794..76a2d5ba8 100644 --- a/tests/nodes/primary_nodes/test_project.py +++ b/tests/nodes/primary_nodes/test_project.py @@ -5,7 +5,6 @@ import pytest import cript -from cript.nodes.util import load_nodes_from_json from tests.utils.integration_test_helper import ( delete_integration_node_helper, save_integration_node_helper, From a32425cb1c05d2bebff1412703fd882543c7fbe0 Mon Sep 17 00:00:00 2001 From: Ali Date: Wed, 3 Apr 2024 16:22:16 -0400 Subject: [PATCH 14/20] still 2 tests failing , test_data.py and test_computational_process.py --- src/cript/api/api.py | 46 +++---------------- .../test_computational_process.py | 5 +- tests/nodes/primary_nodes/test_data.py | 6 ++- tests/nodes/primary_nodes/test_inventory.py | 1 + 4 files changed, 17 insertions(+), 41 deletions(-) diff --git a/src/cript/api/api.py b/src/cript/api/api.py index 982e88b13..b0ee51aca 100644 --- a/src/cript/api/api.py +++ b/src/cript/api/api.py @@ -424,58 +424,28 @@ def api_prefix(self): def api_version(self): return self._api_version - # def save(self, project: Project) -> None: #changed bc we need it to be node agnostic def save(self, new_node: PrimaryBaseNode) -> None: - # Member function of API - ################################################################################ - - # In this mockup, we can't actually do the search: - # print("old_node_paginator = self.search(node_type=cript.Project, search_mode=cript.SearchModes.UUID, value_to_search=project)") - # print("old_node_paginator.auto_load_nodes = False") - - # node_type=Project this needs to be dynamically loaded with locals ? - # NOTE: Needed to make it node agnostic node_class_name = new_node.node_type.capitalize() NodeClass = getattr(PrimaryNodes, node_class_name) - # print(node_class_name) - # print(NodeClass) - # print("----------+++++-----------") - old_node_paginator = self.search(node_type=NodeClass, search_mode=SearchModes.UUID, value_to_search=str(new_node.uuid)) old_node_paginator.auto_load_nodes = False try: - # print("old_node_json = next(old_node_paginator)") - - # # Just for this mock-up we load from the file instead - # with open("new_project.json") as json_handle: - # old_node_json = json_handle.read() old_node_json = next(old_node_paginator) except StopIteration: # New Project do POST instead # Do the POST request call. only on project # or else its a patch handled by previous node - print("--------900---------") - print(new_node.node_type.lower()) if new_node.node_type.lower() == "project": data = new_node.get_json().json - response = self._capsule_request(url_path="/project/", method="POST", data=data) - print("----700----") - print(response.json()) + self._capsule_request(url_path="/project/", method="POST", data=data) return # Return here, since we are done after Posting - # This is where a patch is needed (we do it below) - - # Load the project in a separate cache - # old_project, old_uuid_map = cript.load_nodes_from_json(nodes_json=old_node_json, _use_uuid_cache={}) old_project, old_uuid_map = load_nodes_from_json(nodes_json=old_node_json, _use_uuid_cache={}) - # Check if there is any difference between old and new. - # if project.deep_equal(project, old_project): - if new_node.deep_equal(old_project): return # No save necessary, since nothing changed @@ -489,10 +459,10 @@ def save(self, new_node: PrimaryBaseNode) -> None: old_node = old_uuid_map[node.uuid] except KeyError: # This node only exists in the new new project, - # But it has a parent, that will patch it in, so we don't need to do anything + # But it has a parent which patches it in, so no action needed pass - # Let's see if we need to delete any children, that existed in the old node, but don't exit in the new node. + # do we need to delete any children, that existed in the old node, but don't exit in the new node. node_child_map = {child.uuid: child for child in node.find_children({}, search_depth=1)} old_child_map = {child.uuid: child for child in old_node.find_children({}, search_depth=1)} for old_uuid in old_child_map: @@ -500,8 +470,8 @@ def save(self, new_node: PrimaryBaseNode) -> None: if old_uuid not in delete_uuid: delete_uuid += [old_uuid] - # Next we check if the current new node needs a patch - # like # project.deep_equal(old_project) or shallow_equal(node, old_node): + # check if the current new node needs a patch + if not node.shallow_equal(old_node): patch_map[node.uuid] = node @@ -510,13 +480,11 @@ def save(self, new_node: PrimaryBaseNode) -> None: # here its project but really it should be a primary base node url_path = f"/{new_node.node_type}/{new_node.uuid}" - print("url_path") - print(url_path) for uuid_ in reversed(patch_map.keys_sorted_by_last_modified()): node = patch_map[uuid_] - print(f"Doing API PATCH for {node.uuid}") + # "Doing API PATCH for {node.uuid}" # either link if found or patch json to parent data = node.get_json().json # first level search will also include attributes? @@ -525,7 +493,7 @@ def save(self, new_node: PrimaryBaseNode) -> None: for uuid_ in delete_uuid: # do the delete *unlinking here # actually here we are able to send list of uuids to be deleted - optimize later - print(f"Doing API Delete for {uuid_}") + # "Doing API Delete for {uuid_}" unlink_payload = {"uuid": str(uuid_)} self._capsule_request(url_path=url_path, method="DELETE", data=json.dumps(unlink_payload)) diff --git a/tests/nodes/primary_nodes/test_computational_process.py b/tests/nodes/primary_nodes/test_computational_process.py index 4b84c0a2d..3ae0e85ad 100644 --- a/tests/nodes/primary_nodes/test_computational_process.py +++ b/tests/nodes/primary_nodes/test_computational_process.py @@ -150,7 +150,7 @@ def test_serialize_computational_process_to_json(simple_computational_process_no assert ref_dict == expected_dict -def test_integration_computational_process(cript_api, simple_project_node, simple_collection_node, simple_experiment_node, simplest_computational_process_node, simple_material_node, simple_data_node) -> None: +def test_integration_computational_process(cript_api, simple_project_node, simple_inventory_node, simple_collection_node, simple_experiment_node, simplest_computational_process_node, simple_material_node, simple_data_node) -> None: """ integration test between Python SDK and API Client @@ -166,6 +166,9 @@ def test_integration_computational_process(cript_api, simple_project_node, simpl simple_project_node.material = [simple_material_node] + simple_inventory_node.material = [] + simple_project_node.collection[0].inventory = [simple_inventory_node] + simple_project_node.collection = [simple_collection_node] simple_project_node.collection[0].experiment = [simple_experiment_node] diff --git a/tests/nodes/primary_nodes/test_data.py b/tests/nodes/primary_nodes/test_data.py index 6f3f6bcde..501328bab 100644 --- a/tests/nodes/primary_nodes/test_data.py +++ b/tests/nodes/primary_nodes/test_data.py @@ -167,7 +167,7 @@ def test_serialize_data_to_json(simple_data_node) -> None: assert ref_dict == expected_data_dict -def test_integration_data(cript_api, simple_project_node, simple_data_node): +def test_integration_data(cript_api, simple_project_node, simple_inventory_node, simple_data_node): """ integration test between Python SDK and API Client @@ -182,6 +182,10 @@ def test_integration_data(cript_api, simple_project_node, simple_data_node): # ========= test create ========= simple_project_node.name = f"test_integration_project_name_{uuid.uuid4().hex}" + # simple_data_node.material something + simple_inventory_node.material = [] + simple_project_node.collection[0].inventory = [simple_inventory_node] + simple_project_node.collection[0].experiment[0].data = [simple_data_node] save_integration_node_helper(cript_api=cript_api, project_node=simple_project_node) diff --git a/tests/nodes/primary_nodes/test_inventory.py b/tests/nodes/primary_nodes/test_inventory.py index ecb5add37..f0f48435b 100644 --- a/tests/nodes/primary_nodes/test_inventory.py +++ b/tests/nodes/primary_nodes/test_inventory.py @@ -75,6 +75,7 @@ def test_integration_inventory(cript_api, simple_project_node, simple_inventory_ simple_project_node.collection[0].name = f"collection_name_{uuid.uuid4().hex}" simple_inventory_node.name = f"inventory_name_{uuid.uuid4().hex}" + simple_inventory_node.material = [] simple_project_node.collection[0].inventory = [simple_inventory_node] save_integration_node_helper(cript_api=cript_api, project_node=simple_project_node) From da796419819427f54afc69ea181db8b8043bd6b8 Mon Sep 17 00:00:00 2001 From: Ali Date: Thu, 4 Apr 2024 10:20:26 -0400 Subject: [PATCH 15/20] still weird where the material refuses to be set to an empty list --- tests/fixtures/primary_nodes.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/fixtures/primary_nodes.py b/tests/fixtures/primary_nodes.py index 063a3a334..c8ac6f545 100644 --- a/tests/fixtures/primary_nodes.py +++ b/tests/fixtures/primary_nodes.py @@ -664,7 +664,9 @@ def simple_inventory_node(simple_material_node) -> None: material_2 = cript.Material(name="material 2 " + str(uuid.uuid4()), bigsmiles="{[][$]COC[$][]}") - my_inventory = cript.Inventory(name="my inventory name", material=[simple_material_node, material_2]) + my_inventory = cript.Inventory(name="my inventory name", material=[]) # material=[simple_material_node, material_2]) + + # my_inventory.material = [] # use my_inventory in another test return my_inventory From fcb28798da8e51e6989e37e753676dc80a2de75b Mon Sep 17 00:00:00 2001 From: Ali Date: Thu, 4 Apr 2024 10:33:25 -0400 Subject: [PATCH 16/20] still weird error with inventory fixture cannot set material to be empty --- src/cript/api/api.py | 9 ++++++--- tests/fixtures/primary_nodes.py | 4 ++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/cript/api/api.py b/src/cript/api/api.py index b0ee51aca..93845dc8d 100644 --- a/src/cript/api/api.py +++ b/src/cript/api/api.py @@ -440,9 +440,12 @@ def save(self, new_node: PrimaryBaseNode) -> None: if new_node.node_type.lower() == "project": data = new_node.get_json().json - self._capsule_request(url_path="/project/", method="POST", data=data) - - return # Return here, since we are done after Posting + response = self._capsule_request(url_path="/project/", method="POST", data=data) + if response.status_code in [200, 201]: + return # Return here, since we successfully Posting + else: # debug + print("GET HERE ALI") + raise APIError old_project, old_uuid_map = load_nodes_from_json(nodes_json=old_node_json, _use_uuid_cache={}) diff --git a/tests/fixtures/primary_nodes.py b/tests/fixtures/primary_nodes.py index c8ac6f545..1e6b880c6 100644 --- a/tests/fixtures/primary_nodes.py +++ b/tests/fixtures/primary_nodes.py @@ -656,13 +656,13 @@ def complex_material_node(simple_property_node, simple_process_node, complex_com @pytest.fixture(scope="function") -def simple_inventory_node(simple_material_node) -> None: +def simple_inventory_node() -> None: """ minimal inventory node to use for other tests """ # set up inventory node - material_2 = cript.Material(name="material 2 " + str(uuid.uuid4()), bigsmiles="{[][$]COC[$][]}") + # material_2 = cript.Material(name="material 2 " + str(uuid.uuid4()), bigsmiles="{[][$]COC[$][]}") my_inventory = cript.Inventory(name="my inventory name", material=[]) # material=[simple_material_node, material_2]) From bc83a8ea45d65625151444a53a7134908e2fffa8 Mon Sep 17 00:00:00 2001 From: Ludwig Schneider Date: Thu, 4 Apr 2024 10:35:12 -0500 Subject: [PATCH 17/20] add no_condense_uuid option to get_json --- src/cript/nodes/core.py | 5 +++++ src/cript/nodes/util/json.py | 6 +++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/cript/nodes/core.py b/src/cript/nodes/core.py index 68edb19e4..343fcca6d 100644 --- a/src/cript/nodes/core.py +++ b/src/cript/nodes/core.py @@ -447,6 +447,7 @@ def get_json( "Project": {"member", "admin"}, "Collection": {"member", "admin"}, }, + _no_condense_uuid: bool = False, **kwargs, ): """ @@ -486,6 +487,9 @@ class ReturnTuple: previous_condense_to_uuid = copy.deepcopy(NodeEncoder.condense_to_uuid) NodeEncoder.condense_to_uuid = condense_to_uuid + previous_no_condense_uuid = copy.deepcopy(NodeEncoder.no_condense_uuid) + NodeEncoder.no_condense_uuid = _no_condense_uuid + try: tmp_json = json.dumps(self, cls=NodeEncoder, **kwargs) tmp_dict = json.loads(tmp_json) @@ -503,6 +507,7 @@ class ReturnTuple: NodeEncoder.known_uuid = previous_known_uuid NodeEncoder.suppress_attributes = previous_suppress_attributes NodeEncoder.condense_to_uuid = previous_condense_to_uuid + NodeEncoder.no_condense_uuid = previous_no_condense_uuid def find_children(self, search_attr: dict, search_depth: int = -1, handled_nodes: Optional[List] = None) -> List: """ diff --git a/src/cript/nodes/util/json.py b/src/cript/nodes/util/json.py index 2b923bab5..6d287cb2f 100644 --- a/src/cript/nodes/util/json.py +++ b/src/cript/nodes/util/json.py @@ -66,6 +66,7 @@ class NodeEncoder(json.JSONEncoder): known_uuid: Set[str] = set() condense_to_uuid: Dict[str, Set[str]] = dict() suppress_attributes: Optional[Dict[str, Set[str]]] = None + no_condense_uuid: bool = False def default(self, obj): """ @@ -182,7 +183,10 @@ def strip_to_edge_uuid(element): except AttributeError: uid = element["uid"] - element = {"uuid": str(uuid)} + if self.no_condense_uuid: + element = "" + else: + element = {"uuid": str(uuid)} return element, uid # Processes an attribute based on its type (list or single element) From 71210d31668d0ffce8e4cc2fa164fe6c93d16859 Mon Sep 17 00:00:00 2001 From: Ali Date: Thu, 4 Apr 2024 15:11:35 -0400 Subject: [PATCH 18/20] 99luftballoons --- src/cript/api/api.py | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/src/cript/api/api.py b/src/cript/api/api.py index 93845dc8d..157118528 100644 --- a/src/cript/api/api.py +++ b/src/cript/api/api.py @@ -424,8 +424,23 @@ def api_prefix(self): def api_version(self): return self._api_version - def save(self, new_node: PrimaryBaseNode) -> None: - # NOTE: Needed to make it node agnostic + # _no_condense_uuid is either active or not + def save(self, new_node): + self._internal_save(new_node, _no_condense_uuid=True) + print("GET_ALI_HERE") + quit() + self._internal_save(new_node, _no_condense_uuid=False) + + def _internal_save(self, new_node: PrimaryBaseNode, _no_condense_uuid: bool) -> None: + print("_no_condense_uuid") + print(_no_condense_uuid) + + data = new_node.get_json(_no_condense_uuid=_no_condense_uuid).json + + print("data") + print(data) + print("----------\\------------\n") + node_class_name = new_node.node_type.capitalize() NodeClass = getattr(PrimaryNodes, node_class_name) @@ -439,13 +454,20 @@ def save(self, new_node: PrimaryBaseNode) -> None: # or else its a patch handled by previous node if new_node.node_type.lower() == "project": - data = new_node.get_json().json + data = new_node.get_json(_no_condense_uuid=_no_condense_uuid).json + + print("---- data -----") + print(data) + print("---- data end -----") + # if _no_condense_uuid is true do a POST if its false do a patch, + # but wouldnt we then just find the existing node above in the generator? response = self._capsule_request(url_path="/project/", method="POST", data=data) if response.status_code in [200, 201]: return # Return here, since we successfully Posting - else: # debug + else: # debug for now print("GET HERE ALI") - raise APIError + res = response.json() + raise Exception(f"APIError {res}") old_project, old_uuid_map = load_nodes_from_json(nodes_json=old_node_json, _use_uuid_cache={}) @@ -500,7 +522,7 @@ def save(self, new_node: PrimaryBaseNode) -> None: unlink_payload = {"uuid": str(uuid_)} self._capsule_request(url_path=url_path, method="DELETE", data=json.dumps(unlink_payload)) - ################################################################################ + ################################################################################ def save_old(self, project: Project) -> None: """ @@ -533,7 +555,7 @@ def save_old(self, project: Project) -> None: pass raise exc from exc - def _internal_save(self, node, save_values: Optional[_InternalSaveValues] = None) -> _InternalSaveValues: + def _internal_save_old(self, node, save_values: Optional[_InternalSaveValues] = None) -> _InternalSaveValues: """ Internal helper function that handles the saving of different nodes (not just project). From b191cc8193d5e96ea59726a8d83f58b27dc5043d Mon Sep 17 00:00:00 2001 From: Ali Date: Fri, 5 Apr 2024 12:11:13 -0400 Subject: [PATCH 19/20] WIP notes for using uid ? --- src/cript/api/api.py | 68 ++++++++++++++++++++++++++++++++---- src/cript/nodes/core.py | 4 +++ src/cript/nodes/util/json.py | 16 ++++++--- 3 files changed, 77 insertions(+), 11 deletions(-) diff --git a/src/cript/api/api.py b/src/cript/api/api.py index 157118528..8bfd8bae8 100644 --- a/src/cript/api/api.py +++ b/src/cript/api/api.py @@ -431,14 +431,26 @@ def save(self, new_node): quit() self._internal_save(new_node, _no_condense_uuid=False) - def _internal_save(self, new_node: PrimaryBaseNode, _no_condense_uuid: bool) -> None: - print("_no_condense_uuid") - print(_no_condense_uuid) + def _internal_save(self, new_node: PrimaryBaseNode, preknown_uid: str, _no_condense_uuid: bool) -> None: + """ + NOTE: for Ludwig + + WIP NOTES: + + unfinished WIP, but the idea is we want to send + a preknown_uuid + + I'm imagining something like + data = new_node.get_json(preknown_uid="preknown uid into here").json + in the same way we did the _no_condense_uuid + + but, then , I see where I would add a preknown uuid + scroll down to - data = new_node.get_json(_no_condense_uuid=_no_condense_uuid).json + WIP NOTES CONTINUED: + + """ - print("data") - print(data) print("----------\\------------\n") node_class_name = new_node.node_type.capitalize() @@ -454,15 +466,31 @@ def _internal_save(self, new_node: PrimaryBaseNode, _no_condense_uuid: bool) -> # or else its a patch handled by previous node if new_node.node_type.lower() == "project": + # data = new_node.get_json(_no_condense_uuid=_no_condense_uuid).json + # data = new_node.get_json(sort_keys=False, condense_to_uuid={}, indent=2).json # indent=2 + + # data = new_node.get_json(_no_condense_uuid=_no_condense_uuid).json + data = new_node.get_json(_no_condense_uuid=_no_condense_uuid).json - print("---- data -----") + # data = new_node.get_json(condense_to_uuid={}).json + print("---- data 2 -----") + # print(type(data2)) + # print(type(json.loads(data2))) + # data = json.loads(data) + # print(type(data)) + # print(str(data)) print(data) + # data = str(data) + # data = json.dumps(data) + # data = data.replace('""', "[]") + # print("now data\n", data) print("---- data end -----") # if _no_condense_uuid is true do a POST if its false do a patch, # but wouldnt we then just find the existing node above in the generator? response = self._capsule_request(url_path="/project/", method="POST", data=data) if response.status_code in [200, 201]: + print("FINALLY_WORKED!") return # Return here, since we successfully Posting else: # debug for now print("GET HERE ALI") @@ -506,6 +534,32 @@ def _internal_save(self, new_node: PrimaryBaseNode, _no_condense_uuid: bool) -> url_path = f"/{new_node.node_type}/{new_node.uuid}" + """ + WIP NOTES CONTINUED: + + this is where we would need to make a map of the uids + patch_map[uid_] ? constructed above? + + problem right now is , + we need the uids to be in place for the original POST json + which happens up above + so I'm wondeing how to go about that, + + and I think I would need to rewalk the original get_json for the post + switching any {uuid: "uuid"} for {uid:"uid"} + + AND I TRY TO DO THAT if you look inside + + json.py , you'd find the following below + + ######## WIP HERE ################ + if self.preknown_uid: + element = {"uid": str(uid)} + return element, uid + + but we need this uid map and i'm not so sure where to put this ? + + """ for uuid_ in reversed(patch_map.keys_sorted_by_last_modified()): node = patch_map[uuid_] diff --git a/src/cript/nodes/core.py b/src/cript/nodes/core.py index 343fcca6d..d2d58ce4a 100644 --- a/src/cript/nodes/core.py +++ b/src/cript/nodes/core.py @@ -448,6 +448,7 @@ def get_json( "Collection": {"member", "admin"}, }, _no_condense_uuid: bool = False, + preknown_uid: str = "", **kwargs, ): """ @@ -489,6 +490,7 @@ class ReturnTuple: previous_no_condense_uuid = copy.deepcopy(NodeEncoder.no_condense_uuid) NodeEncoder.no_condense_uuid = _no_condense_uuid + NodeEncoder.preknown_uid = preknown_uid try: tmp_json = json.dumps(self, cls=NodeEncoder, **kwargs) @@ -508,6 +510,8 @@ class ReturnTuple: NodeEncoder.suppress_attributes = previous_suppress_attributes NodeEncoder.condense_to_uuid = previous_condense_to_uuid NodeEncoder.no_condense_uuid = previous_no_condense_uuid + # HERE?? + # NodeEncoder.preknown_uid = preknown_uid def find_children(self, search_attr: dict, search_depth: int = -1, handled_nodes: Optional[List] = None) -> List: """ diff --git a/src/cript/nodes/util/json.py b/src/cript/nodes/util/json.py index 6d287cb2f..d0e88e965 100644 --- a/src/cript/nodes/util/json.py +++ b/src/cript/nodes/util/json.py @@ -1,6 +1,7 @@ """ This module contains classes and functions that help with the json serialization and deserialization of nodes. """ + import dataclasses import inspect import json @@ -67,6 +68,7 @@ class NodeEncoder(json.JSONEncoder): condense_to_uuid: Dict[str, Set[str]] = dict() suppress_attributes: Optional[Dict[str, Set[str]]] = None no_condense_uuid: bool = False + preknown_uid: str = "" def default(self, obj): """ @@ -183,12 +185,18 @@ def strip_to_edge_uuid(element): except AttributeError: uid = element["uid"] - if self.no_condense_uuid: - element = "" - else: - element = {"uuid": str(uuid)} + ######## WIP HERE ################ + if self.preknown_uid: + element = {"uid": str(uid)} return element, uid + ######################### + # if self.no_condense_uuid: + # element = "" + # else: + # element = {"uuid": str(uuid)} + # return element, uid + # Processes an attribute based on its type (list or single element) if isinstance(attribute, List): processed_elements = [] From 8e7375d78c39be17ba5dda872d86ef59e218444f Mon Sep 17 00:00:00 2001 From: Ludwig Schneider Date: Fri, 5 Apr 2024 12:19:04 -0500 Subject: [PATCH 20/20] change again --- src/cript/api/api.py | 16 ++++++---------- src/cript/nodes/core.py | 11 +++++++---- src/cript/nodes/util/json.py | 7 ++++--- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/cript/api/api.py b/src/cript/api/api.py index 157118528..a04ff9dfa 100644 --- a/src/cript/api/api.py +++ b/src/cript/api/api.py @@ -426,16 +426,12 @@ def api_version(self): # _no_condense_uuid is either active or not def save(self, new_node): - self._internal_save(new_node, _no_condense_uuid=True) + self._internal_save(new_node) print("GET_ALI_HERE") - quit() - self._internal_save(new_node, _no_condense_uuid=False) - def _internal_save(self, new_node: PrimaryBaseNode, _no_condense_uuid: bool) -> None: - print("_no_condense_uuid") - print(_no_condense_uuid) - - data = new_node.get_json(_no_condense_uuid=_no_condense_uuid).json + def _internal_save(self, new_node: PrimaryBaseNode) -> None: + new_node.validate(force_validation=True) + data = new_node.get_json().json print("data") print(data) @@ -454,13 +450,13 @@ def _internal_save(self, new_node: PrimaryBaseNode, _no_condense_uuid: bool) -> # or else its a patch handled by previous node if new_node.node_type.lower() == "project": - data = new_node.get_json(_no_condense_uuid=_no_condense_uuid).json + data = new_node.get_json().json print("---- data -----") print(data) print("---- data end -----") # if _no_condense_uuid is true do a POST if its false do a patch, - # but wouldnt we then just find the existing node above in the generator? + # but wouldn't we then just find the existing node above in the generator? response = self._capsule_request(url_path="/project/", method="POST", data=data) if response.status_code in [200, 201]: return # Return here, since we successfully Posting diff --git a/src/cript/nodes/core.py b/src/cript/nodes/core.py index 343fcca6d..78746cfbf 100644 --- a/src/cript/nodes/core.py +++ b/src/cript/nodes/core.py @@ -447,7 +447,6 @@ def get_json( "Project": {"member", "admin"}, "Collection": {"member", "admin"}, }, - _no_condense_uuid: bool = False, **kwargs, ): """ @@ -487,8 +486,12 @@ class ReturnTuple: previous_condense_to_uuid = copy.deepcopy(NodeEncoder.condense_to_uuid) NodeEncoder.condense_to_uuid = condense_to_uuid - previous_no_condense_uuid = copy.deepcopy(NodeEncoder.no_condense_uuid) - NodeEncoder.no_condense_uuid = _no_condense_uuid + known_uid = set() + for child_node in self: + known_uid.add(child_node.uid) + + previous_known_uid = copy.deepcopy(NodeEncoder.known_uid) + NodeEncoder.known_uid = known_uid try: tmp_json = json.dumps(self, cls=NodeEncoder, **kwargs) @@ -507,7 +510,7 @@ class ReturnTuple: NodeEncoder.known_uuid = previous_known_uuid NodeEncoder.suppress_attributes = previous_suppress_attributes NodeEncoder.condense_to_uuid = previous_condense_to_uuid - NodeEncoder.no_condense_uuid = previous_no_condense_uuid + NodeEncoder.known_uid = previous_known_uid def find_children(self, search_attr: dict, search_depth: int = -1, handled_nodes: Optional[List] = None) -> List: """ diff --git a/src/cript/nodes/util/json.py b/src/cript/nodes/util/json.py index 6d287cb2f..5c4fcff80 100644 --- a/src/cript/nodes/util/json.py +++ b/src/cript/nodes/util/json.py @@ -66,7 +66,7 @@ class NodeEncoder(json.JSONEncoder): known_uuid: Set[str] = set() condense_to_uuid: Dict[str, Set[str]] = dict() suppress_attributes: Optional[Dict[str, Set[str]]] = None - no_condense_uuid: bool = False + known_uid: Optional[Set[str]] = None def default(self, obj): """ @@ -183,8 +183,9 @@ def strip_to_edge_uuid(element): except AttributeError: uid = element["uid"] - if self.no_condense_uuid: - element = "" + # If this is not the only occurrence of the node, replace it with UID instead of UUID + if self.known_uid and uid in self.known_uid: + element = {"uid": uid} else: element = {"uuid": str(uuid)} return element, uid