From 58913e2f233f15f26eff86763eadc21cee426665 Mon Sep 17 00:00:00 2001 From: Ludwig Schneider Date: Tue, 12 Mar 2024 10:43:00 -0500 Subject: [PATCH 1/5] cache seems to work as intended now --- src/cript/nodes/exceptions.py | 13 + src/cript/nodes/util/json.py | 20 +- src/cript/nodes/uuid_base.py | 20 +- tests/test_node_util.py | 691 +++++++++++++++++----------------- 4 files changed, 392 insertions(+), 352 deletions(-) diff --git a/src/cript/nodes/exceptions.py b/src/cript/nodes/exceptions.py index 0d1942388..656298c6b 100644 --- a/src/cript/nodes/exceptions.py +++ b/src/cript/nodes/exceptions.py @@ -4,6 +4,19 @@ from cript.exceptions import CRIPTException +class CRIPTUUIDException(CRIPTException): + def __init__(self, uuid: str, old_type: str, new_type: str): + self.uuid = uuid + self.old_type = old_type + self.new_type = new_type + + def __str__(self) -> str: + return_msg = f"UUID collision error. A new node with UUID {self.uuid} is created of type {self.new_type}," + return_msg += f" but a node with the same UUID exists already as type {self.old_type}." + return_msg += " Please report the error on https://github.com/C-Accel-CRIPT/Python-SDK/issues , thank you." + return return_msg + + class CRIPTNodeSchemaError(CRIPTException): """ ## Definition diff --git a/src/cript/nodes/util/json.py b/src/cript/nodes/util/json.py index e61ff2307..ce013a794 100644 --- a/src/cript/nodes/util/json.py +++ b/src/cript/nodes/util/json.py @@ -14,6 +14,7 @@ CRIPTJsonDeserializationError, CRIPTJsonNodeError, ) +from cript.nodes.uuid_base import UUIDBaseNode class NodeEncoder(json.JSONEncoder): @@ -294,7 +295,7 @@ def __call__(self, node_str: Union[Dict, str]) -> Dict: return node_dict -def load_nodes_from_json(nodes_json: Union[str, Dict]): +def load_nodes_from_json(nodes_json: Union[str, Dict], _use_uuid_cache: Optional[Dict] = None): """ User facing function, that return a node and all its children from a json string input. @@ -349,7 +350,22 @@ def load_nodes_from_json(nodes_json: Union[str, Dict]): # but catches a lot of odd cases. And at the moment performance is not bottle necked here if not isinstance(nodes_json, str): nodes_json = json.dumps(nodes_json) - return json.loads(nodes_json, object_hook=node_json_hook) + + # Store previous UUIDBaseNode Cache state + previous_uuid_cache = UUIDBaseNode._uuid_cache + + if not (_use_uuid_cache is None): # If requested use a custom cache. + UUIDBaseNode._uuid_cache = _use_uuid_cache + + try: + loaded_nodes = json.loads(nodes_json, object_hook=node_json_hook) + finally: + # Definitively restore the old cachse state + UUIDBaseNode._uuid_cache = previous_uuid_cache + + if _use_uuid_cache is not None: + return loaded_nodes, _use_uuid_cache + return loaded_nodes def _rename_field(serialize_dict: Dict, old_name: str, new_name: str) -> Dict: diff --git a/src/cript/nodes/uuid_base.py b/src/cript/nodes/uuid_base.py index 1a3aa6e33..7841cb15d 100644 --- a/src/cript/nodes/uuid_base.py +++ b/src/cript/nodes/uuid_base.py @@ -1,9 +1,10 @@ import uuid from abc import ABC from dataclasses import dataclass, field, replace -from typing import Any, Dict +from typing import Any, Dict, Optional from cript.nodes.core import BaseNode +from cript.nodes.exceptions import CRIPTUUIDException def get_uuid_from_uid(uid): @@ -32,16 +33,24 @@ class JsonAttributes(BaseNode.JsonAttributes): _json_attrs: JsonAttributes = JsonAttributes() + def __new__(cls, **kwargs): + uuid: Optional[str] = kwargs.get("uuid") + if uuid and uuid in UUIDBaseNode._uuid_cache: + existing_node_to_overwrite = UUIDBaseNode._uuid_cache[uuid] + if type(existing_node_to_overwrite) is not cls: + raise CRIPTUUIDException(uuid, type(existing_node_to_overwrite), cls) + return existing_node_to_overwrite + new_uuid_node = super().__new__(cls) + return new_uuid_node + def __init__(self, **kwargs): # initialize Base class with node super().__init__(**kwargs) # Respect uuid if passed as argument, otherwise construct uuid from uid - uuid = kwargs.get("uuid", get_uuid_from_uid(self.uid)) + uuid: str = kwargs.get("uuid", get_uuid_from_uid(self.uid)) # replace name and notes within PrimaryBase self._json_attrs = replace(self._json_attrs, uuid=uuid) - - # Place successfully created node in the UUID cache - self._uuid_cache[uuid] = self + UUIDBaseNode._uuid_cache[uuid] = self @property def uuid(self) -> uuid.UUID: @@ -57,6 +66,7 @@ def url(self): def __deepcopy__(self, memo): node = super().__deepcopy__(memo) node._json_attrs = replace(node._json_attrs, uuid=get_uuid_from_uid(node.uid)) + UUIDBaseNode._uuid_cache[str(node.uuid)] = node return node @property diff --git a/tests/test_node_util.py b/tests/test_node_util.py index 3b03e93b1..3dc99906e 100644 --- a/tests/test_node_util.py +++ b/tests/test_node_util.py @@ -1,346 +1,347 @@ -import copy -import json -from dataclasses import replace - -import pytest - import cript -from cript.nodes.core import get_new_uid -from cript.nodes.exceptions import ( - CRIPTJsonNodeError, - CRIPTJsonSerializationError, - CRIPTNodeSchemaError, - CRIPTOrphanedComputationalProcessError, - CRIPTOrphanedComputationError, - CRIPTOrphanedDataError, - CRIPTOrphanedMaterialError, - CRIPTOrphanedProcessError, -) -from tests.utils.util import strip_uid_from_dict - - -def test_removing_nodes(simple_algorithm_node, complex_parameter_node, simple_algorithm_dict): - a = simple_algorithm_node - p = complex_parameter_node - a.parameter += [p] - assert strip_uid_from_dict(json.loads(a.json)) != simple_algorithm_dict - a.remove_child(p) - assert strip_uid_from_dict(json.loads(a.json)) == simple_algorithm_dict - - -def test_uid_deserialization(simple_algorithm_node, complex_parameter_node, simple_algorithm_dict): - material = cript.Material(name="my material", bigsmiles="{[][$]CC[$][]}") - - computation = cript.Computation(name="my computation name", type="analysis") - property1 = cript.Property("modulus_shear", "value", 5.0, "GPa", computation=[computation]) - property2 = cript.Property("modulus_loss", "value", 5.0, "GPa", computation=[computation]) - material.property = [property1, property2] - - material2 = cript.load_nodes_from_json(material.json) - assert json.loads(material.json) == json.loads(material2.json) - - material3_dict = { - "node": ["Material"], - "uid": "_:f6d56fdc-9df7-49a1-a843-cf92681932ad", - "uuid": "f6d56fdc-9df7-49a1-a843-cf92681932ad", - "name": "my material", - "property": [ - { - "node": ["Property"], - "uid": "_:82e7270e-9f35-4b35-80a2-faa6e7f670be", - "uuid": "82e7270e-9f35-4b35-80a2-faa6e7f670be", - "key": "modulus_shear", - "type": "value", - "value": 5.0, - "unit": "GPa", - "computation": [{"uid": "_:9ddda2c0-ff8c-4ce3-beb0-e0cafb6169ef"}], - }, - { - "node": ["Property"], - "uid": "_:fc4dfa5e-742c-4d0b-bb66-2185461f4582", - "uuid": "fc4dfa5e-742c-4d0b-bb66-2185461f4582", - "key": "modulus_loss", - "type": "value", - "value": 5.0, - "unit": "GPa", - "computation": [ - { - "uid": "_:9ddda2c0-ff8c-4ce3-beb0-e0cafb6169ef", - } - ], - }, - ], - "bigsmiles": "123456", - } - - with pytest.raises(cript.nodes.exceptions.CRIPTDeserializationUIDError): - cript.load_nodes_from_json(json.dumps(material3_dict)) - - # TODO convince beartype to allow _ProxyUID as well - # material4_dict = { - # "node": [ - # "Material" - # ], - # "uid": "_:f6d56fdc-9df7-49a1-a843-cf92681932ad", - # "uuid": "f6d56fdc-9df7-49a1-a843-cf92681932ad", - # "name": "my material", - # "property": [ - # { - # "node": [ - # "Property" - # ], - # "uid": "_:82e7270e-9f35-4b35-80a2-faa6e7f670be", - # "uuid": "82e7270e-9f35-4b35-80a2-faa6e7f670be", - # "key": "modulus_shear", - # "type": "value", - # "value": 5.0, - # "unit": "GPa", - # "computation": [ - # { - # "node": [ - # "Computation" - # ], - # "uid": "_:9ddda2c0-ff8c-4ce3-beb0-e0cafb6169ef" - # } - # ] - # }, - # { - # "node": [ - # "Property" - # ], - # "uid": "_:fc4dfa5e-742c-4d0b-bb66-2185461f4582", - # "uuid": "fc4dfa5e-742c-4d0b-bb66-2185461f4582", - # "key": "modulus_loss", - # "type": "value", - # "value": 5.0, - # "unit": "GPa", - # "computation": [ - # { - # "node": [ - # "Computation" - # ], - # "uid": "_:9ddda2c0-ff8c-4ce3-beb0-e0cafb6169ef", - # "uuid": "9ddda2c0-ff8c-4ce3-beb0-e0cafb6169ef", - # "name": "my computation name", - # "type": "analysis", - # "citation": [] - # } - # ] - # } - # ], - # "bigsmiles": "123456" - # } - - # material4 = cript.load_nodes_from_json(json.dumps(material4_dict)) - # assert json.loads(material.json) == json.loads(material4.json) - - -def test_json_error(complex_parameter_node): - parameter = complex_parameter_node - # Let's break the node by violating the data model - parameter._json_attrs = replace(parameter._json_attrs, value="abc") - with pytest.raises(CRIPTNodeSchemaError): - parameter.validate() - # Let's break it completely - parameter._json_attrs = None - with pytest.raises(CRIPTJsonSerializationError): - parameter.json - - -def test_local_search(simple_algorithm_node, complex_parameter_node): - a = simple_algorithm_node - # Check if we can use search to find the algorithm node, but specifying node and key - find_algorithms = a.find_children({"node": "Algorithm", "key": "mc_barostat"}) - assert find_algorithms == [a] - # Check if it correctly exclude the algorithm if key is specified to non-existent value - find_algorithms = a.find_children({"node": "Algorithm", "key": "mc"}) - assert find_algorithms == [] - - # Adding 2 separate parameters to test deeper search - p1 = complex_parameter_node - p2 = copy.deepcopy(complex_parameter_node) - p2.key = "damping_time" - p2.value = 15.0 - p2.unit = "m" - a.parameter += [p1, p2] - - # Test if we can find a specific one of the parameters - find_parameter = a.find_children({"key": "damping_time"}) - assert find_parameter == [p2] - - # Test to find the other parameter - find_parameter = a.find_children({"key": "update_frequency"}) - assert find_parameter == [p1] - - # Test if correctly find no parameter if we are searching for a non-existent parameter - find_parameter = a.find_children({"key": "update"}) - assert find_parameter == [] - - # Test nested search. Here we are looking for any node that has a child node parameter as specified. - find_algorithms = a.find_children({"parameter": {"key": "damping_time"}}) - assert find_algorithms == [a] - # Same as before, but specifying two children that have to be present (AND condition) - find_algorithms = a.find_children({"parameter": [{"key": "damping_time"}, {"key": "update_frequency"}]}) - assert find_algorithms == [a] - - # Test that the main node is correctly excluded if we specify an additionally non-existent parameter - find_algorithms = a.find_children({"parameter": [{"key": "damping_time"}, {"key": "update_frequency"}, {"foo": "bar"}]}) - assert find_algorithms == [] - - -def test_cycles(complex_data_node, simple_computation_node): - # We create a wrong cycle with parameters here. - # TODO replace this with nodes that actually can form a cycle - d = copy.deepcopy(complex_data_node) - c = copy.deepcopy(simple_computation_node) - d.computation += [c] - # Using input and output data guarantees a cycle here. - c.output_data += [d] - c.input_data += [d] - - # # Test the repetition of a citation. - # # Notice that we do not use a deepcopy here, as we want the citation to be the exact same node. - # citation = d.citation[0] - # # c._json_attrs.citation.append(citation) - # c.citation += [citation] - # # print(c.get_json(indent=2).json) - # # c.validate() - - # Generate json with an implicit cycle - c.json - d.json - - -def test_uid_serial(simple_inventory_node): - simple_inventory_node.material += simple_inventory_node.material - json_dict = json.loads(simple_inventory_node.get_json(condense_to_uuid={}).json) - assert len(json_dict["material"]) == 4 - assert isinstance(json_dict["material"][2]["uid"], str) - assert json_dict["material"][2]["uid"].startswith("_:") - assert len(json_dict["material"][2]["uid"]) == len(get_new_uid()) - assert isinstance(json_dict["material"][3]["uid"], str) - assert json_dict["material"][3]["uid"].startswith("_:") - assert len(json_dict["material"][3]["uid"]) == len(get_new_uid()) - assert json_dict["material"][3]["uid"] != json_dict["material"][2]["uid"] - - -def test_invalid_json_load(): - def raise_node_dict(node_dict): - node_str = json.dumps(node_dict) - with pytest.raises(CRIPTJsonNodeError): - cript.load_nodes_from_json(node_str) - - node_dict = {"node": "Computation"} - raise_node_dict(node_dict) - node_dict = {"node": []} - raise_node_dict(node_dict) - node_dict = {"node": ["asdf", "asdf"]} - raise_node_dict(node_dict) - node_dict = {"node": [None]} - raise_node_dict(node_dict) - - -def test_invalid_project_graphs(simple_project_node, simple_material_node, simple_process_node, simple_property_node, simple_data_node, simple_computation_node, simple_computation_process_node): - project = copy.deepcopy(simple_project_node) - process = copy.deepcopy(simple_process_node) - material = copy.deepcopy(simple_material_node) - - ingredient = cript.Ingredient(material=material, quantity=[cript.Quantity(key="mass", value=1.23, unit="kg")]) - process.ingredient += [ingredient] - - # Add the process to the experiment, but not in inventory or materials - # Invalid graph - project.collection[0].experiment[0].process += [process] - with pytest.raises(CRIPTOrphanedMaterialError): - project.validate() - - # First fix add material to inventory - project.collection[0].inventory += [cript.Inventory("test_inventory", material=[material])] - project.validate() - # Reverse this fix - project.collection[0].inventory = [] - with pytest.raises(CRIPTOrphanedMaterialError): - project.validate() - - # Fix by add to the materials list instead. - # Using the util helper function for this. - cript.add_orphaned_nodes_to_project(project, active_experiment=None, max_iteration=10) - project.validate() - - # Now add an orphan process to the graph - process2 = copy.deepcopy(simple_process_node) - process.prerequisite_process += [process2] - with pytest.raises(CRIPTOrphanedProcessError): - project.validate() - - # Wrong fix it helper node - dummy_experiment = copy.deepcopy(project.collection[0].experiment[0]) - with pytest.raises(RuntimeError): - cript.add_orphaned_nodes_to_project(project, dummy_experiment) - # Problem still persists - with pytest.raises(CRIPTOrphanedProcessError): - project.validate() - # Fix by using the helper function correctly - cript.add_orphaned_nodes_to_project(project, project.collection[0].experiment[0], 10) - project.validate() - - # We add property to the material, because that adds the opportunity for orphaned data and computation - property = copy.deepcopy(simple_property_node) - material.property += [property] - project.validate() - # Now add an orphan data - data = copy.deepcopy(simple_data_node) - property.data = [data] - with pytest.raises(CRIPTOrphanedDataError): - project.validate() - # Fix with the helper function - cript.add_orphaned_nodes_to_project(project, project.collection[0].experiment[0], 10) - project.validate() - - # Add an orphan Computation - computation = copy.deepcopy(simple_computation_node) - property.computation += [computation] - with pytest.raises(CRIPTOrphanedComputationError): - project.validate() - # Fix with the helper function - cript.add_orphaned_nodes_to_project(project, project.collection[0].experiment[0], 10) - project.validate() - - # Add orphan computational process - comp_proc = copy.deepcopy(simple_computation_process_node) - data.computation_process += [comp_proc] - with pytest.raises(CRIPTOrphanedComputationalProcessError): - while True: - try: # Do trigger not orphan materials - project.validate() - except CRIPTOrphanedMaterialError as exc: - project._json_attrs.material.append(exc.orphaned_node) - except CRIPTOrphanedProcessError as exc: - project.collection[0].experiment[0]._json_attrs.process.append(exc.orphaned_node) - else: - break - - cript.add_orphaned_nodes_to_project(project, project.collection[0].experiment[0], 10) - project.validate() - - -def test_expanded_json(complex_project_node): - """ - Tests the generation and deserialization of expanded JSON for a complex project node. - - This test verifies 2 key aspects: - 1. A complex project node can be serialized into an expanded JSON string, without UUID placeholders. - 2. The expanded JSON can be deserialized into a node that is equivalent to the original node. - """ - project_expanded_json: str = complex_project_node.get_expanded_json() - deserialized_project_node: cript.Project = cript.load_nodes_from_json(project_expanded_json) - - # assert the expanded JSON was correctly deserialized to project node - assert deserialized_project_node == complex_project_node - - condensed_json: str = complex_project_node.json - - # since short JSON has UUID it will not be able to deserialize correctly and will - # raise CRIPTJsonDeserializationError - with pytest.raises(cript.nodes.exceptions.CRIPTJsonDeserializationError): - cript.load_nodes_from_json(condensed_json) + +# def test_removing_nodes(simple_algorithm_node, complex_parameter_node, simple_algorithm_dict): +# a = simple_algorithm_node +# p = complex_parameter_node +# a.parameter += [p] +# assert strip_uid_from_dict(json.loads(a.json)) != simple_algorithm_dict +# a.remove_child(p) +# assert strip_uid_from_dict(json.loads(a.json)) == simple_algorithm_dict + + +# def test_uid_deserialization(simple_algorithm_node, complex_parameter_node, simple_algorithm_dict): +# material = cript.Material(name="my material", bigsmiles="{[][$]CC[$][]}") + +# computation = cript.Computation(name="my computation name", type="analysis") +# property1 = cript.Property("modulus_shear", "value", 5.0, "GPa", computation=[computation]) +# property2 = cript.Property("modulus_loss", "value", 5.0, "GPa", computation=[computation]) +# material.property = [property1, property2] + +# material2 = cript.load_nodes_from_json(material.json) +# assert json.loads(material.json) == json.loads(material2.json) + +# material3_dict = { +# "node": ["Material"], +# "uid": "_:f6d56fdc-9df7-49a1-a843-cf92681932ad", +# "uuid": "f6d56fdc-9df7-49a1-a843-cf92681932ad", +# "name": "my material", +# "property": [ +# { +# "node": ["Property"], +# "uid": "_:82e7270e-9f35-4b35-80a2-faa6e7f670be", +# "uuid": "82e7270e-9f35-4b35-80a2-faa6e7f670be", +# "key": "modulus_shear", +# "type": "value", +# "value": 5.0, +# "unit": "GPa", +# "computation": [{"uid": "_:9ddda2c0-ff8c-4ce3-beb0-e0cafb6169ef"}], +# }, +# { +# "node": ["Property"], +# "uid": "_:fc4dfa5e-742c-4d0b-bb66-2185461f4582", +# "uuid": "fc4dfa5e-742c-4d0b-bb66-2185461f4582", +# "key": "modulus_loss", +# "type": "value", +# "value": 5.0, +# "unit": "GPa", +# "computation": [ +# { +# "uid": "_:9ddda2c0-ff8c-4ce3-beb0-e0cafb6169ef", +# } +# ], +# }, +# ], +# "bigsmiles": "123456", +# } + +# with pytest.raises(cript.nodes.exceptions.CRIPTDeserializationUIDError): +# cript.load_nodes_from_json(json.dumps(material3_dict)) + +# # TODO convince beartype to allow _ProxyUID as well +# # material4_dict = { +# # "node": [ +# # "Material" +# # ], +# # "uid": "_:f6d56fdc-9df7-49a1-a843-cf92681932ad", +# # "uuid": "f6d56fdc-9df7-49a1-a843-cf92681932ad", +# # "name": "my material", +# # "property": [ +# # { +# # "node": [ +# # "Property" +# # ], +# # "uid": "_:82e7270e-9f35-4b35-80a2-faa6e7f670be", +# # "uuid": "82e7270e-9f35-4b35-80a2-faa6e7f670be", +# # "key": "modulus_shear", +# # "type": "value", +# # "value": 5.0, +# # "unit": "GPa", +# # "computation": [ +# # { +# # "node": [ +# # "Computation" +# # ], +# # "uid": "_:9ddda2c0-ff8c-4ce3-beb0-e0cafb6169ef" +# # } +# # ] +# # }, +# # { +# # "node": [ +# # "Property" +# # ], +# # "uid": "_:fc4dfa5e-742c-4d0b-bb66-2185461f4582", +# # "uuid": "fc4dfa5e-742c-4d0b-bb66-2185461f4582", +# # "key": "modulus_loss", +# # "type": "value", +# # "value": 5.0, +# # "unit": "GPa", +# # "computation": [ +# # { +# # "node": [ +# # "Computation" +# # ], +# # "uid": "_:9ddda2c0-ff8c-4ce3-beb0-e0cafb6169ef", +# # "uuid": "9ddda2c0-ff8c-4ce3-beb0-e0cafb6169ef", +# # "name": "my computation name", +# # "type": "analysis", +# # "citation": [] +# # } +# # ] +# # } +# # ], +# # "bigsmiles": "123456" +# # } + +# # material4 = cript.load_nodes_from_json(json.dumps(material4_dict)) +# # assert json.loads(material.json) == json.loads(material4.json) + + +# def test_json_error(complex_parameter_node): +# parameter = complex_parameter_node +# # Let's break the node by violating the data model +# parameter._json_attrs = replace(parameter._json_attrs, value="abc") +# with pytest.raises(CRIPTNodeSchemaError): +# parameter.validate() +# # Let's break it completely +# parameter._json_attrs = None +# with pytest.raises(CRIPTJsonSerializationError): +# parameter.json + + +# def test_local_search(simple_algorithm_node, complex_parameter_node): +# a = simple_algorithm_node +# # Check if we can use search to find the algorithm node, but specifying node and key +# find_algorithms = a.find_children({"node": "Algorithm", "key": "mc_barostat"}) +# assert find_algorithms == [a] +# # Check if it correctly exclude the algorithm if key is specified to non-existent value +# find_algorithms = a.find_children({"node": "Algorithm", "key": "mc"}) +# assert find_algorithms == [] + +# # Adding 2 separate parameters to test deeper search +# p1 = complex_parameter_node +# p2 = copy.deepcopy(complex_parameter_node) +# p2.key = "damping_time" +# p2.value = 15.0 +# p2.unit = "m" +# a.parameter += [p1, p2] + +# # Test if we can find a specific one of the parameters +# find_parameter = a.find_children({"key": "damping_time"}) +# assert find_parameter == [p2] + +# # Test to find the other parameter +# find_parameter = a.find_children({"key": "update_frequency"}) +# assert find_parameter == [p1] + +# # Test if correctly find no parameter if we are searching for a non-existent parameter +# find_parameter = a.find_children({"key": "update"}) +# assert find_parameter == [] + +# # Test nested search. Here we are looking for any node that has a child node parameter as specified. +# find_algorithms = a.find_children({"parameter": {"key": "damping_time"}}) +# assert find_algorithms == [a] +# # Same as before, but specifying two children that have to be present (AND condition) +# find_algorithms = a.find_children({"parameter": [{"key": "damping_time"}, {"key": "update_frequency"}]}) +# assert find_algorithms == [a] + +# # Test that the main node is correctly excluded if we specify an additionally non-existent parameter +# find_algorithms = a.find_children({"parameter": [{"key": "damping_time"}, {"key": "update_frequency"}, {"foo": "bar"}]}) +# assert find_algorithms == [] + + +# def test_cycles(complex_data_node, simple_computation_node): +# # We create a wrong cycle with parameters here. +# # TODO replace this with nodes that actually can form a cycle +# d = copy.deepcopy(complex_data_node) +# c = copy.deepcopy(simple_computation_node) +# d.computation += [c] +# # Using input and output data guarantees a cycle here. +# c.output_data += [d] +# c.input_data += [d] + +# # # Test the repetition of a citation. +# # # Notice that we do not use a deepcopy here, as we want the citation to be the exact same node. +# # citation = d.citation[0] +# # # c._json_attrs.citation.append(citation) +# # c.citation += [citation] +# # # print(c.get_json(indent=2).json) +# # # c.validate() + +# # Generate json with an implicit cycle +# c.json +# d.json + + +# def test_uid_serial(simple_inventory_node): +# simple_inventory_node.material += simple_inventory_node.material +# json_dict = json.loads(simple_inventory_node.get_json(condense_to_uuid={}).json) +# assert len(json_dict["material"]) == 4 +# assert isinstance(json_dict["material"][2]["uid"], str) +# assert json_dict["material"][2]["uid"].startswith("_:") +# assert len(json_dict["material"][2]["uid"]) == len(get_new_uid()) +# assert isinstance(json_dict["material"][3]["uid"], str) +# assert json_dict["material"][3]["uid"].startswith("_:") +# assert len(json_dict["material"][3]["uid"]) == len(get_new_uid()) +# assert json_dict["material"][3]["uid"] != json_dict["material"][2]["uid"] + + +# def test_invalid_json_load(): +# def raise_node_dict(node_dict): +# node_str = json.dumps(node_dict) +# with pytest.raises(CRIPTJsonNodeError): +# cript.load_nodes_from_json(node_str) + +# node_dict = {"node": "Computation"} +# raise_node_dict(node_dict) +# node_dict = {"node": []} +# raise_node_dict(node_dict) +# node_dict = {"node": ["asdf", "asdf"]} +# raise_node_dict(node_dict) +# node_dict = {"node": [None]} +# raise_node_dict(node_dict) + + +# def test_invalid_project_graphs(simple_project_node, simple_material_node, simple_process_node, simple_property_node, simple_data_node, simple_computation_node, simple_computation_process_node): +# project = copy.deepcopy(simple_project_node) +# process = copy.deepcopy(simple_process_node) +# material = copy.deepcopy(simple_material_node) + +# ingredient = cript.Ingredient(material=material, quantity=[cript.Quantity(key="mass", value=1.23, unit="kg")]) +# process.ingredient += [ingredient] + +# # Add the process to the experiment, but not in inventory or materials +# # Invalid graph +# project.collection[0].experiment[0].process += [process] +# with pytest.raises(CRIPTOrphanedMaterialError): +# project.validate() + +# # First fix add material to inventory +# project.collection[0].inventory += [cript.Inventory("test_inventory", material=[material])] +# project.validate() +# # Reverse this fix +# project.collection[0].inventory = [] +# with pytest.raises(CRIPTOrphanedMaterialError): +# project.validate() + +# # Fix by add to the materials list instead. +# # Using the util helper function for this. +# cript.add_orphaned_nodes_to_project(project, active_experiment=None, max_iteration=10) +# project.validate() + +# # Now add an orphan process to the graph +# process2 = copy.deepcopy(simple_process_node) +# process.prerequisite_process += [process2] +# with pytest.raises(CRIPTOrphanedProcessError): +# project.validate() + +# # Wrong fix it helper node +# dummy_experiment = copy.deepcopy(project.collection[0].experiment[0]) +# with pytest.raises(RuntimeError): +# cript.add_orphaned_nodes_to_project(project, dummy_experiment) +# # Problem still persists +# with pytest.raises(CRIPTOrphanedProcessError): +# project.validate() +# # Fix by using the helper function correctly +# cript.add_orphaned_nodes_to_project(project, project.collection[0].experiment[0], 10) +# project.validate() + +# # We add property to the material, because that adds the opportunity for orphaned data and computation +# property = copy.deepcopy(simple_property_node) +# material.property += [property] +# project.validate() +# # Now add an orphan data +# data = copy.deepcopy(simple_data_node) +# property.data = [data] +# with pytest.raises(CRIPTOrphanedDataError): +# project.validate() +# # Fix with the helper function +# cript.add_orphaned_nodes_to_project(project, project.collection[0].experiment[0], 10) +# project.validate() + +# # Add an orphan Computation +# computation = copy.deepcopy(simple_computation_node) +# property.computation += [computation] +# with pytest.raises(CRIPTOrphanedComputationError): +# project.validate() +# # Fix with the helper function +# cript.add_orphaned_nodes_to_project(project, project.collection[0].experiment[0], 10) +# project.validate() + +# # Add orphan computational process +# comp_proc = copy.deepcopy(simple_computation_process_node) +# data.computation_process += [comp_proc] +# with pytest.raises(CRIPTOrphanedComputationalProcessError): +# while True: +# try: # Do trigger not orphan materials +# project.validate() +# except CRIPTOrphanedMaterialError as exc: +# project._json_attrs.material.append(exc.orphaned_node) +# except CRIPTOrphanedProcessError as exc: +# project.collection[0].experiment[0]._json_attrs.process.append(exc.orphaned_node) +# else: +# break + +# cript.add_orphaned_nodes_to_project(project, project.collection[0].experiment[0], 10) +# project.validate() + + +# def test_expanded_json(complex_project_node): +# """ +# Tests the generation and deserialization of expanded JSON for a complex project node. + +# This test verifies 2 key aspects: +# 1. A complex project node can be serialized into an expanded JSON string, without UUID placeholders. +# 2. The expanded JSON can be deserialized into a node that is equivalent to the original node. +# """ +# project_expanded_json: str = complex_project_node.get_expanded_json() +# deserialized_project_node: cript.Project = cript.load_nodes_from_json(project_expanded_json) + +# # assert the expanded JSON was correctly deserialized to project node +# assert deserialized_project_node == complex_project_node + +# condensed_json: str = complex_project_node.json + +# # since short JSON has UUID it will not be able to deserialize correctly and will +# # raise CRIPTJsonDeserializationError +# with pytest.raises(cript.nodes.exceptions.CRIPTJsonDeserializationError): +# cript.load_nodes_from_json(condensed_json) + + +def test_uuid_cache_override(complex_project_node): + normal_serial = complex_project_node.get_expanded_json() + reloaded_project = cript.load_nodes_from_json(normal_serial) + + # For a normal load, the reloaded node as to be the same as before. + assert reloaded_project is complex_project_node + + # Load with custom cache override + custom_project, cache = cript.load_nodes_from_json(normal_serial, _use_uuid_cache=dict()) + + assert custom_project is not reloaded_project + + # Make sure that the nodes in the different caches are different + for key in cache: + old_node = cript.nodes.uuid_base.UUIDBaseNode._uuid_cache[key] + new_node = cache[key] + assert old_node.uuid == new_node.uuid + assert old_node is not new_node From ccbbdb546c1aa1d4029975d7731809855a297856 Mon Sep 17 00:00:00 2001 From: Ludwig Schneider Date: Tue, 12 Mar 2024 10:43:20 -0500 Subject: [PATCH 2/5] reenable tests --- tests/test_node_util.py | 651 ++++++++++++++++++++-------------------- 1 file changed, 326 insertions(+), 325 deletions(-) diff --git a/tests/test_node_util.py b/tests/test_node_util.py index 3dc99906e..aef4fa695 100644 --- a/tests/test_node_util.py +++ b/tests/test_node_util.py @@ -1,330 +1,331 @@ import cript -# def test_removing_nodes(simple_algorithm_node, complex_parameter_node, simple_algorithm_dict): -# a = simple_algorithm_node -# p = complex_parameter_node -# a.parameter += [p] -# assert strip_uid_from_dict(json.loads(a.json)) != simple_algorithm_dict -# a.remove_child(p) -# assert strip_uid_from_dict(json.loads(a.json)) == simple_algorithm_dict - - -# def test_uid_deserialization(simple_algorithm_node, complex_parameter_node, simple_algorithm_dict): -# material = cript.Material(name="my material", bigsmiles="{[][$]CC[$][]}") - -# computation = cript.Computation(name="my computation name", type="analysis") -# property1 = cript.Property("modulus_shear", "value", 5.0, "GPa", computation=[computation]) -# property2 = cript.Property("modulus_loss", "value", 5.0, "GPa", computation=[computation]) -# material.property = [property1, property2] - -# material2 = cript.load_nodes_from_json(material.json) -# assert json.loads(material.json) == json.loads(material2.json) - -# material3_dict = { -# "node": ["Material"], -# "uid": "_:f6d56fdc-9df7-49a1-a843-cf92681932ad", -# "uuid": "f6d56fdc-9df7-49a1-a843-cf92681932ad", -# "name": "my material", -# "property": [ -# { -# "node": ["Property"], -# "uid": "_:82e7270e-9f35-4b35-80a2-faa6e7f670be", -# "uuid": "82e7270e-9f35-4b35-80a2-faa6e7f670be", -# "key": "modulus_shear", -# "type": "value", -# "value": 5.0, -# "unit": "GPa", -# "computation": [{"uid": "_:9ddda2c0-ff8c-4ce3-beb0-e0cafb6169ef"}], -# }, -# { -# "node": ["Property"], -# "uid": "_:fc4dfa5e-742c-4d0b-bb66-2185461f4582", -# "uuid": "fc4dfa5e-742c-4d0b-bb66-2185461f4582", -# "key": "modulus_loss", -# "type": "value", -# "value": 5.0, -# "unit": "GPa", -# "computation": [ -# { -# "uid": "_:9ddda2c0-ff8c-4ce3-beb0-e0cafb6169ef", -# } -# ], -# }, -# ], -# "bigsmiles": "123456", -# } - -# with pytest.raises(cript.nodes.exceptions.CRIPTDeserializationUIDError): -# cript.load_nodes_from_json(json.dumps(material3_dict)) - -# # TODO convince beartype to allow _ProxyUID as well -# # material4_dict = { -# # "node": [ -# # "Material" -# # ], -# # "uid": "_:f6d56fdc-9df7-49a1-a843-cf92681932ad", -# # "uuid": "f6d56fdc-9df7-49a1-a843-cf92681932ad", -# # "name": "my material", -# # "property": [ -# # { -# # "node": [ -# # "Property" -# # ], -# # "uid": "_:82e7270e-9f35-4b35-80a2-faa6e7f670be", -# # "uuid": "82e7270e-9f35-4b35-80a2-faa6e7f670be", -# # "key": "modulus_shear", -# # "type": "value", -# # "value": 5.0, -# # "unit": "GPa", -# # "computation": [ -# # { -# # "node": [ -# # "Computation" -# # ], -# # "uid": "_:9ddda2c0-ff8c-4ce3-beb0-e0cafb6169ef" -# # } -# # ] -# # }, -# # { -# # "node": [ -# # "Property" -# # ], -# # "uid": "_:fc4dfa5e-742c-4d0b-bb66-2185461f4582", -# # "uuid": "fc4dfa5e-742c-4d0b-bb66-2185461f4582", -# # "key": "modulus_loss", -# # "type": "value", -# # "value": 5.0, -# # "unit": "GPa", -# # "computation": [ -# # { -# # "node": [ -# # "Computation" -# # ], -# # "uid": "_:9ddda2c0-ff8c-4ce3-beb0-e0cafb6169ef", -# # "uuid": "9ddda2c0-ff8c-4ce3-beb0-e0cafb6169ef", -# # "name": "my computation name", -# # "type": "analysis", -# # "citation": [] -# # } -# # ] -# # } -# # ], -# # "bigsmiles": "123456" -# # } - -# # material4 = cript.load_nodes_from_json(json.dumps(material4_dict)) -# # assert json.loads(material.json) == json.loads(material4.json) - - -# def test_json_error(complex_parameter_node): -# parameter = complex_parameter_node -# # Let's break the node by violating the data model -# parameter._json_attrs = replace(parameter._json_attrs, value="abc") -# with pytest.raises(CRIPTNodeSchemaError): -# parameter.validate() -# # Let's break it completely -# parameter._json_attrs = None -# with pytest.raises(CRIPTJsonSerializationError): -# parameter.json - - -# def test_local_search(simple_algorithm_node, complex_parameter_node): -# a = simple_algorithm_node -# # Check if we can use search to find the algorithm node, but specifying node and key -# find_algorithms = a.find_children({"node": "Algorithm", "key": "mc_barostat"}) -# assert find_algorithms == [a] -# # Check if it correctly exclude the algorithm if key is specified to non-existent value -# find_algorithms = a.find_children({"node": "Algorithm", "key": "mc"}) -# assert find_algorithms == [] - -# # Adding 2 separate parameters to test deeper search -# p1 = complex_parameter_node -# p2 = copy.deepcopy(complex_parameter_node) -# p2.key = "damping_time" -# p2.value = 15.0 -# p2.unit = "m" -# a.parameter += [p1, p2] - -# # Test if we can find a specific one of the parameters -# find_parameter = a.find_children({"key": "damping_time"}) -# assert find_parameter == [p2] - -# # Test to find the other parameter -# find_parameter = a.find_children({"key": "update_frequency"}) -# assert find_parameter == [p1] - -# # Test if correctly find no parameter if we are searching for a non-existent parameter -# find_parameter = a.find_children({"key": "update"}) -# assert find_parameter == [] - -# # Test nested search. Here we are looking for any node that has a child node parameter as specified. -# find_algorithms = a.find_children({"parameter": {"key": "damping_time"}}) -# assert find_algorithms == [a] -# # Same as before, but specifying two children that have to be present (AND condition) -# find_algorithms = a.find_children({"parameter": [{"key": "damping_time"}, {"key": "update_frequency"}]}) -# assert find_algorithms == [a] - -# # Test that the main node is correctly excluded if we specify an additionally non-existent parameter -# find_algorithms = a.find_children({"parameter": [{"key": "damping_time"}, {"key": "update_frequency"}, {"foo": "bar"}]}) -# assert find_algorithms == [] - - -# def test_cycles(complex_data_node, simple_computation_node): -# # We create a wrong cycle with parameters here. -# # TODO replace this with nodes that actually can form a cycle -# d = copy.deepcopy(complex_data_node) -# c = copy.deepcopy(simple_computation_node) -# d.computation += [c] -# # Using input and output data guarantees a cycle here. -# c.output_data += [d] -# c.input_data += [d] - -# # # Test the repetition of a citation. -# # # Notice that we do not use a deepcopy here, as we want the citation to be the exact same node. -# # citation = d.citation[0] -# # # c._json_attrs.citation.append(citation) -# # c.citation += [citation] -# # # print(c.get_json(indent=2).json) -# # # c.validate() - -# # Generate json with an implicit cycle -# c.json -# d.json - - -# def test_uid_serial(simple_inventory_node): -# simple_inventory_node.material += simple_inventory_node.material -# json_dict = json.loads(simple_inventory_node.get_json(condense_to_uuid={}).json) -# assert len(json_dict["material"]) == 4 -# assert isinstance(json_dict["material"][2]["uid"], str) -# assert json_dict["material"][2]["uid"].startswith("_:") -# assert len(json_dict["material"][2]["uid"]) == len(get_new_uid()) -# assert isinstance(json_dict["material"][3]["uid"], str) -# assert json_dict["material"][3]["uid"].startswith("_:") -# assert len(json_dict["material"][3]["uid"]) == len(get_new_uid()) -# assert json_dict["material"][3]["uid"] != json_dict["material"][2]["uid"] - - -# def test_invalid_json_load(): -# def raise_node_dict(node_dict): -# node_str = json.dumps(node_dict) -# with pytest.raises(CRIPTJsonNodeError): -# cript.load_nodes_from_json(node_str) - -# node_dict = {"node": "Computation"} -# raise_node_dict(node_dict) -# node_dict = {"node": []} -# raise_node_dict(node_dict) -# node_dict = {"node": ["asdf", "asdf"]} -# raise_node_dict(node_dict) -# node_dict = {"node": [None]} -# raise_node_dict(node_dict) - - -# def test_invalid_project_graphs(simple_project_node, simple_material_node, simple_process_node, simple_property_node, simple_data_node, simple_computation_node, simple_computation_process_node): -# project = copy.deepcopy(simple_project_node) -# process = copy.deepcopy(simple_process_node) -# material = copy.deepcopy(simple_material_node) - -# ingredient = cript.Ingredient(material=material, quantity=[cript.Quantity(key="mass", value=1.23, unit="kg")]) -# process.ingredient += [ingredient] - -# # Add the process to the experiment, but not in inventory or materials -# # Invalid graph -# project.collection[0].experiment[0].process += [process] -# with pytest.raises(CRIPTOrphanedMaterialError): -# project.validate() - -# # First fix add material to inventory -# project.collection[0].inventory += [cript.Inventory("test_inventory", material=[material])] -# project.validate() -# # Reverse this fix -# project.collection[0].inventory = [] -# with pytest.raises(CRIPTOrphanedMaterialError): -# project.validate() - -# # Fix by add to the materials list instead. -# # Using the util helper function for this. -# cript.add_orphaned_nodes_to_project(project, active_experiment=None, max_iteration=10) -# project.validate() - -# # Now add an orphan process to the graph -# process2 = copy.deepcopy(simple_process_node) -# process.prerequisite_process += [process2] -# with pytest.raises(CRIPTOrphanedProcessError): -# project.validate() - -# # Wrong fix it helper node -# dummy_experiment = copy.deepcopy(project.collection[0].experiment[0]) -# with pytest.raises(RuntimeError): -# cript.add_orphaned_nodes_to_project(project, dummy_experiment) -# # Problem still persists -# with pytest.raises(CRIPTOrphanedProcessError): -# project.validate() -# # Fix by using the helper function correctly -# cript.add_orphaned_nodes_to_project(project, project.collection[0].experiment[0], 10) -# project.validate() - -# # We add property to the material, because that adds the opportunity for orphaned data and computation -# property = copy.deepcopy(simple_property_node) -# material.property += [property] -# project.validate() -# # Now add an orphan data -# data = copy.deepcopy(simple_data_node) -# property.data = [data] -# with pytest.raises(CRIPTOrphanedDataError): -# project.validate() -# # Fix with the helper function -# cript.add_orphaned_nodes_to_project(project, project.collection[0].experiment[0], 10) -# project.validate() - -# # Add an orphan Computation -# computation = copy.deepcopy(simple_computation_node) -# property.computation += [computation] -# with pytest.raises(CRIPTOrphanedComputationError): -# project.validate() -# # Fix with the helper function -# cript.add_orphaned_nodes_to_project(project, project.collection[0].experiment[0], 10) -# project.validate() - -# # Add orphan computational process -# comp_proc = copy.deepcopy(simple_computation_process_node) -# data.computation_process += [comp_proc] -# with pytest.raises(CRIPTOrphanedComputationalProcessError): -# while True: -# try: # Do trigger not orphan materials -# project.validate() -# except CRIPTOrphanedMaterialError as exc: -# project._json_attrs.material.append(exc.orphaned_node) -# except CRIPTOrphanedProcessError as exc: -# project.collection[0].experiment[0]._json_attrs.process.append(exc.orphaned_node) -# else: -# break - -# cript.add_orphaned_nodes_to_project(project, project.collection[0].experiment[0], 10) -# project.validate() - - -# def test_expanded_json(complex_project_node): -# """ -# Tests the generation and deserialization of expanded JSON for a complex project node. - -# This test verifies 2 key aspects: -# 1. A complex project node can be serialized into an expanded JSON string, without UUID placeholders. -# 2. The expanded JSON can be deserialized into a node that is equivalent to the original node. -# """ -# project_expanded_json: str = complex_project_node.get_expanded_json() -# deserialized_project_node: cript.Project = cript.load_nodes_from_json(project_expanded_json) - -# # assert the expanded JSON was correctly deserialized to project node -# assert deserialized_project_node == complex_project_node - -# condensed_json: str = complex_project_node.json - -# # since short JSON has UUID it will not be able to deserialize correctly and will -# # raise CRIPTJsonDeserializationError -# with pytest.raises(cript.nodes.exceptions.CRIPTJsonDeserializationError): -# cript.load_nodes_from_json(condensed_json) + +def test_removing_nodes(simple_algorithm_node, complex_parameter_node, simple_algorithm_dict): + a = simple_algorithm_node + p = complex_parameter_node + a.parameter += [p] + assert strip_uid_from_dict(json.loads(a.json)) != simple_algorithm_dict + a.remove_child(p) + assert strip_uid_from_dict(json.loads(a.json)) == simple_algorithm_dict + + +def test_uid_deserialization(simple_algorithm_node, complex_parameter_node, simple_algorithm_dict): + material = cript.Material(name="my material", bigsmiles="{[][$]CC[$][]}") + + computation = cript.Computation(name="my computation name", type="analysis") + property1 = cript.Property("modulus_shear", "value", 5.0, "GPa", computation=[computation]) + property2 = cript.Property("modulus_loss", "value", 5.0, "GPa", computation=[computation]) + material.property = [property1, property2] + + material2 = cript.load_nodes_from_json(material.json) + assert json.loads(material.json) == json.loads(material2.json) + + material3_dict = { + "node": ["Material"], + "uid": "_:f6d56fdc-9df7-49a1-a843-cf92681932ad", + "uuid": "f6d56fdc-9df7-49a1-a843-cf92681932ad", + "name": "my material", + "property": [ + { + "node": ["Property"], + "uid": "_:82e7270e-9f35-4b35-80a2-faa6e7f670be", + "uuid": "82e7270e-9f35-4b35-80a2-faa6e7f670be", + "key": "modulus_shear", + "type": "value", + "value": 5.0, + "unit": "GPa", + "computation": [{"uid": "_:9ddda2c0-ff8c-4ce3-beb0-e0cafb6169ef"}], + }, + { + "node": ["Property"], + "uid": "_:fc4dfa5e-742c-4d0b-bb66-2185461f4582", + "uuid": "fc4dfa5e-742c-4d0b-bb66-2185461f4582", + "key": "modulus_loss", + "type": "value", + "value": 5.0, + "unit": "GPa", + "computation": [ + { + "uid": "_:9ddda2c0-ff8c-4ce3-beb0-e0cafb6169ef", + } + ], + }, + ], + "bigsmiles": "123456", + } + + with pytest.raises(cript.nodes.exceptions.CRIPTDeserializationUIDError): + cript.load_nodes_from_json(json.dumps(material3_dict)) + + # TODO convince beartype to allow _ProxyUID as well + # material4_dict = { + # "node": [ + # "Material" + # ], + # "uid": "_:f6d56fdc-9df7-49a1-a843-cf92681932ad", + # "uuid": "f6d56fdc-9df7-49a1-a843-cf92681932ad", + # "name": "my material", + # "property": [ + # { + # "node": [ + # "Property" + # ], + # "uid": "_:82e7270e-9f35-4b35-80a2-faa6e7f670be", + # "uuid": "82e7270e-9f35-4b35-80a2-faa6e7f670be", + # "key": "modulus_shear", + # "type": "value", + # "value": 5.0, + # "unit": "GPa", + # "computation": [ + # { + # "node": [ + # "Computation" + # ], + # "uid": "_:9ddda2c0-ff8c-4ce3-beb0-e0cafb6169ef" + # } + # ] + # }, + # { + # "node": [ + # "Property" + # ], + # "uid": "_:fc4dfa5e-742c-4d0b-bb66-2185461f4582", + # "uuid": "fc4dfa5e-742c-4d0b-bb66-2185461f4582", + # "key": "modulus_loss", + # "type": "value", + # "value": 5.0, + # "unit": "GPa", + # "computation": [ + # { + # "node": [ + # "Computation" + # ], + # "uid": "_:9ddda2c0-ff8c-4ce3-beb0-e0cafb6169ef", + # "uuid": "9ddda2c0-ff8c-4ce3-beb0-e0cafb6169ef", + # "name": "my computation name", + # "type": "analysis", + # "citation": [] + # } + # ] + # } + # ], + # "bigsmiles": "123456" + # } + + # material4 = cript.load_nodes_from_json(json.dumps(material4_dict)) + # assert json.loads(material.json) == json.loads(material4.json) + + +def test_json_error(complex_parameter_node): + parameter = complex_parameter_node + # Let's break the node by violating the data model + parameter._json_attrs = replace(parameter._json_attrs, value="abc") + with pytest.raises(CRIPTNodeSchemaError): + parameter.validate() + # Let's break it completely + parameter._json_attrs = None + with pytest.raises(CRIPTJsonSerializationError): + parameter.json + + +def test_local_search(simple_algorithm_node, complex_parameter_node): + a = simple_algorithm_node + # Check if we can use search to find the algorithm node, but specifying node and key + find_algorithms = a.find_children({"node": "Algorithm", "key": "mc_barostat"}) + assert find_algorithms == [a] + # Check if it correctly exclude the algorithm if key is specified to non-existent value + find_algorithms = a.find_children({"node": "Algorithm", "key": "mc"}) + assert find_algorithms == [] + + # Adding 2 separate parameters to test deeper search + p1 = complex_parameter_node + p2 = copy.deepcopy(complex_parameter_node) + p2.key = "damping_time" + p2.value = 15.0 + p2.unit = "m" + a.parameter += [p1, p2] + + # Test if we can find a specific one of the parameters + find_parameter = a.find_children({"key": "damping_time"}) + assert find_parameter == [p2] + + # Test to find the other parameter + find_parameter = a.find_children({"key": "update_frequency"}) + assert find_parameter == [p1] + + # Test if correctly find no parameter if we are searching for a non-existent parameter + find_parameter = a.find_children({"key": "update"}) + assert find_parameter == [] + + # Test nested search. Here we are looking for any node that has a child node parameter as specified. + find_algorithms = a.find_children({"parameter": {"key": "damping_time"}}) + assert find_algorithms == [a] + # Same as before, but specifying two children that have to be present (AND condition) + find_algorithms = a.find_children({"parameter": [{"key": "damping_time"}, {"key": "update_frequency"}]}) + assert find_algorithms == [a] + + # Test that the main node is correctly excluded if we specify an additionally non-existent parameter + find_algorithms = a.find_children({"parameter": [{"key": "damping_time"}, {"key": "update_frequency"}, {"foo": "bar"}]}) + assert find_algorithms == [] + + +def test_cycles(complex_data_node, simple_computation_node): + # We create a wrong cycle with parameters here. + # TODO replace this with nodes that actually can form a cycle + d = copy.deepcopy(complex_data_node) + c = copy.deepcopy(simple_computation_node) + d.computation += [c] + # Using input and output data guarantees a cycle here. + c.output_data += [d] + c.input_data += [d] + + # # Test the repetition of a citation. + # # Notice that we do not use a deepcopy here, as we want the citation to be the exact same node. + # citation = d.citation[0] + # # c._json_attrs.citation.append(citation) + # c.citation += [citation] + # # print(c.get_json(indent=2).json) + # # c.validate() + + # Generate json with an implicit cycle + c.json + d.json + + +def test_uid_serial(simple_inventory_node): + simple_inventory_node.material += simple_inventory_node.material + json_dict = json.loads(simple_inventory_node.get_json(condense_to_uuid={}).json) + assert len(json_dict["material"]) == 4 + assert isinstance(json_dict["material"][2]["uid"], str) + assert json_dict["material"][2]["uid"].startswith("_:") + assert len(json_dict["material"][2]["uid"]) == len(get_new_uid()) + assert isinstance(json_dict["material"][3]["uid"], str) + assert json_dict["material"][3]["uid"].startswith("_:") + assert len(json_dict["material"][3]["uid"]) == len(get_new_uid()) + assert json_dict["material"][3]["uid"] != json_dict["material"][2]["uid"] + + +def test_invalid_json_load(): + def raise_node_dict(node_dict): + node_str = json.dumps(node_dict) + with pytest.raises(CRIPTJsonNodeError): + cript.load_nodes_from_json(node_str) + + node_dict = {"node": "Computation"} + raise_node_dict(node_dict) + node_dict = {"node": []} + raise_node_dict(node_dict) + node_dict = {"node": ["asdf", "asdf"]} + raise_node_dict(node_dict) + node_dict = {"node": [None]} + raise_node_dict(node_dict) + + +def test_invalid_project_graphs(simple_project_node, simple_material_node, simple_process_node, simple_property_node, simple_data_node, simple_computation_node, simple_computation_process_node): + project = copy.deepcopy(simple_project_node) + process = copy.deepcopy(simple_process_node) + material = copy.deepcopy(simple_material_node) + + ingredient = cript.Ingredient(material=material, quantity=[cript.Quantity(key="mass", value=1.23, unit="kg")]) + process.ingredient += [ingredient] + + # Add the process to the experiment, but not in inventory or materials + # Invalid graph + project.collection[0].experiment[0].process += [process] + with pytest.raises(CRIPTOrphanedMaterialError): + project.validate() + + # First fix add material to inventory + project.collection[0].inventory += [cript.Inventory("test_inventory", material=[material])] + project.validate() + # Reverse this fix + project.collection[0].inventory = [] + with pytest.raises(CRIPTOrphanedMaterialError): + project.validate() + + # Fix by add to the materials list instead. + # Using the util helper function for this. + cript.add_orphaned_nodes_to_project(project, active_experiment=None, max_iteration=10) + project.validate() + + # Now add an orphan process to the graph + process2 = copy.deepcopy(simple_process_node) + process.prerequisite_process += [process2] + with pytest.raises(CRIPTOrphanedProcessError): + project.validate() + + # Wrong fix it helper node + dummy_experiment = copy.deepcopy(project.collection[0].experiment[0]) + with pytest.raises(RuntimeError): + cript.add_orphaned_nodes_to_project(project, dummy_experiment) + # Problem still persists + with pytest.raises(CRIPTOrphanedProcessError): + project.validate() + # Fix by using the helper function correctly + cript.add_orphaned_nodes_to_project(project, project.collection[0].experiment[0], 10) + project.validate() + + # We add property to the material, because that adds the opportunity for orphaned data and computation + property = copy.deepcopy(simple_property_node) + material.property += [property] + project.validate() + # Now add an orphan data + data = copy.deepcopy(simple_data_node) + property.data = [data] + with pytest.raises(CRIPTOrphanedDataError): + project.validate() + # Fix with the helper function + cript.add_orphaned_nodes_to_project(project, project.collection[0].experiment[0], 10) + project.validate() + + # Add an orphan Computation + computation = copy.deepcopy(simple_computation_node) + property.computation += [computation] + with pytest.raises(CRIPTOrphanedComputationError): + project.validate() + # Fix with the helper function + cript.add_orphaned_nodes_to_project(project, project.collection[0].experiment[0], 10) + project.validate() + + # Add orphan computational process + comp_proc = copy.deepcopy(simple_computation_process_node) + data.computation_process += [comp_proc] + with pytest.raises(CRIPTOrphanedComputationalProcessError): + while True: + try: # Do trigger not orphan materials + project.validate() + except CRIPTOrphanedMaterialError as exc: + project._json_attrs.material.append(exc.orphaned_node) + except CRIPTOrphanedProcessError as exc: + project.collection[0].experiment[0]._json_attrs.process.append(exc.orphaned_node) + else: + break + + cript.add_orphaned_nodes_to_project(project, project.collection[0].experiment[0], 10) + project.validate() + + +def test_expanded_json(complex_project_node): + """ + Tests the generation and deserialization of expanded JSON for a complex project node. + + This test verifies 2 key aspects: + 1. A complex project node can be serialized into an expanded JSON string, without UUID placeholders. + 2. The expanded JSON can be deserialized into a node that is equivalent to the original node. + """ + project_expanded_json: str = complex_project_node.get_expanded_json() + deserialized_project_node: cript.Project = cript.load_nodes_from_json(project_expanded_json) + + # assert the expanded JSON was correctly deserialized to project node + assert deserialized_project_node == complex_project_node + + condensed_json: str = complex_project_node.json + + # since short JSON has UUID it will not be able to deserialize correctly and will + # raise CRIPTJsonDeserializationError + with pytest.raises(cript.nodes.exceptions.CRIPTJsonDeserializationError): + cript.load_nodes_from_json(condensed_json) def test_uuid_cache_override(complex_project_node): From 55db9b9f4b4283eca7a0b9578f231929deca2adb Mon Sep 17 00:00:00 2001 From: Ludwig Schneider Date: Tue, 12 Mar 2024 11:18:06 -0500 Subject: [PATCH 3/5] fixing some issues --- .trunk/configs/.cspell.json | 3 +- src/cript/nodes/core.py | 4 ++ src/cript/nodes/exceptions.py | 21 -------- src/cript/nodes/primary_nodes/project.py | 6 +-- src/cript/nodes/util/__init__.py | 49 ++--------------- src/cript/nodes/util/core.py | 69 ++++++++++++++++++++++++ src/cript/nodes/util/json.py | 4 +- src/cript/nodes/uuid_base.py | 14 ++--- tests/api/test_search.py | 32 +++++------ tests/test_node_util.py | 18 +++++++ 10 files changed, 121 insertions(+), 99 deletions(-) create mode 100644 src/cript/nodes/util/core.py diff --git a/.trunk/configs/.cspell.json b/.trunk/configs/.cspell.json index a0dfac46a..ccdbc4d40 100644 --- a/.trunk/configs/.cspell.json +++ b/.trunk/configs/.cspell.json @@ -122,6 +122,7 @@ "Doctest", "Doctests", "linenums", - "XLYOFNOQVPJJNP" + "XLYOFNOQVPJJNP", + "CRIPTUUID" ] } diff --git a/src/cript/nodes/core.py b/src/cript/nodes/core.py index 3f43fedd4..6902d505e 100644 --- a/src/cript/nodes/core.py +++ b/src/cript/nodes/core.py @@ -213,6 +213,8 @@ def _from_json(cls, json_dict: dict): return node def __deepcopy__(self, memo): + from cript.nodes.util.core import get_uuid_from_uid + # Ideally I would call `asdict`, but that is not allowed inside a deepcopy chain. # Making a manual transform into a dictionary here. arguments = {} @@ -224,6 +226,8 @@ def __deepcopy__(self, memo): # a new uid will prompt the creation of a new matching uuid. uid = get_new_uid() arguments["uid"] = uid + if "uuid" in arguments: + arguments["uuid"] = get_uuid_from_uid(uid) # Create node and init constructor attributes node = self.__class__(**arguments) diff --git a/src/cript/nodes/exceptions.py b/src/cript/nodes/exceptions.py index 656298c6b..dfda18352 100644 --- a/src/cript/nodes/exceptions.py +++ b/src/cript/nodes/exceptions.py @@ -348,27 +348,6 @@ def __str__(self) -> str: return ret_string -def get_orphaned_experiment_exception(orphaned_node): - """ - Return the correct specific Exception based in the orphaned node type for nodes not correctly listed in experiment. - """ - from cript.nodes.primary_nodes.computation import Computation - from cript.nodes.primary_nodes.computation_process import ComputationProcess - from cript.nodes.primary_nodes.data import Data - from cript.nodes.primary_nodes.process import Process - - if isinstance(orphaned_node, Data): - return CRIPTOrphanedDataError(orphaned_node) - if isinstance(orphaned_node, Process): - return CRIPTOrphanedProcessError(orphaned_node) - if isinstance(orphaned_node, Computation): - return CRIPTOrphanedComputationError(orphaned_node) - if isinstance(orphaned_node, ComputationProcess): - return CRIPTOrphanedComputationalProcessError(orphaned_node) - # Base case raise the parent exception. TODO add bug warning. - return CRIPTOrphanedExperimentError(orphaned_node) - - class CRIPTOrphanedDataError(CRIPTOrphanedExperimentError): """ ## Definition diff --git a/src/cript/nodes/primary_nodes/project.py b/src/cript/nodes/primary_nodes/project.py index 48e182463..c644221bc 100644 --- a/src/cript/nodes/primary_nodes/project.py +++ b/src/cript/nodes/primary_nodes/project.py @@ -105,10 +105,8 @@ def __init__(self, name: str, collection: Optional[List[Collection]] = None, mat self._update_json_attrs_if_valid(new_json_attrs) def validate(self, api=None, is_patch=False, force_validation: bool = False): - from cript.nodes.exceptions import ( - CRIPTOrphanedMaterialError, - get_orphaned_experiment_exception, - ) + from cript.nodes.exceptions import CRIPTOrphanedMaterialError + from cript.nodes.util.core import get_orphaned_experiment_exception # First validate like other nodes super().validate(api=api, is_patch=is_patch, force_validation=force_validation) diff --git a/src/cript/nodes/util/__init__.py b/src/cript/nodes/util/__init__.py index 635151b72..4b7e58be9 100644 --- a/src/cript/nodes/util/__init__.py +++ b/src/cript/nodes/util/__init__.py @@ -1,48 +1,9 @@ -from cript.nodes.exceptions import ( - CRIPTOrphanedComputationalProcessError, - CRIPTOrphanedComputationError, - CRIPTOrphanedDataError, - CRIPTOrphanedMaterialError, - CRIPTOrphanedProcessError, -) -from cript.nodes.primary_nodes.experiment import Experiment -from cript.nodes.primary_nodes.project import Project - # trunk-ignore-begin(ruff/F401) +from .core import ( + add_orphaned_nodes_to_project, + get_orphaned_experiment_exception, + get_uuid_from_uid, +) from .json import NodeEncoder, load_nodes_from_json # trunk-ignore-end(ruff/F401) - - -def add_orphaned_nodes_to_project(project: Project, active_experiment: Experiment, max_iteration: int = -1): - """ - Helper function that adds all orphaned material nodes of the project graph to the - `project.materials` attribute. - Material additions only is permissible with `active_experiment is None`. - This function also adds all orphaned data, process, computation and computational process nodes - of the project graph to the `active_experiment`. - This functions call `project.validate` and might raise Exceptions from there. - """ - if active_experiment is not None and active_experiment not in project.find_children({"node": ["Experiment"]}): - raise RuntimeError(f"The provided active experiment {active_experiment} is not part of the project graph. Choose an active experiment that is part of a collection of this project.") - - counter = 0 - while True: - if counter > max_iteration >= 0: - break # Emergency stop - try: - project.validate() - except CRIPTOrphanedMaterialError as exc: - # because calling the setter calls `validate` we have to force add the material. - project._json_attrs.material.append(exc.orphaned_node) - except CRIPTOrphanedDataError as exc: - active_experiment.data += [exc.orphaned_node] - except CRIPTOrphanedProcessError as exc: - active_experiment.process += [exc.orphaned_node] - except CRIPTOrphanedComputationError as exc: - active_experiment.computation += [exc.orphaned_node] - except CRIPTOrphanedComputationalProcessError as exc: - active_experiment.computation_process += [exc.orphaned_node] - else: - break - counter += 1 diff --git a/src/cript/nodes/util/core.py b/src/cript/nodes/util/core.py new file mode 100644 index 000000000..938ec7201 --- /dev/null +++ b/src/cript/nodes/util/core.py @@ -0,0 +1,69 @@ +import uuid + +from cript.nodes.exceptions import ( + CRIPTOrphanedComputationalProcessError, + CRIPTOrphanedComputationError, + CRIPTOrphanedDataError, + CRIPTOrphanedExperimentError, + CRIPTOrphanedMaterialError, + CRIPTOrphanedProcessError, +) + + +def get_uuid_from_uid(uid): + return str(uuid.UUID(uid[2:])) + + +def add_orphaned_nodes_to_project(project, active_experiment, max_iteration: int = -1): + """ + Helper function that adds all orphaned material nodes of the project graph to the + `project.materials` attribute. + Material additions only is permissible with `active_experiment is None`. + This function also adds all orphaned data, process, computation and computational process nodes + of the project graph to the `active_experiment`. + This functions call `project.validate` and might raise Exceptions from there. + """ + if active_experiment is not None and active_experiment not in project.find_children({"node": ["Experiment"]}): + raise RuntimeError(f"The provided active experiment {active_experiment} is not part of the project graph. Choose an active experiment that is part of a collection of this project.") + + counter = 0 + while True: + if counter > max_iteration >= 0: + break # Emergency stop + try: + project.validate() + except CRIPTOrphanedMaterialError as exc: + # because calling the setter calls `validate` we have to force add the material. + project._json_attrs.material.append(exc.orphaned_node) + except CRIPTOrphanedDataError as exc: + active_experiment.data += [exc.orphaned_node] + except CRIPTOrphanedProcessError as exc: + active_experiment.process += [exc.orphaned_node] + except CRIPTOrphanedComputationError as exc: + active_experiment.computation += [exc.orphaned_node] + except CRIPTOrphanedComputationalProcessError as exc: + active_experiment.computation_process += [exc.orphaned_node] + else: + break + counter += 1 + + +def get_orphaned_experiment_exception(orphaned_node): + """ + Return the correct specific Exception based in the orphaned node type for nodes not correctly listed in experiment. + """ + from cript.nodes.primary_nodes.computation import Computation + from cript.nodes.primary_nodes.computation_process import ComputationProcess + from cript.nodes.primary_nodes.data import Data + from cript.nodes.primary_nodes.process import Process + + if isinstance(orphaned_node, Data): + return CRIPTOrphanedDataError(orphaned_node) + if isinstance(orphaned_node, Process): + return CRIPTOrphanedProcessError(orphaned_node) + if isinstance(orphaned_node, Computation): + return CRIPTOrphanedComputationError(orphaned_node) + if isinstance(orphaned_node, ComputationProcess): + return CRIPTOrphanedComputationalProcessError(orphaned_node) + # Base case raise the parent exception. TODO add bug warning. + return CRIPTOrphanedExperimentError(orphaned_node) diff --git a/src/cript/nodes/util/json.py b/src/cript/nodes/util/json.py index ce013a794..274c1df1c 100644 --- a/src/cript/nodes/util/json.py +++ b/src/cript/nodes/util/json.py @@ -354,13 +354,13 @@ def load_nodes_from_json(nodes_json: Union[str, Dict], _use_uuid_cache: Optional # Store previous UUIDBaseNode Cache state previous_uuid_cache = UUIDBaseNode._uuid_cache - if not (_use_uuid_cache is None): # If requested use a custom cache. + if _use_uuid_cache is not None: # If requested use a custom cache. UUIDBaseNode._uuid_cache = _use_uuid_cache try: loaded_nodes = json.loads(nodes_json, object_hook=node_json_hook) finally: - # Definitively restore the old cachse state + # Definitively restore the old cache state UUIDBaseNode._uuid_cache = previous_uuid_cache if _use_uuid_cache is not None: diff --git a/src/cript/nodes/uuid_base.py b/src/cript/nodes/uuid_base.py index 7841cb15d..87fac9043 100644 --- a/src/cript/nodes/uuid_base.py +++ b/src/cript/nodes/uuid_base.py @@ -7,10 +7,6 @@ from cript.nodes.exceptions import CRIPTUUIDException -def get_uuid_from_uid(uid): - return str(uuid.UUID(uid[2:])) - - class UUIDBaseNode(BaseNode, ABC): """ Base node that handles UUIDs and URLs. @@ -33,7 +29,7 @@ class JsonAttributes(BaseNode.JsonAttributes): _json_attrs: JsonAttributes = JsonAttributes() - def __new__(cls, **kwargs): + def __new__(cls, *args, **kwargs): uuid: Optional[str] = kwargs.get("uuid") if uuid and uuid in UUIDBaseNode._uuid_cache: existing_node_to_overwrite = UUIDBaseNode._uuid_cache[uuid] @@ -44,6 +40,8 @@ def __new__(cls, **kwargs): return new_uuid_node def __init__(self, **kwargs): + from cript.nodes.util.core import get_uuid_from_uid + # initialize Base class with node super().__init__(**kwargs) # Respect uuid if passed as argument, otherwise construct uuid from uid @@ -63,12 +61,6 @@ def url(self): api = _get_global_cached_api() return f"{api.host}/{api.api_prefix}/{api.api_version}/{self.uuid}" - def __deepcopy__(self, memo): - node = super().__deepcopy__(memo) - node._json_attrs = replace(node._json_attrs, uuid=get_uuid_from_uid(node.uid)) - UUIDBaseNode._uuid_cache[str(node.uuid)] = node - return node - @property def updated_by(self): return self._json_attrs.updated_by diff --git a/tests/api/test_search.py b/tests/api/test_search.py index 0b88da769..bffc397fc 100644 --- a/tests/api/test_search.py +++ b/tests/api/test_search.py @@ -125,19 +125,19 @@ def test_empty_paginator(cript_api: cript.API) -> None: next(uuid_paginator) -@pytest.mark.skipif(not HAS_INTEGRATION_TESTS_ENABLED, reason="requires a real cript_api_token") -def test_api_search_bigsmiles(cript_api: cript.API) -> None: - """ - tests search method with bigsmiles SearchMode to see if we just get at least one match - searches for material - "{[][<]C(C)C(=O)O[>][<]}{[$][$]CCC(C)C[$],[$]CC(C(C)C)[$],[$]CC(C)(CC)[$][]}" - - another good example can be "{[][$]CC(C)(C(=O)OCCCC)[$][]}" - """ - bigsmiles_search_value = "{[][<]C(C)C(=O)O[>][<]}{[$][$]CCC(C)C[$],[$]CC(C(C)C)[$],[$]CC(C)(CC)[$][]}" - - bigsmiles_paginator = cript_api.search(node_type=cript.Material, search_mode=cript.SearchModes.BIGSMILES, value_to_search=bigsmiles_search_value) - - assert isinstance(bigsmiles_paginator, Paginator) - bigsmiles_list = list(bigsmiles_paginator) - assert len(bigsmiles_list) >= 1 +# @pytest.mark.skipif(not HAS_INTEGRATION_TESTS_ENABLED, reason="requires a real cript_api_token") +# def test_api_search_bigsmiles(cript_api: cript.API) -> None: +# """ +# tests search method with bigsmiles SearchMode to see if we just get at least one match +# searches for material +# "{[][<]C(C)C(=O)O[>][<]}{[$][$]CCC(C)C[$],[$]CC(C(C)C)[$],[$]CC(C)(CC)[$][]}" + +# another good example can be "{[][$]CC(C)(C(=O)OCCCC)[$][]}" +# """ +# bigsmiles_search_value = "{[][<]C(C)C(=O)O[>][<]}{[$][$]CCC(C)C[$],[$]CC(C(C)C)[$],[$]CC(C)(CC)[$][]}" + +# bigsmiles_paginator = cript_api.search(node_type=cript.Material, search_mode=cript.SearchModes.BIGSMILES, value_to_search=bigsmiles_search_value) + +# assert isinstance(bigsmiles_paginator, Paginator) +# bigsmiles_list = list(bigsmiles_paginator) +# assert len(bigsmiles_list) >= 1 diff --git a/tests/test_node_util.py b/tests/test_node_util.py index aef4fa695..806d4799b 100644 --- a/tests/test_node_util.py +++ b/tests/test_node_util.py @@ -1,4 +1,22 @@ +import copy +import json +from dataclasses import replace + +import pytest + import cript +from cript.nodes.core import get_new_uid +from cript.nodes.exceptions import ( + CRIPTJsonNodeError, + CRIPTJsonSerializationError, + CRIPTNodeSchemaError, + CRIPTOrphanedComputationalProcessError, + CRIPTOrphanedComputationError, + CRIPTOrphanedDataError, + CRIPTOrphanedMaterialError, + CRIPTOrphanedProcessError, +) +from tests.utils.util import strip_uid_from_dict def test_removing_nodes(simple_algorithm_node, complex_parameter_node, simple_algorithm_dict): From d5d3c72d73d34c8a7dfb3f0199a2993a85638216 Mon Sep 17 00:00:00 2001 From: Ludwig Schneider Date: Tue, 12 Mar 2024 11:49:25 -0500 Subject: [PATCH 4/5] reanble broken test --- tests/api/test_search.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/tests/api/test_search.py b/tests/api/test_search.py index bffc397fc..0b88da769 100644 --- a/tests/api/test_search.py +++ b/tests/api/test_search.py @@ -125,19 +125,19 @@ def test_empty_paginator(cript_api: cript.API) -> None: next(uuid_paginator) -# @pytest.mark.skipif(not HAS_INTEGRATION_TESTS_ENABLED, reason="requires a real cript_api_token") -# def test_api_search_bigsmiles(cript_api: cript.API) -> None: -# """ -# tests search method with bigsmiles SearchMode to see if we just get at least one match -# searches for material -# "{[][<]C(C)C(=O)O[>][<]}{[$][$]CCC(C)C[$],[$]CC(C(C)C)[$],[$]CC(C)(CC)[$][]}" - -# another good example can be "{[][$]CC(C)(C(=O)OCCCC)[$][]}" -# """ -# bigsmiles_search_value = "{[][<]C(C)C(=O)O[>][<]}{[$][$]CCC(C)C[$],[$]CC(C(C)C)[$],[$]CC(C)(CC)[$][]}" - -# bigsmiles_paginator = cript_api.search(node_type=cript.Material, search_mode=cript.SearchModes.BIGSMILES, value_to_search=bigsmiles_search_value) - -# assert isinstance(bigsmiles_paginator, Paginator) -# bigsmiles_list = list(bigsmiles_paginator) -# assert len(bigsmiles_list) >= 1 +@pytest.mark.skipif(not HAS_INTEGRATION_TESTS_ENABLED, reason="requires a real cript_api_token") +def test_api_search_bigsmiles(cript_api: cript.API) -> None: + """ + tests search method with bigsmiles SearchMode to see if we just get at least one match + searches for material + "{[][<]C(C)C(=O)O[>][<]}{[$][$]CCC(C)C[$],[$]CC(C(C)C)[$],[$]CC(C)(CC)[$][]}" + + another good example can be "{[][$]CC(C)(C(=O)OCCCC)[$][]}" + """ + bigsmiles_search_value = "{[][<]C(C)C(=O)O[>][<]}{[$][$]CCC(C)C[$],[$]CC(C(C)C)[$],[$]CC(C)(CC)[$][]}" + + bigsmiles_paginator = cript_api.search(node_type=cript.Material, search_mode=cript.SearchModes.BIGSMILES, value_to_search=bigsmiles_search_value) + + assert isinstance(bigsmiles_paginator, Paginator) + bigsmiles_list = list(bigsmiles_paginator) + assert len(bigsmiles_list) >= 1 From 54392c63ef4f58e393658ad4af5e2a10482ce790 Mon Sep 17 00:00:00 2001 From: Ludwig Schneider Date: Tue, 12 Mar 2024 14:59:43 -0500 Subject: [PATCH 5/5] remove unused code --- src/cript/nodes/core.py | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/src/cript/nodes/core.py b/src/cript/nodes/core.py index 6902d505e..c8daff22d 100644 --- a/src/cript/nodes/core.py +++ b/src/cript/nodes/core.py @@ -179,20 +179,13 @@ def _from_json(cls, json_dict: dict): if field_name not in arguments: arguments[field_name] = getattr(default_dataclass, field_name) - # If a node with this UUID already exists, we don't create a new node. - # Instead we use the existing node from the cache and just update it. - from cript.nodes.uuid_base import UUIDBaseNode - - if "uuid" in json_dict and json_dict["uuid"] in UUIDBaseNode._uuid_cache: - node = UUIDBaseNode._uuid_cache[json_dict["uuid"]] - else: # Create a new node - try: - node = cls(**arguments) - # TODO we should not catch all exceptions if we are handling them, and instead let it fail - # to create a good error message that points to the correct place that it failed to make debugging easier - except Exception as exc: - print(cls, arguments) - raise exc + try: + node = cls(**arguments) + # TODO we should not catch all exceptions if we are handling them, and instead let it fail + # to create a good error message that points to the correct place that it failed to make debugging easier + except Exception as exc: + print(cls, arguments) + raise exc attrs = cls.JsonAttributes(**arguments)