def test_process_id_link(): """Test that a process run can house a LinkByUID object, and that it survives serde.""" uid = str(uuid4()) proc_link = LinkByUID(scope='id', id=uid) mat_run = MaterialRun("Another cake", process=proc_link) copy_material = loads(dumps(mat_run)) assert dumps(copy_material) == dumps(mat_run)
def _build_child_objects(cls, data: dict, session: Session = None): """ Build the condition and parameter templates and bounds. Parameters ---------- data: dict A serialized material template. session: Session, optional Citrine session used to connect to the database. Returns ------- None The serialized process template is modified so that its conditions are now a list of object pairs of the form [ConditionTemplate, :py:class:`BaseBounds <taurus.entity.bounds.base_bounds.BaseBounds>`], and the parameters are [ParameterTemplate, :py:class:`BaseBounds <taurus.entity.bounds.base_bounds.BaseBounds>`]. """ if 'conditions' in data and len(data['conditions']) != 0: data['conditions'] = [[ ConditionTemplate.build(cond[0].as_dict()), loads(dumps(cond[1])) ] for cond in data['conditions']] if 'parameters' in data and len(data['parameters']) != 0: data['parameters'] = [[ ParameterTemplate.build(param[0].as_dict()), loads(dumps(param[1])) ] for param in data['parameters']]
def test_material_run(): """ Test the ability to create a MaterialRun that is linked to a MaterialSpec. Make sure all enumerated values are respected, and check consistency after serializing and deserializing. """ # Define a property, and make sure that an inappropriate value for origin throws ValueError with pytest.raises(ValueError): prop = Property(name="A property", origin="bad origin", value=NominalReal(17, units='')) # Create a MaterialSpec with a property prop = Property(name="A property", origin="specified", value=NominalReal(17, units='')) mat_spec = MaterialSpec(name="a specification for a material", properties=PropertyAndConditions(prop), notes="Funny lookin'") # Make sure that when property is serialized, origin (an enumeration) is serialized as a string copy_prop = json.loads(dumps(mat_spec)) copy_origin = copy_prop[0][0]["properties"][0]['property']['origin'] assert isinstance(copy_origin, str) # Create a MaterialRun, and make sure an inappropriate value for sample_type throws ValueError with pytest.raises(ValueError): mat = MaterialRun(spec=mat_spec, sample_type="imaginary") mat = MaterialRun(spec=mat_spec, sample_type="virtual") # ensure that serialization does not change the MaterialRun copy = loads(dumps(mat)) assert dumps(copy) == dumps(mat), \ "Material run is modified by serialization or deserialization"
def test_cake(): """Create cake, serialize, deserialize.""" cake = make_cake() assert dumps(loads(dumps(cake)), indent=2) == dumps(cake, indent=2) queue = [cake] seen = set() while queue: obj = queue.pop() if obj in seen: continue seen.add(obj) if isinstance(obj, MaterialSpec): if obj.process is not None: queue.append(obj.process) assert obj.process.output_material == obj elif isinstance(obj, MaterialRun): if obj.process is not None: queue.append(obj.process) assert obj.process.output_material == obj if obj.measurements: queue.extend(obj.measurements) for msr in obj.measurements: assert msr.material == obj if obj.spec is not None: queue.append(obj.spec) if obj.process is not None: assert obj.spec.process == obj.process.spec elif isinstance(obj, ProcessRun): if obj.ingredients: queue.extend(obj.ingredients) if obj.output_material is not None: queue.append(obj.output_material) assert obj.output_material.process == obj if obj.spec is not None: assert obj.spec.output_material == obj.output_material.spec elif isinstance(obj, ProcessSpec): if obj.ingredients: queue.extend(obj.ingredients) if obj.output_material is not None: queue.append(obj.output_material) assert obj.output_material.process == obj elif isinstance(obj, MeasurementSpec): pass # Doesn't link elif isinstance(obj, MeasurementRun): if obj.spec: queue.append(obj.spec) elif isinstance(obj, IngredientSpec): if obj.material: queue.append(obj.material) elif isinstance(obj, IngredientRun): if obj.spec: queue.append(obj.spec) if obj.material and isinstance(obj.material, MaterialRun): assert obj.spec.material == obj.material.spec if obj.material: queue.append(obj.material)
def test_unexpected_serialization(): """Trying to serialize an unexpected class should throw a TypeError.""" class DummyClass: def __init__(self, foo): self.foo = foo with pytest.raises(TypeError): dumps(ProcessRun("A process", notes=DummyClass("something")))
def test_process_spec(): """Tests that the Process Spec/Run connection persists when serializing.""" # Create the ProcessSpec condition1 = Condition(name="a condition on the process in general") spec = ProcessSpec(conditions=condition1) # Create the ProcessRun with a link to the ProcessSpec from above condition2 = Condition( name="a condition on this process run in particular") process = ProcessRun(conditions=condition2, spec=spec) copy_process = loads(dumps(process)) assert dumps(copy_process.spec) == dumps(spec), \ "Process spec should be preserved through serialization"
def test_material_spec(): """Test that Process/Material Spec link survives serialization.""" # Create a ProcessSpec proc_spec = ProcessSpec(name="a process spec", tags=["tag1", "tag2"]) # Create MaterialSpec without a ProcessSpec prop = Property( name="The material is a solid", value=DiscreteCategorical(probabilities="solid") ) mat_spec = MaterialSpec(name="a material spec", properties=PropertyAndConditions(prop)) assert mat_spec.process is None, \ "MaterialSpec should be initialized with no ProcessSpec, by default" # Assign a ProcessSpec to mat_spec, first ensuring that the type is enforced with pytest.raises(TypeError): mat_spec.process = 17 mat_spec.process = proc_spec # Assert circular links assert dumps(proc_spec.output_material.process) == dumps(proc_spec), \ "ProcessSpec should link to MaterialSpec that links back to itself" assert dumps(mat_spec.process.output_material) == dumps(mat_spec), \ "MaterialSpec should link to ProcessSpec that links back to itself" # Make copies of both specs mat_spec_copy = loads(dumps(mat_spec)) proc_spec_copy = loads(dumps(proc_spec)) assert proc_spec_copy.output_material is None, \ "Serialization should break link from ProcessSpec to MaterialSpec" assert dumps(mat_spec_copy.process) == dumps(proc_spec), \ "Serialization should preserve link from MaterialSpec to ProcessSpec"
def test_material_soft_link(): """Test that a measurement run can link to a material run, and that it survives serde.""" dye = MaterialRun("rhodamine", file_links=FileLink(filename='a.csv', url='/a/path')) assert dye.measurements == [], "default value of .measurements should be an empty list" # The .measurements member should not be settable with pytest.raises(AttributeError): dye.measurements = [MeasurementRun()] absorbance = MeasurementRun(name="Absorbance", uids={'id': str(uuid4())}, properties=[ Property(name='Abs at 500 nm', value=NominalReal(0.1, '')) ]) assert absorbance.material is None, "Measurements should have None as the material by default" absorbance.material = dye assert absorbance.material == dye, "Material not set correctly for measurement" assert dye.measurements == [ absorbance ], "Soft-link from material to measurement not created" fluorescence = MeasurementRun(name="Fluorescence", uids={'id': str(uuid4())}, properties=[ Property(name='PL counts at 550 nm', value=NominalReal(30000, '')) ], material=dye) assert fluorescence.material == dye, "Material not set correctly for measurement" assert dye.measurements == [absorbance, fluorescence], \ "Soft-link from material to measurements not created" assert loads(dumps(absorbance)) == absorbance, \ "Measurement should remain unchanged when serialized" assert loads(dumps(fluorescence)) == fluorescence, \ "Measurement should remain unchanged when serialized" # Serializing the material breaks the material-->measurement link. assert loads(dumps(dye)).measurements == [], \ "Measurement information should be removed when material is serialized" assert 'measurements' in repr(dye) assert 'material' in repr(fluorescence) assert 'material' in repr(absorbance) substitute_links(dye.measurements) assert 'measurements' in repr(dye)
def test_process_run(): """Test that a process run can house a material, and that it survives serde.""" process_run = ProcessRun("Bake a cake", uids={'My_ID': 17}) material_run = MaterialRun("A cake", process=process_run) # Check that a bi-directional link is established assert material_run.process == process_run assert process_run.output_material == material_run copy_material = loads(dumps(material_run)) assert dumps(copy_material) == dumps(material_run) assert 'output_material' in repr(process_run) assert 'process' in repr(material_run)
def test_numpy(): """Test that ndarrays, Series work as well.""" assert len(array_like()) < 5 # In case we extend at some point if len(array_like()) > 2: # Test numpy import numpy as np np_bounds = CategoricalBounds(np.array(["spam", "eggs"], dtype=object)) np_copy = loads(dumps(np_bounds)) assert np_copy == np_bounds if len(array_like()) > 3: # Test numpy import pandas as pd pd_bounds = CategoricalBounds(pd.Series(["spam", "eggs"])) pd_copy = loads(dumps(pd_bounds)) assert pd_copy == pd_bounds
def test_measurement_spec(): """Test the measurement spec/run connection survives ser/de.""" condition = Condition(name="Temp condition", value=NominalReal(nominal=298, units='kelvin')) parameter = Parameter(name="Important parameter") spec = MeasurementSpec(name="Precise way to do a measurement", parameters=parameter, conditions=condition) # Create a measurement run from this measurement spec measurement = MeasurementRun(conditions=condition, spec=spec) copy = loads(dumps(measurement)) assert dumps(copy.spec) == dumps(measurement.spec), \ "Measurement spec should be preserved if measurement run is serialized"
def test_dict_serialization(): """Test that a dictionary can be serialized and then deserialized as a taurus object.""" process = ProcessRun("A process") mat = MaterialRun("A material", process=process) meas = MeasurementRun("A measurement", material=mat) copy = loads(dumps(meas.as_dict())) assert copy == meas
def test_access_data(): """Demonstrate and test access patterns within the data island.""" binders = { "Polyethylene Glycol 100M": 0.02, "Sodium lignosulfonate": 0.004, "Polyvinyl Acetate": 0.0001 } powders = {"Al2O3": 0.96} island = make_data_island( density=1.0, bulk_modulus=300.0, firing_temperature=750.0, binders=binders, powders=powders, tag="Me" ) # read the density value assert(island.measurements[0].properties[0].value == NominalReal(1.0, '')) # read the bulk modulus value assert(island.measurements[0].properties[1].value == NormalReal(300.0, 3.0, '')) # read the firing temperature assert(island.process.conditions[0].value == UniformReal(749.5, 750.5, 'degC')) assert(island.process.parameters[0].value == DiscreteCategorical({"hot": 1.0})) # read the quantity of alumina quantities = island.process.ingredients[0].material.process.conditions[0].value.quantities assert(list( keyfilter(lambda x: x.spec.name == "Al2O3", quantities).values() )[0] == 0.96) # check that the serialization results in the correct number of objects in the preface # (note that neither measurements nor ingredients are serialized) assert(len(json.loads(dumps(island))[0]) == 8)
def _build_child_objects(cls, data: dict, session: Session = None): """ Build the property templates and bounds. Parameters ---------- data: dict A serialized material template. session: Session, optional Citrine session used to connect to the database. Returns ------- None The serialized material template is modified so that its properties are [PropertyTemplate, Bounds]. """ if 'properties' in data and len(data['properties']) != 0: # Each entry in the list data['properties'] has a property template as the 1st entry # and a base bounds as the 2nd entry. They are built in different ways. data['properties'] = [[ PropertyTemplate.build(prop[0].as_dict()), loads(dumps(prop[1])) ] for prop in data['properties']]
def test_json_serde(): """Test that values can be ser/de using our custom json loads/dumps.""" # Enums are only used in the context of another class -- # it is not possible to deserialize to enum with the current # serialization strategy (plain string) without this context. original = Property(name="foo", origin=Origin.MEASURED) copy = loads(dumps(original)) assert original == copy
def test_uid_deser(): """Test that uids continue to be a CaseInsensitiveDict after deserialization.""" material = MaterialRun("Input material", tags="input", uids={'Sample ID': '500-B'}) ingredient = IngredientRun(material=material) ingredient_copy = loads(dumps(ingredient)) assert isinstance(ingredient_copy.uids, CaseInsensitiveDict) assert ingredient_copy.material == material assert ingredient_copy.material.uids['sample id'] == material.uids['Sample ID']
def test_source(): """Test that source can be set, serialized, and deserialized.""" source = PerformedSource(performed_by="Marie Curie", performed_date="1898-07-01") measurement = MeasurementRun(name="Polonium", source=source) assert loads(dumps(measurement)).source.performed_by == "Marie Curie" with pytest.raises(TypeError): MeasurementRun(name="Polonium", source="Marie Curie on 1898-07-01")
def test_sac(): """Make S&C table and assert that it can be serialized.""" sac = make_strehlow_objects() sac_tbl = make_strehlow_table(sac) # Look at each different combination of Value types in a S&C record seen = set() for comp, row in zip(sac, sac_tbl['content']): mask = ''.join(map(lambda x: str(type(x)), row)) if mask not in seen: seen.add(mask) # Check that all shapes of records serialize and deserialize assert je.loads(je.dumps(comp)) == comp # Make sure that the diversity of value types isn't lost, e.g. something is being None'd assert len(seen) == 12 # Make sure there's no migration with repeated serialization assert je.dumps(je.loads(je.dumps(sac_tbl))) == je.dumps(sac_tbl)
def test_ingredient_spec(): """Tests that a process can house an ingredient, and that pairing survives serialization.""" # Create a ProcessSpec proc_spec = ProcessSpec(name="a process spec", tags=["tag1", "tag2"]) IngredientSpec(name='Input', material=MaterialSpec(name='Raw'), process=proc_spec) # Make copies of both specs proc_spec_copy = loads(dumps(proc_spec)) assert proc_spec_copy == proc_spec, "Full structure wasn't preserved across serialization"
def test_link_by_uid(): """Test that linking works.""" root = MaterialRun(name='root', process=ProcessRun(name='root proc')) leaf = MaterialRun(name='leaf', process=ProcessRun(name='leaf proc')) IngredientRun(process=root.process, material=leaf) IngredientRun(process=root.process, material=LinkByUID.from_entity(leaf)) hist = complete_material_history(root) copy = loads(dumps(hist))[-1] assert copy.process.ingredients[0].material == copy.process.ingredients[1].material
def complete_material_history(mat): """ Get a list of every single object in the material history, all as dictionaries. This is useful for testing, if we want the context list that can be used to rehydrate an entire material history. :param mat: root material run :return: a list containing every object connected to mat, each a dictionary with all links substituted. """ from taurus.entity.base_entity import BaseEntity from taurus.entity.dict_serializable import DictSerializable import json from taurus.client.json_encoder import dumps, loads from taurus.util.impl import substitute_links queue = [mat] seen = set() result = [] while queue: obj = queue.pop(0) if isinstance(obj, BaseEntity): if obj not in seen: seen.add(obj) queue.extend(obj.__dict__.values()) copy = loads(dumps(obj)) substitute_links(copy) result.insert(0, json.loads(dumps(copy))[0][0]) # Leaf first elif isinstance(obj, (list, tuple)): queue.extend(obj) elif isinstance(obj, dict): queue.extend(obj.values()) elif isinstance(obj, DictSerializable): queue.extend(obj.__dict__.values()) return result
def test_unexpected_deserialization(): """Trying to deserialize an unexpected class should throw a TypeError.""" class DummyClass(DictSerializable): typ = 'dummy_class' def __init__(self, foo): self.foo = foo # DummyClass can be serialized because it is a DictSerializable, but cannot be # deserialized because it is not in the _clazzes list. serialized = dumps(ProcessRun("A process", notes=DummyClass("something"))) with pytest.raises(TypeError): loads(serialized)
def build(cls, data: dict, session: Session = None): """ Build a data concepts object from a dictionary or from a Taurus object. This is an internal method, and should not be called directly by users. Parameters ---------- data: dict A representation of the object. It must be possible to put this dictionary through the loads/dumps cycle of the Taurus :py:mod:`JSON encoder <taurus.client.json_encoder>`. The ensuing dictionary must have a `type` field that corresponds to the response key of this class or of :py:class:`LinkByUID <taurus.entity.link_by_uid.LinkByUID>`. session: Session the Citrine session to assign to the built object. Returns ------- DataConcepts An object corresponding to a data concepts resource. """ # Running through a taurus loads/dumps cycle validates all of the fields and ensures # the object is now a dictionary with a well-understood structure data_copy_dict = loads(dumps(deepcopy(data))).as_dict() # Check the type--it should either correspond to LinkByUID or to this class. if 'type' in data_copy_dict and data_copy_dict['type'] == LinkByUID.typ: return loads(dumps(data_copy_dict)) validate_type(data_copy_dict, cls._response_key) cls._remove_local_keys(data_copy_dict) cls._build_child_objects(data_copy_dict, data) data_concepts_object = cls(**data_copy_dict) data_concepts_object.session = session cls._build_discarded_objects(data_concepts_object, data, session) return data_concepts_object
def test_condition_template(): """Test creation and serde of condition templates.""" bounds = RealBounds(2.5, 10.0, default_units='cm') template = ConditionTemplate("Chamber width", bounds=bounds, description="width of chamber") assert template.uids is not None # uids should be added automatically # Take template through a serde cycle and ensure that it is unchanged assert ConditionTemplate.build(template.dump()) == template # A more complicated cycle that goes through both taurus and citrine-python serde. assert ConditionTemplate.build(loads(dumps( template.dump())).as_dict()) == template
def test_serialize(): """Serializing a nested object should be identical to individually serializing each piece.""" condition = Condition(name="A condition", value=NominalReal(7, '')) parameter = Parameter(name="A parameter", value=NormalReal(mean=17, std=1, units='')) input_material = MaterialRun(tags="input") process = ProcessRun(tags="A tag on a process run") ingredient = IngredientRun(material=input_material, process=process) material = MaterialRun(tags=["A tag on a material"], process=process) measurement = MeasurementRun(tags="A tag on a measurement", conditions=condition, parameters=parameter, material=material) # serialize the root of the tree native_object = json.loads(dumps(measurement)) # ingredients don't get serialized on the process assert(len(native_object[0]) == 3) assert(native_object[1]["type"] == LinkByUID.typ) # serialize all of the nodes native_batch = json.loads(dumps([material, process, measurement, ingredient])) assert(len(native_batch[0]) == 5) assert(len(native_batch[1]) == 4) assert(all(x["type"] == LinkByUID.typ for x in native_batch[1]))
def dump(self): """ Convert the object to a JSON dictionary, so that every entry is serialized. Uses the json encoder client, so objects with uids are converted to LinkByUID dictionaries. Returns ------- str A string representation of the object as a dictionary. """ from taurus.client.json_encoder import dumps return json.loads(dumps(self))[1]
def test_measurement_example(): """Simple driver to populate flex_measurements.json and validate that it has contents.""" num_measurements = 4 results = make_demo_measurements(num_measurements, extra_tags={"demo"}) with open("flex_measurements.json", "w") as f: f.write(dumps(results, indent=2)) with open("flex_measurements.json", "r") as f: copy = load(f) assert len(copy) == len(results) assert all("demo" in x.tags for x in copy) assert all("my_id" in x.uids for x in copy)
def test_process_attachment(): """Test that a process can be attached to a material, and that the connection survives serde""" cake = MaterialRun('Final cake') cake.process = ProcessRun('Icing') cake_data = cake.dump() cake_copy = loads(dumps(cake_data)).as_dict() assert cake_copy['name'] == cake.name assert cake_copy['uids'] == cake.uids assert cake.process.uids['id'] == cake_copy['process'].uids['id'] reconstituted_cake = MaterialRun.build(cake_copy) assert isinstance(reconstituted_cake, MaterialRun) assert isinstance(reconstituted_cake.process, ProcessRun)
def test_ingredient_run(): """Tests that a process can house an ingredient, and that pairing survives serialization.""" # Create a ProcessSpec proc_run = ProcessRun(name="a process spec", tags=["tag1", "tag2"]) ingred_run = IngredientRun(name='Input', material=MaterialRun(name='Raw'), process=proc_run) # Make copies of both specs proc_run_copy = loads(dumps(proc_run)) assert proc_run_copy == proc_run, "Full structure wasn't preserved across serialization" assert 'process' in repr(ingred_run) assert 'ingredients' in repr(proc_run)
def build(d): """ Build an object from a JSON dictionary. This differs from `from_dict` in that the values themselves may *also* be dictionaries corresponding to serialized DictSerializable objects. Parameters ---------- d: dict The object as a serialized dictionary. Returns ------- DictSerializable The deserialized object. """ from taurus.client.json_encoder import loads, dumps return loads(dumps(d))