def test_assign_audit_info(): """Test that audit_info can be injected with build but not set""" assert ProcessSpec("Spec with no audit info").audit_info is None,\ "Audit info should be None by default" audit_info_dict = {'created_by': str(uuid4()), 'created_at': 1560033807392} audit_info_obj = AuditInfo.build(audit_info_dict) sample_object = ProcessSpec.build({ 'type': 'process_spec', 'name': "A process spec", "audit_info": audit_info_dict }) assert sample_object.audit_info == audit_info_obj, "Audit info should be built from a dict" another_object = ProcessSpec.build({ 'type': 'process_spec', 'name': "A process spec", "audit_info": audit_info_obj }) assert another_object.audit_info == audit_info_obj, "Audit info should be built from an obj" with pytest.raises(AttributeError, message="Audit info cannot be set"): sample_object.audit_info = None with pytest.raises(TypeError, message="Audit info must be dict or obj valued"): ProcessSpec.build({ 'type': 'process_spec', 'name': "A process spec", "audit_info": "Created by me, yesterday" })
def test_project_batch_delete_no_errors(project, session): job_resp = { 'job_id': '1234' } # Actual response-like data - note there is no 'failures' array within 'output' successful_job_resp = { 'job_type': 'batch_delete', 'status': 'Success', 'tasks': [ { "id": "7b6bafd9-f32a-4567-b54c-7ce594edc018", "task_type": "batch_delete", "status": "Success", "dependencies": [] } ], 'output': {} } session.set_responses(job_resp, successful_job_resp) # When del_resp = project.gemd_batch_delete([uuid.UUID( '16fd2706-8baf-433b-82eb-8c7fada847da')]) # Then assert len(del_resp) == 0 # When trying with entities session.set_responses(job_resp, successful_job_resp) entity = ProcessSpec(name="proc spec", uids={'id': '16fd2706-8baf-433b-82eb-8c7fada847da'}) del_resp = project.gemd_batch_delete([entity]) # Then assert len(del_resp) == 0
def test_simple_deserialization(valid_data): """Ensure that a deserialized Process Spec looks sane.""" process_spec: ProcessSpec = ProcessSpec.build(valid_data) assert process_spec.uids == {'id': valid_data['uids']['id']} assert process_spec.tags == ['baking::cakes', 'danger::low'] assert process_spec.parameters[0] == Parameter(name='oven temp', value=UniformReal( 195, 205, ''), origin='specified') assert process_spec.conditions == [] assert process_spec.template == \ ProcessTemplate('the template', uids={'id': valid_data['template']['uids']['id']}, parameters=[ [ParameterTemplate('oven temp template', bounds=RealBounds(175, 225, ''), uids={'id': valid_data['template']['parameters'][0][0]['uids']['id']}), RealBounds(175, 225, '')] ], description='a long description', allowed_labels=['a', 'b'], allowed_names=['a name']) assert process_spec.name == 'Process 1' assert process_spec.notes == 'make sure to use oven mitts' assert process_spec.file_links == [ FileLink('cake_recipe.txt', 'www.baking.com') ] assert process_spec.typ == 'process_spec' assert process_spec.audit_info == AuditInfo(**valid_data['audit_info'])
def test_get_by_name_or_create_no_exist(fake_collection): # test when name doesn't exist default_provider = lambda: fake_collection.register( ProcessSpec("New Resource")) old_resource_count = len(list(fake_collection.list())) result = get_by_name_or_create(fake_collection, "New Resource", default_provider) new_resource_count = len(list(fake_collection.list())) assert result.name == "New Resource" assert new_resource_count == old_resource_count + 1
def test_both_present(): objs = [ ProcessSpec("ps"), PropertyTemplate("pt", bounds=CategoricalBounds()), MeasurementSpec("ms"), ConditionTemplate("ct", bounds=CategoricalBounds()) ] templates, data_objects = split_templates_from_objects(objs) assert len(templates) == 2 assert len(data_objects) == 2
def test_get_by_name_or_create_exist(fake_collection): # test when name exists resource_name = "resource 2" default_provider = lambda: fake_collection.register( ProcessSpec("New Resource")) old_resource_count = len(list(fake_collection.list())) result = get_by_name_or_create(fake_collection, resource_name, default_provider) new_resource_count = len(list(fake_collection.list())) assert result.name == resource_name assert new_resource_count == old_resource_count
def test_soft_process_ingredient_attachment(): """Test that soft attachments are formed from process to ingredients""" vinegar = MaterialSpec("vinegar") baking_soda = MaterialSpec("baking soda") eruption = ProcessSpec("Volcano eruption") vinegar_sample = IngredientSpec("a bit of vinegar", material=vinegar, process=eruption) baking_soda_sample = IngredientSpec("a bit of NaOh", material=baking_soda) baking_soda_sample.process = eruption assert set(eruption.ingredients) == {vinegar_sample, baking_soda_sample}, \ "Creating an ingredient for a process did not auto-populate that process's ingredient list"
def fake_collection() -> Collection: class FakeCollection(ProcessSpecCollection): resources = [] def register(self, model: ProcessSpec, dry_run=False) -> ProcessSpec: self.resources.append(model) return model def list(self, page: Optional[int] = None, per_page: int = 100): if page is None: return self.resources else: return self.resources[(page - 1) * per_page:page * per_page] collection = FakeCollection(UUID('6b608f78-e341-422c-8076-35adc8828545'), UUID('6b608f78-e341-422c-8076-35adc8828545'), session) for i in range(0, 5): collection.register(ProcessSpec("resource " + str(i))) for i in range(0, 2): collection.register(ProcessSpec(duplicate_name)) return collection
def test_template_assignment(): """Test that an object and its attributes can both be assigned templates.""" humidity_template = ConditionTemplate("Humidity", RealBounds(0.5, 0.75, "")) template = ProcessTemplate( "Dry", conditions=[[humidity_template, RealBounds(0.5, 0.65, "")]]) ProcessSpec("Dry a polymer", template=template, conditions=[ Condition("Humidity", value=NominalReal(0.6, ""), template=humidity_template) ])
def test_flatten(): """Test that gemd utility methods can be applied to citrine-python objects. Citrine-python resources extend the gemd data model, so the gemd operations should work on them. """ bounds = CategoricalBounds(categories=["foo", "bar"]) template = ProcessTemplate( "spam", conditions=[(ConditionTemplate(name="eggs", bounds=bounds), bounds)] ) spec = ProcessSpec(name="spec", template=template) flat = flatten(spec, scope='testing') assert len(flat) == 3, "Expected 3 flattened objects"
def test_make_resource_private(project, session): dataset_id = str(uuid.uuid4()) dataset = project.datasets.build(dict( id=dataset_id, name="private dataset", summary="test", description="test" )) assert project.make_private(dataset) assert 1 == session.num_calls expected_call = FakeCall( method='POST', path='/projects/{}/make-private'.format(project.uid), json={ 'resource': {'type': 'DATASET', 'id': dataset_id} } ) assert expected_call == session.last_call with pytest.raises(RuntimeError): project.make_private(ProcessSpec("dummy process"))
def test_register_data_concepts(dataset): """Check that register routes to the correct collections""" expected = { MaterialTemplateCollection: MaterialTemplate("foo"), MaterialSpecCollection: MaterialSpec("foo"), MaterialRunCollection: MaterialRun("foo"), ProcessTemplateCollection: ProcessTemplate("foo"), ProcessSpecCollection: ProcessSpec("foo"), ProcessRunCollection: ProcessRun("foo"), MeasurementTemplateCollection: MeasurementTemplate("foo"), MeasurementSpecCollection: MeasurementSpec("foo"), MeasurementRunCollection: MeasurementRun("foo"), IngredientSpecCollection: IngredientSpec("foo"), IngredientRunCollection: IngredientRun(), PropertyTemplateCollection: PropertyTemplate("bar", bounds=IntegerBounds(0, 1)), ParameterTemplateCollection: ParameterTemplate("bar", bounds=IntegerBounds(0, 1)), ConditionTemplateCollection: ConditionTemplate("bar", bounds=IntegerBounds(0, 1)) } for collection, obj in expected.items(): assert len(obj.uids) == 0 registered = dataset.register(obj) assert len(obj.uids) == 1 assert len(registered.uids) == 1 assert basename(dataset.session.calls[-1].path) == basename( collection._path_template) for pair in obj.uids.items(): assert pair[1] == registered.uids[pair[0]]
def test_simple_deserialization(valid_data): """Ensure that a deserialized Process Run looks sane.""" process_run: ProcessRun = ProcessRun.build(valid_data) assert process_run.uids == {'id': valid_data['uids']['id'], 'my_id': 'process1-v1'} assert process_run.tags == ['baking::cakes', 'danger::low'] assert process_run.conditions[0] == Condition(name='oven temp', value=NominalReal(203.0, ''), origin='measured') assert process_run.parameters == [] assert process_run.file_links == [] assert process_run.template is None assert process_run.output_material is None assert process_run.spec == \ ProcessSpec(name="Spec for proc 1", uids={'id': valid_data['spec']['uids']['id']}, conditions=[Condition(name='oven temp', value=UniformReal(175, 225, ''), origin='specified')] ) assert process_run.name == 'Process 1' assert process_run.notes == 'make sure to use oven mitts' assert process_run.typ == 'process_run'
def test_transfer_resource(project, session): dataset_id = str(uuid.uuid4()) dataset = project.datasets.build(dict( id=dataset_id, name="dataset to transfer", summary="test", description="test" )) assert project.transfer_resource(dataset, project.uid) expected_call = FakeCall( method='POST', path='/projects/{}/transfer-resource'.format(project.uid), json={ 'to_project_id': str(project.uid), 'resource': dataset.as_entity_dict() } ) assert expected_call == session.last_call with pytest.raises(RuntimeError): project.transfer_resource(ProcessSpec("dummy process"), project.uid)
def test_batch_delete(dataset): job_resp = {'job_id': '1234'} import json failures_escaped_json = json.dumps([{ "id": { 'scope': 'somescope', 'id': 'abcd-1234' }, 'cause': { "code": 400, "message": "", "validation_errors": [{ "failure_message": "fail msg", "failure_id": "identifier.coreid.missing" }] } }]) failed_job_resp = { 'job_type': 'batch_delete', 'status': 'Success', 'tasks': [], 'output': { 'failures': failures_escaped_json } } session = dataset.session session.set_responses(job_resp, failed_job_resp) # When del_resp = dataset.gemd_batch_delete( [uuid.UUID('16fd2706-8baf-433b-82eb-8c7fada847da')]) # Then assert 2 == session.num_calls assert len(del_resp) == 1 first_failure = del_resp[0] expected_api_error = ApiError( 400, "", validation_errors=[ ValidationError(failure_message="fail msg", failure_id="identifier.coreid.missing") ]) assert first_failure == (LinkByUID('somescope', 'abcd-1234'), expected_api_error) # And again with tuples of (scope, id) session.set_responses(job_resp, failed_job_resp) del_resp = dataset.gemd_batch_delete( [LinkByUID('id', '16fd2706-8baf-433b-82eb-8c7fada847da')]) assert len(del_resp) == 1 first_failure = del_resp[0] assert first_failure == (LinkByUID('somescope', 'abcd-1234'), expected_api_error) # And again with UUID-like strings session.set_responses(job_resp, failed_job_resp) del_resp = dataset.gemd_batch_delete( ['16fd2706-8baf-433b-82eb-8c7fada847da']) assert len(del_resp) == 1 first_failure = del_resp[0] assert first_failure == (LinkByUID('somescope', 'abcd-1234'), expected_api_error) # And again with a Base Entity session.set_responses(job_resp, failed_job_resp) del_resp = dataset.gemd_batch_delete( [ProcessSpec(name='PS', uids={"foof": "1"})]) assert len(del_resp) == 1 first_failure = del_resp[0] assert first_failure == (LinkByUID('somescope', 'abcd-1234'), expected_api_error)
def test_no_templates(): objs = [ProcessSpec("ps"), MeasurementSpec("ms")] templates, data_objects = split_templates_from_objects(objs) assert len(templates) == 0 assert len(data_objects) == 2
def test_serialization(valid_data): """Ensure that a serialized Process Run looks sane.""" process_spec: ProcessSpec = ProcessSpec.build(valid_data) serialized = process_spec.dump() valid_data.pop('audit_info') # this field is not serialized assert serialized == valid_data
def test_serialization(valid_data): """Ensure that a serialized Process Run looks sane.""" process_spec: ProcessSpec = ProcessSpec.build(valid_data) serialized = process_spec.dump() assert serialized == valid_data
def test_get_type(): """Test that get_type works, even though its not used in DataConcepts.build""" assert DataConcepts.get_type({"type": "process_run"}) == ProcessRun assert DataConcepts.get_type(ProcessSpec("foo")) == ProcessSpec
def test_register_all_data_concepts(dataset): """Check that register_all registers everything and routes to all collections""" bounds = IntegerBounds(0, 1) property_template = PropertyTemplate("bar", bounds=bounds) parameter_template = ParameterTemplate("bar", bounds=bounds) condition_template = ConditionTemplate("bar", bounds=bounds) foo_process_template = ProcessTemplate( "foo", conditions=[[condition_template, bounds]], parameters=[[parameter_template, bounds]]) foo_process_spec = ProcessSpec("foo", template=foo_process_template) foo_process_run = ProcessRun("foo", spec=foo_process_spec) foo_material_template = MaterialTemplate( "foo", properties=[[property_template, bounds]]) foo_material_spec = MaterialSpec("foo", template=foo_material_template, process=foo_process_spec) foo_material_run = MaterialRun("foo", spec=foo_material_spec, process=foo_process_run) baz_template = MaterialTemplate("baz") foo_measurement_template = MeasurementTemplate( "foo", conditions=[[condition_template, bounds]], parameters=[[parameter_template, bounds]], properties=[[property_template, bounds]]) foo_measurement_spec = MeasurementSpec("foo", template=foo_measurement_template) foo_measurement_run = MeasurementRun("foo", spec=foo_measurement_spec, material=foo_material_run) foo_ingredient_spec = IngredientSpec("foo", material=foo_material_spec, process=foo_process_spec) foo_ingredient_run = IngredientRun(spec=foo_ingredient_spec, material=foo_material_run, process=foo_process_run) baz_run = MeasurementRun("baz") # worst order possible expected = { foo_ingredient_run: IngredientRunCollection, foo_ingredient_spec: IngredientSpecCollection, foo_measurement_run: MeasurementRunCollection, foo_measurement_spec: MeasurementSpecCollection, foo_measurement_template: MeasurementTemplateCollection, foo_material_run: MaterialRunCollection, foo_material_spec: MaterialSpecCollection, foo_material_template: MaterialTemplateCollection, foo_process_run: ProcessRunCollection, foo_process_spec: ProcessSpecCollection, foo_process_template: ProcessTemplateCollection, baz_template: MaterialTemplateCollection, baz_run: MeasurementRunCollection, property_template: PropertyTemplateCollection, parameter_template: ParameterTemplateCollection, condition_template: ConditionTemplateCollection } for obj in expected: assert len(obj.uids) == 0 # All should be without ids registered = dataset.register_all(expected.keys()) assert len(registered) == len(expected) seen_ids = set() for obj in expected: assert len(obj.uids) == 1 # All should now have exactly 1 id for pair in obj.uids.items(): assert pair not in seen_ids # All ids are different seen_ids.add(pair) for obj in registered: for pair in obj.uids.items(): assert pair in seen_ids # registered items have the same ids call_basenames = [ call.path.split('/')[-2] for call in dataset.session.calls ] collection_basenames = [ basename(collection._path_template) for collection in expected.values() ] assert set(call_basenames) == set(collection_basenames) assert len(set(call_basenames)) == len( call_basenames) # calls are batched internally # spot check order. Does not check every constraint assert call_basenames.index( basename( IngredientRunCollection._path_template)) > call_basenames.index( basename(IngredientSpecCollection._path_template)) assert call_basenames.index(basename( MaterialRunCollection._path_template)) > call_basenames.index( basename(MaterialSpecCollection._path_template)) assert call_basenames.index( basename( MeasurementRunCollection._path_template)) > call_basenames.index( basename(MeasurementSpecCollection._path_template)) assert call_basenames.index(basename( ProcessRunCollection._path_template)) > call_basenames.index( basename(ProcessSpecCollection._path_template)) assert call_basenames.index(basename( MaterialSpecCollection._path_template)) > call_basenames.index( basename(MaterialTemplateCollection._path_template)) assert call_basenames.index( basename( MeasurementSpecCollection._path_template)) > call_basenames.index( basename(MeasurementTemplateCollection._path_template)) assert call_basenames.index(basename( ProcessSpecCollection._path_template)) > call_basenames.index( basename(ProcessTemplateCollection._path_template)) assert call_basenames.index(basename( MaterialSpecCollection._path_template)) > call_basenames.index( basename(ProcessSpecCollection._path_template)) assert call_basenames.index(basename( MaterialSpecCollection._path_template)) > call_basenames.index( basename(MeasurementSpecCollection._path_template)) assert call_basenames.index( basename(MeasurementTemplateCollection._path_template) ) > call_basenames.index( basename(ConditionTemplateCollection._path_template)) assert call_basenames.index( basename(MeasurementTemplateCollection._path_template) ) > call_basenames.index( basename(ParameterTemplateCollection._path_template)) assert call_basenames.index( basename( MaterialTemplateCollection._path_template)) > call_basenames.index( basename(PropertyTemplateCollection._path_template))