diff --git a/src/cript/api/api.py b/src/cript/api/api.py index 68cec881..fc37bb30 100644 --- a/src/cript/api/api.py +++ b/src/cript/api/api.py @@ -427,38 +427,100 @@ def add_to_dict(cls, dictionary, key=None, value=None): # Key does not exist, create a new list with the value dictionary[key] = [value] - # ================================== SAVE EXISTING =================================== - def add_existing_node_by_name( + # ================================== ADD EXISTING NODES-NAMES=================================== + def add_existing_nodes_by_name( self, parent_node: PrimaryBaseNode, - child_node: PrimaryBaseNode, + child_class_type: str, + existing_child_node_names: list, # must be list of strings ): - child_class_name = child_node.node[0] - child_class_object = globals().get(child_class_name.capitalize(), None) + """ + this function will take a list of exact names and add them by uuid + takes in Parent Node, child cass type as a str i.e. "material" or "Material" (either work) + + """ + + # child_class_type = child_node.node[0] + child_class_object = globals().get(child_class_type.capitalize(), None) # Check if the class exists if child_class_object is None: - raise ValueError(f"Class {child_class_name} not found") - - existing_node = next(self.search(child_class_object, search_mode=SearchModes.EXACT_NAME, value_to_search=child_node.name)) - existing_uuid = str(existing_node.uuid) + raise ValueError(f"Class {child_class_type} not found") + # go through thte list of names and create the payload : parent_node_type = parent_node.node[0].lower() + url_path = f"/{parent_node_type}/{parent_node.uuid}" + + entity_name = f"{child_class_type.lower()}" + uuid_link_payload = {"node": parent_node.node, entity_name: []} + + for name in existing_child_node_names: + # print(name.strip()) + name = name.strip() + + existing_node = next(self.search(child_class_object, search_mode=SearchModes.EXACT_NAME, value_to_search=name.strip())) + existing_uuid = str(existing_node.uuid) + + parent_node_type = parent_node.node[0].lower() + + API.add_to_dict(uuid_link_payload, key=entity_name, value={"uuid": f"{existing_uuid}"}) + + patch_response = self._capsule_request(url_path=url_path, method="PATCH", data=json.dumps(uuid_link_payload)) + + if patch_response.status_code in [200, 201]: + # print("worked") + # print(patch_response.json()) + return patch_response + else: + raise ("error in patching existing item") + + def remove_nodes_by_name( + self, + parent_node: PrimaryBaseNode, + child_class_type: str, + existing_child_node_names: list, + ): + """ + this function will take a list of exact names and add them by uuid + takes in Parent Node, child cass type as a str i.e. "material" or "Material" (either work) + + """ + # child_class_type = child_node.node[0] + child_class_object = globals().get(child_class_type.capitalize(), None) + + # Check if the class exists + if child_class_object is None: + raise ValueError(f"Class {child_class_type} not found") + + # go through thte list of names and create the payload : + parent_node_type = parent_node.node[0].lower() url_path = f"/{parent_node_type}/{parent_node.uuid}" - payload = {"node": parent_node.node, f"{child_node.node[0].lower()}": [{"uuid": existing_uuid}]} + entity_name = f"{child_class_type.lower()}" + uuid_link_payload = {"node": parent_node.node, entity_name: []} + + for name in existing_child_node_names: + # print(name.strip()) + name = name.strip().lower() - patch_response = self._capsule_request(url_path=url_path, method="PATCH", data=json.dumps(payload)) + existing_node = next(self.search(child_class_object, search_mode=SearchModes.EXACT_NAME, value_to_search=name.strip())) + existing_uuid = str(existing_node.uuid) - if patch_response.status_code in [200, 201]: - print("worked") + parent_node_type = parent_node.node[0].lower() + + API.add_to_dict(uuid_link_payload, key=entity_name, value={"uuid": f"{existing_uuid}"}) + + del_response = self._capsule_request(url_path=url_path, method="DELETE", data=json.dumps(uuid_link_payload)) + + if del_response.status_code in [200, 201]: + # print("delete worked") + # print(del_response.json()) + return del_response else: raise ("error in patching existing item") - # ================================== SAVE =================================== - - def save_node(self, new_node: PrimaryBaseNode): + def save_node(self, new_node: PrimaryBaseNode, link_existing=True): # try to create or else fail try: @@ -525,6 +587,10 @@ def save_node(self, new_node: PrimaryBaseNode): ) diff_dict = diff_.to_dict() + # print("-----diff_dict") + # print(diff_dict) + # quit() + try: values_changed = diff_dict.get("values_changed", {}) @@ -559,6 +625,7 @@ def save_node(self, new_node: PrimaryBaseNode): API.add_to_dict(entities_to_remove_dict, key=entity_name, value={"uuid": f"{uuid_to_remove}"}) else: print("uuid is None (values changed)") + node_to_add = value["new_value"] # gotta do a search by uuid on the name and node API.add_to_dict(entities_to_patch_dict, key=entity_name, value=node_to_add) @@ -592,7 +659,7 @@ def save_node(self, new_node: PrimaryBaseNode): print(" 3) DICT ITEMS ADDED ") for path in dictionary_items_added: - print(path) + # print(path) # Strip "root" and square brackets, then remove quotes key_name = path.replace("root[", "").replace("]", "").replace("'", "") entities_to_patch_dict[key_name] = cleaned_modified[key_name] @@ -626,15 +693,42 @@ def save_node(self, new_node: PrimaryBaseNode): payload_remove = entities_to_remove_dict payload_patch = entities_to_patch_dict + # print("\n\n____payload_patch") + # print(payload_patch) + # quit() url_path = f"/project/{new_node.uuid}" patch_response = self._capsule_request(url_path=url_path, method="PATCH", data=json.dumps(payload_patch)) if patch_response.status_code in [400, 409]: - print("take the materials that exist and link it , then resend the other materials") - print(patch_response.json()) - # raise ("error") + + """take the materials that exist and link it , then resend the other materials""" + # print(patch_response.json()) + + if link_existing == True: + + names_list_of_dicts = patch_response.json().get("error").split("item")[1].split("for")[0] + child_class_type = patch_response.json().get("error").split("item")[1].split("for")[1].strip().lower() + + eval_names = eval(names_list_of_dicts) + # print(eval_names) + names_list = [name["name"].lower() for name in eval_names] + # print("names_list") + + self.add_existing_nodes_by_name( + parent_node=new_node, + child_class_type=child_class_type, + existing_child_node_names=names_list, + ) + + # need to retry for collection + payload_patch.pop(child_class_type) + patch_response2 = self._capsule_request(url_path=url_path, method="PATCH", data=json.dumps(payload_patch)) + + # print("\n\n___patch_response2") + # print(patch_response2.json()) + # quit() remove_response = self._capsule_request(url_path=url_path, method="DELETE", data=json.dumps(payload_remove)) @@ -643,11 +737,9 @@ def save_node(self, new_node: PrimaryBaseNode): raise (remove_response.json()) # raise ("error") - ###################################### - print(" - FINALLY WE GET ALL THE WAY TO THE END ") - # ================================== + # ====================================================================================================== def save(self, project: Project) -> None: """ This method takes a project node, serializes the class into JSON diff --git a/tests/nodes/primary_nodes/test_material.py b/tests/nodes/primary_nodes/test_material.py index 627eb3c0..71fa91fe 100644 --- a/tests/nodes/primary_nodes/test_material.py +++ b/tests/nodes/primary_nodes/test_material.py @@ -7,6 +7,8 @@ save_integration_node_helper, ) from tests.utils.util import strip_uid_from_dict +import pytest +import time def test_create_complex_material(cript_api, simple_material_node, simple_computational_forcefield_node, simple_process_node) -> None: @@ -156,3 +158,120 @@ def test_integration_material(cript_api, simple_project_node, simple_material_no # ========= test delete ========= delete_integration_node_helper(cript_api=cript_api, node_to_delete=simple_material_node) + + +# @pytest.mark.skip(reason="test is WIP") +# def test_update_material_change_or_reset_children_to_existing_children(cript_api) -> None: +# """ +# pytest nodes/primary_nodes/test_project.py::test_update_project_change_or_reset_material_to_existing_materials +# test that a project can be updated and completley reset +# strategy: +# create something with a post/patch +# with a name (we will delete at the end) +# then try to obtain it with load data +# """ + +# epoch_time = int(time.time()) +# name_1 = f"myproj_ali_{epoch_time}" +# mat_1 = f"my_mat__{epoch_time}" +# col_name = f"031o0col__{epoch_time}" + +# url_path = f"/project/" +# create_payload = {"node": ["Project"], "name": name_1, "material": [{"uuid": "1809330c-31d2-4a80-af72-77b84070ee1d"}, {"uuid": "ea8f957c-b6e5-4668-b306-e0d6b0d05d9a"}]} + +# try: +# create_response = cript_api._capsule_request(url_path=url_path, method="POST", data=json.dumps(create_payload)) +# print(create_response) +# except Exception as e: +# print(e) + +# cr_res_list = create_response.json()["data"]["result"] + +# """ +# 1 - get existing material node by uuid - paginator +# - or create material node with a property + +# 2 - make an edit to a child node (property or process or component) +# 3 - save the node +# """ + +# if create_response.json()["code"] in [409, 400, 401]: +# print("---create_response") +# print(create_response) +# raise ValueError(create_response) + +# elif create_response.json()["code"] in [201, 200]: + +# uuid = None +# for item in cr_res_list: +# if item["node"] == ["Project"]: + +# uuid = item["uuid"] +# if uuid == None: +# raise ValueError("no project node") + +# get_url = f"/project/{uuid}" + +# result = cript_api._capsule_request(url_path=get_url, method="GET") + +# result_json_dict = result.json() + +# my_project_from_res_data_dict = result_json_dict["data"][0] + +# project_list = cript.load_nodes_from_json(nodes_json=json.dumps(my_project_from_res_data_dict)) +# project_loaded = project_list + +# """ +# 1 - get existing material node by uuid - paginator +# - or create material node with a property + +# 2 - make an edit to a child node (property or process or component) +# 3 - save the node +# """ + +# material_001 = cript.Material(name=mat_1, identifier=[]) +# toluene = cript.Material(name="toluene", identifier=[{"smiles": "Cc1ccccc1"}]) # , {"pubchem_id": 1140}]) +# styrene = cript.Material(name="styrene", identifier=[{"smiles": "Cc1ccccc1"}]) + +# collection = cript.Collection(name=col_name) + +# project_loaded.material = [toluene, styrene] +# project_loaded.collection = [collection] + +# print("\n~~~~~~~~~~~~ SAVING NOW ~~~~~~~~~~~") +# print(project_loaded) # material_loaded +# print("~~~~~~~~~~") +# cript_api.save_node(project_loaded) # material_loaded +# print("BASICALLY now WE NEED TO ASSERT ON THE RESPONSE, NOT RELOAD IT INTO A NODE") + +# print("\n-- probably need to fix save --\n---project after saved") + +# get_url = f"/material/{uuid}" # f"/project/{uuid}" +# edited_result = cript_api._capsule_request(url_path=get_url, method="GET") + +# print("\n~~~~~~ saved reflected result") +# print(edited_result.json()) + +# assert len(edited_result.json()["data"]) == 1 + +# final = edited_result.json()["data"][0] + +# assert len(final["property"]) == 2 # styrene and toluene + +# set1 = set([final["property"][0]["name"].lower(), final["property"][1]["name"].lower()]) + +# set2 = set([json.loads(toluene.property.get_json().json)["name"].lower(), json.loads(styrene.property.get_json().json)["name"].lower()]) + +# assert set1 == set2 # or material_003toluene.get_json().json)["name"] + +# print("\n___final") +# print(final) + +# assert final["collection"][0]["name"] == json.loads(collection.get_json().json)["name"] + +# print("now deleting proj and eventually 2 mats") +# print("only issue with this test is the toluene") + +# del_res = cript_api._capsule_request(url_path=f"/material/{uuid}", method="DELETE") + +# assert del_res.json()["code"] == 200 diff --git a/tests/nodes/primary_nodes/test_project.py b/tests/nodes/primary_nodes/test_project.py index a97f1b91..2685f5f9 100644 --- a/tests/nodes/primary_nodes/test_project.py +++ b/tests/nodes/primary_nodes/test_project.py @@ -113,9 +113,9 @@ def test_integration_project(cript_api, simple_project_node): """ -def test_update_project_change_or_reset_material(simple_collection_node, cript_api) -> None: +def test_update_project_change_or_reset_materials_newly_made(cript_api) -> None: """ - pytest nodes/primary_nodes/test_project.py::test_update_project_change_or_reset_material + pytest nodes/primary_nodes/test_project.py::test_update_project_change_or_reset_materials_newly_made test that a project can be updated and completley reset strategy: create something with a post/patch @@ -145,9 +145,9 @@ def test_update_project_change_or_reset_material(simple_collection_node, cript_a cr_res_list = create_response.json()["data"]["result"] - if create_response.json()["code"] in [409, 400]: - print("---create_response") - print(create_response) + if create_response.json()["code"] in [409, 400, 401]: + # print("---create_response") + # print(create_response) raise ValueError(create_response) elif create_response.json()["code"] in [201, 200]: @@ -175,21 +175,21 @@ def test_update_project_change_or_reset_material(simple_collection_node, cript_a collection = cript.Collection(name=col_name) - project_loaded.material = [material_001] + project_loaded.material = [material_001] # [material_001] project_loaded.collection = [collection] - print("\n~~~~~~~~~~~~ SAVING NOW ~~~~~~~~~~~") + # print("\n~~~~~~~~~~~~ SAVING NOW ~~~~~~~~~~~") cript_api.save_node(project_loaded) - print("BASICALLY now WE NEED TO ASSERT ON THE RESPONSE, NOT RELOAD IT INTO A NODE") + # print("BASICALLY now WE NEED TO ASSERT ON THE RESPONSE, NOT RELOAD IT INTO A NODE") - print("\n-- probably need to fix save --\n---project after saved") + # print("\n-- probably need to fix save --\n---project after saved") get_url = f"/project/{uuid}" edited_result = cript_api._capsule_request(url_path=get_url, method="GET") - print("\n~~~~~~ saved reflected result") - print(edited_result.json()) + # print("\n~~~~~~ saved reflected result") + # print(edited_result.json()) assert len(edited_result.json()["data"]) == 1 @@ -200,17 +200,17 @@ def test_update_project_change_or_reset_material(simple_collection_node, cript_a assert final["material"][0]["name"] == json.loads(material_001.get_json().json)["name"] # or material_003toluene.get_json().json)["name"] assert final["collection"][0]["name"] == json.loads(collection.get_json().json)["name"] - print("now deleting proj and eventually 2 mats") - print("only issue with this test is the toluene") + # print("now deleting proj and eventually 2 mats") + # print("only issue with this test is the toluene") del_res = cript_api._capsule_request(url_path=f"/project/{uuid}", method="DELETE") assert del_res.json()["code"] == 200 -def test_add_existing_material_to_project(cript_api) -> None: +def test_update_project_change_or_reset_material_to_existing_materials(cript_api) -> None: """ - pytest nodes/primary_nodes/test_project.py::test_update_project_change_or_reset_material + pytest nodes/primary_nodes/test_project.py::test_update_project_change_or_reset_material_to_existing_materials test that a project can be updated and completley reset strategy: create something with a post/patch @@ -220,13 +220,113 @@ def test_add_existing_material_to_project(cript_api) -> None: epoch_time = int(time.time()) name_1 = f"myproj_ali_{epoch_time}" + mat_1 = f"my_mat__{epoch_time}" + col_name = f"031o0col__{epoch_time}" url_path = f"/project/" create_payload = {"node": ["Project"], "name": name_1, "material": [{"uuid": "1809330c-31d2-4a80-af72-77b84070ee1d"}, {"uuid": "ea8f957c-b6e5-4668-b306-e0d6b0d05d9a"}]} try: create_response = cript_api._capsule_request(url_path=url_path, method="POST", data=json.dumps(create_payload)) - print(create_response) + # print(create_response) + except Exception as e: + print(e) + + cr_res_list = create_response.json()["data"]["result"] + + if create_response.json()["code"] in [409, 400, 401]: + # print("---create_response") + # print(create_response) + raise ValueError(create_response) + + elif create_response.json()["code"] in [201, 200]: + + uuid = None + for item in cr_res_list: + if item["node"] == ["Project"]: + + uuid = item["uuid"] + if uuid == None: + raise ValueError("no project node") + + get_url = f"/project/{uuid}" + + result = cript_api._capsule_request(url_path=get_url, method="GET") + + result_json_dict = result.json() + + my_project_from_res_data_dict = result_json_dict["data"][0] + + project_list = cript.load_nodes_from_json(nodes_json=json.dumps(my_project_from_res_data_dict)) + project_loaded = project_list + + material_001 = cript.Material(name=mat_1, identifier=[]) + toluene = cript.Material(name="toluene", identifier=[{"smiles": "Cc1ccccc1"}]) # , {"pubchem_id": 1140}]) + styrene = cript.Material(name="styrene", identifier=[{"smiles": "Cc1ccccc1"}]) + + collection = cript.Collection(name=col_name) + + project_loaded.material = [toluene, styrene] + project_loaded.collection = [collection] + + # print("\n~~~~~~~~~~~~ SAVING NOW ~~~~~~~~~~~") + # print(project_loaded) + # print("~~~~~~~~~~") + cript_api.save_node(project_loaded) + # print("BASICALLY now WE NEED TO ASSERT ON THE RESPONSE, NOT RELOAD IT INTO A NODE") + + # print("\n-- probably need to fix save --\n---project after saved") + + get_url = f"/project/{uuid}" + edited_result = cript_api._capsule_request(url_path=get_url, method="GET") + + # print("\n~~~~~~ saved reflected result") + # print(edited_result.json()) + + assert len(edited_result.json()["data"]) == 1 + + final = edited_result.json()["data"][0] + + assert len(final["material"]) == 2 # styrene and toluene + + set1 = set([final["material"][0]["name"].lower(), final["material"][1]["name"].lower()]) + + set2 = set([json.loads(toluene.get_json().json)["name"].lower(), json.loads(styrene.get_json().json)["name"].lower()]) + + assert set1 == set2 # or material_003toluene.get_json().json)["name"] + + # print("\n___final") + # print(final) + + assert final["collection"][0]["name"] == json.loads(collection.get_json().json)["name"] + + # print("now deleting proj and eventually 2 mats") + # print("only issue with this test is the toluene") + + del_res = cript_api._capsule_request(url_path=f"/project/{uuid}", method="DELETE") + + assert del_res.json()["code"] == 200 + + +def test_add_existing_materials_by_name_to_project(cript_api) -> None: + """ + pytest nodes/primary_nodes/test_project.py::test_add_existing_materials_by_name_to_project + test that a project can be updated and completley reset + strategy: + create something with a post/patch + with a name (we will delete at the end) + then try to obtain it with load data + """ + + epoch_time = int(time.time()) + name_1 = f"myproj_ali_{epoch_time}" + + url_path = f"/project/" + create_payload = {"node": ["Project"], "name": name_1, "material": [{"uuid": "1809330c-31d2-4a80-af72-77b84070ee1d"}, {"uuid": "ea8f957c-b6e5-4668-b306-e0d6b0d05d9a"}]} + + try: + create_response = cript_api._capsule_request(url_path=url_path, method="POST", data=json.dumps(create_payload)) + # print(create_response) except Exception as e: print(e) @@ -235,7 +335,7 @@ def test_add_existing_material_to_project(cript_api) -> None: if create_response.json()["code"] in [409, 400]: - print(create_response) + # print(create_response) raise ValueError(create_response) elif create_response.json()["code"] in [201, 200]: @@ -258,8 +358,74 @@ def test_add_existing_material_to_project(cript_api) -> None: project_list = cript.load_nodes_from_json(nodes_json=json.dumps(my_project_from_res_data_dict)) project_loaded = project_list - toluene = cript.Material(name="toluene", identifier=[{"smiles": "Cc1ccccc1"}]) # , {"pubchem_id": 1140}]) + # toluene = cript.Material(name="toluene", identifier=[{"smiles": "Cc1ccccc1"}]) # , {"pubchem_id": 1140}]) + # styrene = cript.Material(name="styrene", identifier=[{"smiles": "Cc1ccccc1"}]) + + # print("\n~~~~~~~~~~~~ ADDING NOW ~~~~~~~~~~~") + + # cript_api.add_existing_node_by_name(parent_node=project_loaded, child_node=toluene) + add_res = cript_api.add_existing_nodes_by_name(parent_node=project_loaded, child_class_type="material", existing_child_node_names=["toluene", "styrene"]) + assert add_res.json()["code"] == 200 + + +def test_remove_existing_materials_by_name_from_project(cript_api) -> None: + """ + pytest nodes/primary_nodes/test_project.py::test_remove_existing_materials_by_name_from_project + test that a project can be updated and completley reset + strategy: + create something with a post/patch + with a name (we will delete at the end) + then try to obtain it with load data + """ + + epoch_time = int(time.time()) + name_1 = f"myproj_ali_{epoch_time}" + + url_path = f"/project/" + create_payload = {"node": ["Project"], "name": name_1, "material": [{"uuid": "1809330c-31d2-4a80-af72-77b84070ee1d"}, {"uuid": "ea8f957c-b6e5-4668-b306-e0d6b0d05d9a"}]} + + try: + create_response = cript_api._capsule_request(url_path=url_path, method="POST", data=json.dumps(create_payload)) + # print(create_response) + except Exception as e: + + print(e) + + cr_res_list = create_response.json()["data"]["result"] + + if create_response.json()["code"] in [409, 400]: + + # print(create_response) + raise ValueError(create_response) + + elif create_response.json()["code"] in [201, 200]: + + uuid = None + for item in cr_res_list: + if item["node"] == ["Project"]: + uuid = item["uuid"] + if uuid == None: + raise ValueError("no project node") + + get_url = f"/project/{uuid}" + + result = cript_api._capsule_request(url_path=get_url, method="GET") + result_json_dict = result.json() + my_project_from_res_data_dict = result_json_dict["data"][0] + + project_list = cript.load_nodes_from_json(nodes_json=json.dumps(my_project_from_res_data_dict)) + project_loaded = project_list + + # toluene = cript.Material(name="toluene", identifier=[{"smiles": "Cc1ccccc1"}]) # , {"pubchem_id": 1140}]) + # styrene = cript.Material(name="styrene", identifier=[{"smiles": "Cc1ccccc1"}]) + + # print("\n~~~~~~~~~~~~ ADDING NOW ~~~~~~~~~~~") + + # cript_api.add_existing_node_by_name(parent_node=project_loaded, child_node=toluene) + add_res = cript_api.add_existing_nodes_by_name(parent_node=project_loaded, child_class_type="material", existing_child_node_names=["toluene", "styrene"]) + + # print("\n~~~~~~~~~~~~ now deleteing ~~~~~~~~~~~") - print("\n~~~~~~~~~~~~ ADDING NOW ~~~~~~~~~~~") + delete_res = cript_api.remove_nodes_by_name(parent_node=project_loaded, child_class_type="material", existing_child_node_names=["toluene", "styrene"]) - cript_api.add_existing_node_by_name(parent_node=project_loaded, child_node=toluene) + assert delete_res.json()["code"] == 200 diff --git a/tests/utils/integration_test_helper.py b/tests/utils/integration_test_helper.py index d242475e..f723b0f3 100644 --- a/tests/utils/integration_test_helper.py +++ b/tests/utils/integration_test_helper.py @@ -64,7 +64,7 @@ def save_integration_node_helper(cript_api: cript.API, project_node: cript.Proje print(project_node.get_json(sort_keys=False, condense_to_uuid={}, indent=2).json) print("==============================================================") - cript_api.save(project_node) + cript_api.save(project_node) # cript_api.save_node(project_node) (if this passes then great!) # get the project that was just saved my_paginator = cript_api.search(node_type=cript.Project, search_mode=cript.SearchModes.EXACT_NAME, value_to_search=project_node.name)