예제 #1
0
def test_object_pointer_serde():
    """Test that an object can point to another object, and that this survives serde."""
    baking = ProcessRun("Bake a cake")
    cake = MaterialRun("A cake", process=baking)
    reconstituted_cake = MaterialRun.build(cake.dump())
    assert isinstance(reconstituted_cake.process, ProcessRun)
    assert isinstance(reconstituted_cake.process.output_material, MaterialRun)
def test_build_discarded_objects_in_material_run():
    # Note:  This is really here just for test coverage - couldn't figure out how to
    #        get past the validation/serialization in MaterialRun.build - it might just be dead code
    material_run = MaterialRunFactory()
    material_run_data = MaterialRunDataFactory(
        name='Test Run', measurements=LinkByUIDFactory.create_batch(3))

    MaterialRun._build_discarded_objects(material_run, material_run_data, None)
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)
예제 #4
0
def test_simple_deserialization(valid_data):
    """Ensure that a deserialized Measurement Run looks sane."""
    measurement_run: MeasurementRun = MeasurementRun.build(valid_data)
    assert measurement_run.uids == {'id': valid_data['uids']['id']}
    assert measurement_run.name == 'Taste test'
    assert measurement_run.notes is None
    assert measurement_run.tags == []
    assert measurement_run.conditions == []
    assert measurement_run.parameters == []
    assert measurement_run.properties[0] == Property('sweetness',
                                                     origin="measured",
                                                     value=NominalInteger(7))
    assert measurement_run.properties[1] == Property('fluffiness',
                                                     origin="measured",
                                                     value=NominalInteger(10))
    assert measurement_run.file_links == []
    assert measurement_run.template is None
    assert measurement_run.material == MaterialRun(
        'sponge cake',
        uids={'id': valid_data['material']['uids']['id']},
        sample_type='experimental')
    assert measurement_run.material.audit_info == AuditInfo(
        **valid_data['material']['audit_info'])
    assert measurement_run.material.dataset == UUID(
        valid_data['material']['dataset'])
    assert measurement_run.spec is None
    assert measurement_run.typ == 'measurement_run'
    assert measurement_run.audit_info == AuditInfo(**valid_data['audit_info'])
    assert measurement_run.dataset == UUID(valid_data['dataset'])
예제 #5
0
def test_material_attachment():
    """Test that a material can be attached to a measurement, and the connection survives serde."""
    cake = MaterialRun('Final Cake')
    mass = MeasurementRun('Weigh cake', material=cake)
    mass_data = mass.dump()
    mass_copy = MeasurementRun.build(mass_data)
    assert mass_copy == mass
예제 #6
0
def test_default_for_material(collection: AraDefinitionCollection, session):
    """Test that default for material hits the right route"""
    # Given
    project_id = '6b608f78-e341-422c-8076-35adc8828545'
    dummy_resp = {
        'config': TableConfig(
            name='foo',
            description='foo',
            variables=[],
            columns=[],
            rows=[],
            datasets=[]
        ).dump(),
        'ambiguous': [
            [
                RootIdentifier(name='foo', headers=['foo'], scope='id').dump(),
                IdentityColumn(data_source='foo').dump(),
            ]
        ],
    }
    session.responses.append(dummy_resp)
    collection.default_for_material(
        material='my_id',
        scope='my_scope',
        name='my_name',
        description='my_description',
    )

    assert 1 == session.num_calls
    assert session.last_call == FakeCall(
        method="GET",
        path="projects/{}/table-configs/default".format(project_id),
        params={
            'id': 'my_id',
            'scope': 'my_scope',
            'name': 'my_name',
            'description': 'my_description'
        }
    )
    session.calls.clear()
    session.responses.append(dummy_resp)
    collection.default_for_material(
        material=MaterialRun('foo', uids={'scope': 'id'}),
        scope='ignored',
        name='my_name',
        description='my_description',
    )
    assert 1 == session.num_calls
    assert session.last_call == FakeCall(
        method="GET",
        path="projects/{}/table-configs/default".format(project_id),
        params={
            'id': 'id',
            'scope': 'scope',
            'name': 'my_name',
            'description': 'my_description'
        }
    )
def test_material_attachment():
    """
    Attach a material run to an ingredient run.

    Check that the ingredient can be built, and that the connection survives ser/de.
    """
    flour = MaterialRun("flour", sample_type='unknown')
    flour_ingredient = IngredientRun(material=flour,
                                     absolute_quantity=NominalReal(500, 'g'))

    flour_ingredient_copy = IngredientRun.build(flour_ingredient.dump())
    assert flour_ingredient_copy == flour_ingredient
예제 #8
0
def test_soft_measurement_material_attachment():
    """Test that soft attachments are formed from materials to measurements."""
    cake = MaterialRun("A cake")
    smell_test = MeasurementRun("use your nose",
                                material=cake,
                                properties=[
                                    Property(
                                        name="Smell",
                                        value=DiscreteCategorical("yummy"))
                                ])
    taste_test = MeasurementRun("taste", material=cake)
    assert cake.measurements == [smell_test, taste_test]
예제 #9
0
def test_delete_data_concepts(dataset):
    """Check that delete routes to the correct collections"""
    expected = {
        MaterialTemplateCollection: MaterialTemplate("foo",
                                                     uids={"foo": "bar"}),
        MaterialSpecCollection: MaterialSpec("foo", uids={"foo": "bar"}),
        MaterialRunCollection: MaterialRun("foo", uids={"foo": "bar"}),
        ProcessTemplateCollection: ProcessTemplate("foo", uids={"foo": "bar"}),
    }

    for collection, obj in expected.items():
        dataset.delete(obj)
        assert dataset.session.calls[-1].path.split("/")[-3] == basename(
            collection._path_template)
def test_simple_deserialization():
    """Ensure that a deserialized Material Run looks sane."""
    valid_data: dict = MaterialRunDataFactory(name='Cake 1')
    material_run: MaterialRun = MaterialRun.build(valid_data)
    assert material_run.uids == {'id': valid_data['uids']['id']}
    assert material_run.name == 'Cake 1'
    assert material_run.tags == ["color"]
    assert material_run.notes is None
    assert material_run.process == LinkByUID('id', valid_data['process']['id'])
    assert material_run.sample_type == 'experimental'
    assert material_run.template is None
    assert material_run.spec is None
    assert material_run.file_links == []
    assert material_run.typ == 'material_run'
def test_measurement_material_connection_rehydration():
    """Test that fully-linked GEMD object can be built as fully-linked Citrine-python object."""
    starting_mat_spec = GEMDMaterialSpec("starting material")
    starting_mat = GEMDMaterialRun("starting material", spec=starting_mat_spec)
    meas_spec = GEMDMeasurementSpec("measurement spec")
    meas1 = GEMDMeasurementRun("measurement on starting material",
                               spec=meas_spec, material=starting_mat)

    process_spec = GEMDProcessSpec("Transformative process")
    process = GEMDProcessRun("Transformative process", spec=process_spec)
    ingredient_spec = GEMDIngredientSpec(name="ingredient", material=starting_mat_spec,
                                         process=process_spec)
    ingredient = GEMDIngredientRun(material=starting_mat, process=process, spec=ingredient_spec)

    ending_mat_spec = GEMDMaterialSpec("ending material", process=process_spec)
    ending_mat = GEMDMaterialRun("ending material", process=process, spec=ending_mat_spec)
    meas2 = GEMDMeasurementRun("measurement on ending material",
                               spec=meas_spec, material=ending_mat)

    copy = MaterialRun.build(ending_mat)
    assert isinstance(copy, MaterialRun), "copy of ending_mat should be a MaterialRun"
    assert len(copy.measurements) == 1, "copy of ending_mat should have one measurement"
    assert isinstance(copy.measurements[0], MeasurementRun), \
        "copy of ending_mat should have a measurement that is a MeasurementRun"
    assert isinstance(copy.measurements[0].spec, MeasurementSpec), \
        "copy of ending_mat should have a measurement that has a spec that is a MeasurementSpec"
    assert isinstance(copy.process, ProcessRun), "copy of ending_mat should have a process"
    assert len(copy.process.ingredients) == 1, \
        "copy of ending_mat should have a process with one ingredient"
    assert isinstance(copy.spec, MaterialSpec), "copy of ending_mat should have a spec"
    assert len(copy.spec.process.ingredients) == 1, \
        "copy of ending_mat should have a spec with a process that has one ingredient"
    assert isinstance(copy.process.spec.ingredients[0], IngredientSpec), \
        "copy of ending_mat should have a spec with a process that has an ingredient " \
        "that is an IngredientRun"

    copy_ingredient = copy.process.ingredients[0]
    assert isinstance(copy_ingredient, IngredientRun), \
        "copy of ending_mat should have a process with an ingredient that is an IngredientRun"
    assert isinstance(copy_ingredient.material, MaterialRun), \
        "copy of ending_mat should have a process with an ingredient that links to a MaterialRun"
    assert len(copy_ingredient.material.measurements) == 1, \
        "copy of ending_mat should have a process with an ingredient derived from a material " \
        "that has one measurement performed on it"
    assert isinstance(copy_ingredient.material.measurements[0], MeasurementRun), \
        "copy of ending_mat should have a process with an ingredient derived from a material " \
        "that has one measurement that gets deserialized as a MeasurementRun"
    assert isinstance(copy_ingredient.material.measurements[0].spec, MeasurementSpec), \
        "copy of ending_mat should have a process with an ingredient derived from a material " \
        "that has one measurement that has a spec"
def test_cake():
    """Test that the cake example from gemd can be built without modification.
    This only tests the fix to a limited problem (not all ingredients being reconstructed) and
    is not a full test of equivalence, because the reconstruction creates "dangling paths."
    Consider a material/process run/spec square. The material run links to a material spec, which
    links to a process spec. The material run also links to a process run that links to a process
    spec, but it's a different process spec, and is not linked to the material spec. If you try
    to call mat.process.spec.output_material it returns None. This is due to the way the build()
    method attempts to traverse the object tree, and requires an overhaul of build().
    """
    gemd_cake = make_cake()
    cake = MaterialRun.build(gemd_cake)
    assert [ingred.name for ingred in cake.process.ingredients] == \
           [ingred.name for ingred in gemd_cake.process.ingredients]
def test_list_validation():
    """Test that lists are validated by taurus."""
    mat = MaterialRun("A material")
    with pytest.raises(TypeError):
        # labels must be a list of string, but contains an int
        IngredientRun(material=mat, labels=["Label 1", 17], name="foo")

    ingredient = IngredientRun(material=mat,
                               labels=["Label 1", "label 2"],
                               name="foo")
    with pytest.raises(TypeError):
        # cannot append an int to a list of strings
        ingredient.labels.append(17)

    with pytest.raises(TypeError):
        # list of conditions cannot contain a property
        MeasurementRun("A measurement",
                       conditions=[Property("not a condition")])
예제 #14
0
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]]
예제 #15
0
def test_default_for_material_failure(collection: AraDefinitionCollection):
    with pytest.raises(ValueError):
        collection.default_for_material(
            material=MaterialRun('foo'),
            name='foo'
        )
def test_serialization():
    """Ensure that a serialized Material Run looks sane."""
    valid_data: dict = MaterialRunDataFactory()
    material_run: MaterialRun = MaterialRun.build(valid_data)
    serialized = material_run.dump()
    assert serialized == valid_data
def test_nested_serialization():
    """Create a bunch of nested objects and make sure that nothing breaks."""

    # helper
    def make_ingredient(material: MaterialRun):
        return IngredientRun(name=material.name, material=material)

    icing = ProcessRun(name="Icing")
    cake = MaterialRun(name='Final cake', process=icing)

    cake.process.ingredients.append(make_ingredient(MaterialRun('Baked Cake')))
    cake.process.ingredients.append(make_ingredient(MaterialRun('Frosting')))

    baked = cake.process.ingredients[0].material
    baked.process = ProcessRun(name='Baking')
    baked.process.ingredients.append(make_ingredient(MaterialRun('Batter')))

    batter = baked.process.ingredients[0].material
    batter.process = ProcessRun(name='Mixing batter')

    batter.process.ingredients.append(
        make_ingredient(material=MaterialRun('Butter')))
    batter.process.ingredients.append(
        make_ingredient(material=MaterialRun('Sugar')))
    batter.process.ingredients.append(
        make_ingredient(material=MaterialRun('Flour')))
    batter.process.ingredients.append(
        make_ingredient(material=MaterialRun('Milk')))

    cake.dump()
예제 #18
0
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))
예제 #19
0
def test_default_for_material(collection: TableConfigCollection, session):
    """Test that default for material hits the right route"""
    # Given
    project_id = '6b608f78-e341-422c-8076-35adc8828545'
    dummy_resp = {
        'config':
        TableConfig(name='foo',
                    description='foo',
                    variables=[],
                    columns=[],
                    rows=[],
                    datasets=[]).dump(),
        'ambiguous': [[
            RootIdentifier(name='foo', headers=['foo'], scope='id').dump(),
            IdentityColumn(data_source='foo').dump(),
        ]],
    }
    # Specify by Citrine ID
    session.responses.append(dummy_resp)
    collection.default_for_material(
        material='my_id',
        name='my_name',
        description='my_description',
    )
    assert 1 == session.num_calls
    assert session.last_call == FakeCall(
        method="GET",
        path="projects/{}/table-configs/default".format(project_id),
        params={
            'id': 'my_id',
            'scope': CITRINE_SCOPE,
            'name': 'my_name',
            'description': 'my_description'
        })
    # Specify by id with custom scope, throwing a warning
    session.calls.clear()
    session.responses.append(dummy_resp)
    with warnings.catch_warnings(record=True) as caught:
        collection.default_for_material(material='my_id',
                                        scope='my_scope',
                                        name='my_name',
                                        description='my_description')
        assert len(caught) == 1
        assert issubclass(caught[0].category, DeprecationWarning)
    assert 1 == session.num_calls
    assert session.last_call == FakeCall(
        method="GET",
        path="projects/{}/table-configs/default".format(project_id),
        params={
            'id': 'my_id',
            'scope': 'my_scope',
            'name': 'my_name',
            'description': 'my_description'
        })
    # Specify by MaterialRun
    session.calls.clear()
    session.responses.append(dummy_resp)
    collection.default_for_material(material=MaterialRun('foo',
                                                         uids={'scope': 'id'}),
                                    name='my_name',
                                    description='my_description',
                                    algorithm=TableBuildAlgorithm.FORMULATIONS)
    assert 1 == session.num_calls
    assert session.last_call == FakeCall(
        method="GET",
        path="projects/{}/table-configs/default".format(project_id),
        params={
            'id': 'id',
            'scope': 'scope',
            'name': 'my_name',
            'description': 'my_description',
            'algorithm': TableBuildAlgorithm.FORMULATIONS.value
        })
    # Specify by LinkByUID
    session.calls.clear()
    session.responses.append(dummy_resp)
    collection.default_for_material(
        material=LinkByUID(scope="scope", id="id"),
        name='my_name',
        description='my_description',
    )
    assert 1 == session.num_calls
    assert session.last_call == FakeCall(
        method="GET",
        path="projects/{}/table-configs/default".format(project_id),
        params={
            'id': 'id',
            'scope': 'scope',
            'name': 'my_name',
            'description': 'my_description'
        })

    # And we allowed for the more forgiving call structure, so test it.
    session.calls.clear()
    session.responses.append(dummy_resp)
    collection.default_for_material(
        material=MaterialRun('foo', uids={'scope': 'id'}),
        scope='ignored',
        algorithm=TableBuildAlgorithm.FORMULATIONS.value,
        name='my_name',
        description='my_description',
    )
    assert 1 == session.num_calls
    assert session.last_call == FakeCall(
        method="GET",
        path="projects/{}/table-configs/default".format(project_id),
        params={
            'id': 'id',
            'scope': 'scope',
            'algorithm': TableBuildAlgorithm.FORMULATIONS.value,
            'name': 'my_name',
            'description': 'my_description'
        })
예제 #20
0
def test_soft_process_material_attachment():
    """Test that soft attachments are formed from process to output material"""
    baking = ProcessRun("Bake a cake")
    cake = MaterialRun("A cake", process=baking)
    assert baking.output_material == cake
예제 #21
0
def test_object_validation():
    """Test that an object pointing to another object is validated."""
    meas = MeasurementRun("A measurement")
    with pytest.raises(TypeError):
        MaterialRun("A cake", process=meas)