def test_object_template_serde():
    """Test serde of an object template."""
    length_template = PropertyTemplate("Length", bounds=RealBounds(2.0, 3.5, 'cm'))
    sub_bounds = RealBounds(2.5, 3.0, 'cm')
    color_template = PropertyTemplate("Color", bounds=CategoricalBounds(["red", "green", "blue"]))
    # Properties are a mixture of property templates and [template, bounds], pairs
    block_template = MaterialTemplate("Block", properties=[[length_template, sub_bounds],
                                                           color_template])
    copy_template = MaterialTemplate.build(block_template.dump())
    assert copy_template == block_template

    # Tests below exercise similar code, but for measurement and process templates
    pressure_template = ConditionTemplate("pressure", bounds=RealBounds(0.1, 0.11, 'MPa'))
    index_template = ParameterTemplate("index", bounds=IntegerBounds(2, 10))
    meas_template = MeasurementTemplate("A measurement of length", properties=[length_template],
                                        conditions=[pressure_template], description="Description",
                                        parameters=[index_template], tags=["foo"])
    assert MeasurementTemplate.build(meas_template.dump()) == meas_template

    proc_template = ProcessTemplate("Make an object", parameters=[index_template],
                                    conditions=[pressure_template], allowed_labels=["Label"],
                                    allowed_names=["first sample", "second sample"])
    assert ProcessTemplate.build(proc_template.dump()) == proc_template

    # Check that serde still works if the template is a LinkByUID
    pressure_template.uids['id'] = '12345'  # uids['id'] not populated by default
    proc_template.conditions[0][0] = LinkByUID('id', pressure_template.uids['id'])
    assert ProcessTemplate.build(proc_template.dump()) == proc_template
def test_contains_incompatible_units():
    """Make sure contains returns false when the units don't match."""
    dim = RealBounds(lower_bound=0, upper_bound=100, default_units="m")
    dim2 = RealBounds(lower_bound=0, upper_bound=100, default_units="kJ")
    dim3 = RealBounds(lower_bound=0, upper_bound=100, default_units='')
    assert not dim.contains(dim2)
    assert not dim.contains(dim3)
Beispiel #3
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_bounds_mismatch():
    """Test that a mismatch between the attribute and given bounds throws a ValueError."""
    attribute_bounds = RealBounds(0, 100, '')
    object_bounds = RealBounds(200, 300, '')
    cond_template = ConditionTemplate("a condition", bounds=attribute_bounds)
    with pytest.raises(ValueError):
        ProcessTemplate("a process template",
                        conditions=[[cond_template, object_bounds]])
def test_dependencies():
    """Test that dependency lists make sense."""
    targets = [
        PropertyTemplate(name="name", bounds=RealBounds(0, 1, '')),
        ConditionTemplate(name="name", bounds=RealBounds(0, 1, '')),
        ParameterTemplate(name="name", bounds=RealBounds(0, 1, '')),
    ]
    for target in targets:
        assert len(target.all_dependencies()) == 0, f"{type(target)} had dependencies"
 def _check(value: BaseValue):
     fraction_bounds = RealBounds(lower_bound=0.0,
                                  upper_bound=1.0,
                                  default_units='')
     level = get_validation_level()
     accept = level == WarningLevel.IGNORE or fraction_bounds.contains(
         value)
     if not accept:
         message = f"Value {value} is not between 0 and 1."
         if level == WarningLevel.WARNING:
             logger.warning(message)
         else:
             raise ValueError(message)
def test_dependencies():
    """Test that dependency lists make sense."""
    attribute_bounds = RealBounds(0, 100, '')
    cond_template = ConditionTemplate("a condition", bounds=attribute_bounds)
    proc_template = ProcessTemplate("a process template",
                                    conditions=[cond_template])
    assert cond_template in proc_template.all_dependencies()
def test_constructor_error():
    """Test that invalid real bounds cannot be constructed."""
    with pytest.raises(ValueError):
        RealBounds()

    with pytest.raises(ValueError):
        RealBounds(0, float("inf"), "meter")

    with pytest.raises(ValueError):
        RealBounds(None, 10, '')

    with pytest.raises(ValueError):
        RealBounds(0, 100)

    with pytest.raises(ValueError):
        RealBounds(100, 0, "m")
Beispiel #9
0
def test_complex_substitutions():
    """Make sure accounting works for realistic objects."""
    root = MaterialRun("root",
                       process=ProcessRun("root", spec=ProcessSpec("root")),
                       spec=MaterialSpec("root"))
    root.spec.process = root.process.spec
    input = MaterialRun("input",
                        process=ProcessRun("input", spec=ProcessSpec("input")),
                        spec=MaterialSpec("input"))
    input.spec.process = input.process.spec
    IngredientRun(process=root.process,
                  material=input,
                  spec=IngredientSpec("ingredient",
                                      process=root.process.spec,
                                      material=input.spec))
    param = ParameterTemplate("Param", bounds=RealBounds(-1, 1, "m"))
    root.process.spec.template = ProcessTemplate("Proc", parameters=[param])
    root.process.parameters.append(
        Parameter("Param", value=NormalReal(0, 1, 'm'), template=param))

    links = flatten(root, scope="test-scope")
    index = make_index(links)
    rebuild = substitute_objects(links, index, inplace=True)
    rebuilt_root = next(x for x in rebuild
                        if x.name == root.name and x.typ == root.typ)
    all_objs = recursive_flatmap(rebuilt_root,
                                 func=lambda x: [x],
                                 unidirectional=False)
    unique = [x for i, x in enumerate(all_objs) if i == all_objs.index(x)]
    assert not any(isinstance(x, LinkByUID) for x in unique), "All are objects"
    assert len(links) == len(unique), "Objects are missing"
def test_invalid_assignment(caplog):
    """Test that invalid assignments throw the appropriate errors."""
    with pytest.raises(TypeError):
        Property(value=NominalReal(10, ''))
    with pytest.raises(TypeError):
        Property(name="property", value=10)
    with pytest.raises(TypeError):
        Property(name="property",
                 template=ProcessTemplate("wrong kind of template"))
    with pytest.raises(ValueError):
        Property(name="property", origin=None)

    valid_prop = Property(name="property",
                          value=NominalReal(10, ''),
                          template=PropertyTemplate("template",
                                                    bounds=RealBounds(
                                                        0, 100, '')))
    good_val = valid_prop.value
    bad_val = NominalReal(-10.0, '')
    assert len(caplog.records
               ) == 0, "Warning caught before logging tests were reached."
    with validation_level(WarningLevel.IGNORE):
        valid_prop.value = bad_val
        assert len(caplog.records
                   ) == 0, "Validation warned even though level is IGNORE."
        assert valid_prop.value == bad_val, "IGNORE allowed the bad value to be set."
        valid_prop.value = good_val
        assert len(caplog.records
                   ) == 0, "Validation warned even though level is IGNORE."
    with validation_level(WarningLevel.WARNING):
        valid_prop.value = bad_val
        assert len(caplog.records
                   ) == 1, "Validation didn't warn on out of bounds value."
        assert valid_prop.value == bad_val, "WARNING allowed the bad value to be set."
        valid_prop.value = good_val
        assert len(
            caplog.records) == 1, "Validation DID warn on a valid value."
    with validation_level(WarningLevel.FATAL):
        with pytest.raises(ValueError):
            valid_prop.value = bad_val
        assert valid_prop.value == good_val, "FATAL didn't allow the bad value to be set."

    with validation_level(WarningLevel.FATAL):
        with pytest.raises(ValueError):
            valid_prop.template = PropertyTemplate("template",
                                                   bounds=RealBounds(0, 1, ''))
        assert valid_prop.value == good_val, "FATAL didn't allow the bad value to be set."
Beispiel #11
0
def test_invalid_assignment():
    """Invalid assignments to `process` or `material` throw a TypeError."""
    with pytest.raises(TypeError):
        IngredientRun(material=RealBounds(0, 5.0, ''))
    with pytest.raises(TypeError):
        IngredientRun(process="process")
    with pytest.raises(TypeError):
        IngredientRun(spec=5)
def test_object_template_validation():
    """Test that attribute templates are validated against given bounds."""
    length_template = PropertyTemplate("Length", bounds=RealBounds(2.0, 3.5, 'cm'))
    dial_template = ConditionTemplate("dial", bounds=IntegerBounds(0, 5))
    color_template = ParameterTemplate("Color", bounds=CategoricalBounds(["red", "green", "blue"]))

    with pytest.raises(TypeError):
        MaterialTemplate()

    with pytest.raises(ValueError):
        MaterialTemplate("Block", properties=[[length_template, RealBounds(3.0, 4.0, 'cm')]])

    with pytest.raises(ValueError):
        ProcessTemplate("a process", conditions=[[color_template, CategoricalBounds(["zz"])]])
        
    with pytest.raises(ValueError):
        MeasurementTemplate("A measurement", parameters=[[dial_template, IntegerBounds(-3, -1)]])
Beispiel #13
0
def test_contains():
    """Test basic contains logic."""
    bounds = CategoricalBounds(categories={"spam", "eggs"})
    assert bounds.contains(CategoricalBounds(categories={"spam"}))
    assert not bounds.contains(CategoricalBounds(categories={"spam", "foo"}))
    assert not bounds.contains(RealBounds(0.0, 2.0, ''))
    assert not bounds.contains(None)
    with pytest.raises(TypeError):
        bounds.contains({"spam", "eggs"})
def test_contains():
    """Test basic contains logic."""
    bounds = CompositionBounds(components={"spam", "eggs"})
    assert bounds.contains(CompositionBounds(components={"spam"}))
    assert not bounds.contains(CompositionBounds(components={"foo"}))
    assert not bounds.contains(RealBounds(0.0, 2.0, ''))
    assert not bounds.contains(None)
    with pytest.raises(TypeError):
        bounds.contains({"spam"})
Beispiel #15
0
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 gemd-python and citrine-python serde.
    assert ConditionTemplate.build(loads(dumps(template.dump())).as_dict()) == template
Beispiel #16
0
def test_attribute_serde():
    """An attribute with a link to an attribute template should be copy-able."""
    prop_tmpl = PropertyTemplate(name='prop_tmpl',
                                 bounds=RealBounds(0, 2, 'm'))
    prop = Property(name='prop', template=prop_tmpl, value=NominalReal(1, 'm'))
    meas_spec = MeasurementSpec("a spec")
    meas = MeasurementRun("a measurement", spec=meas_spec, properties=[prop])
    assert loads(dumps(prop)) == prop
    assert loads(dumps(meas)) == meas
    assert isinstance(prop.template, PropertyTemplate)
def test_contains():
    """Test basic contains logic."""
    bounds = MolecularStructureBounds()
    assert bounds.contains(MolecularStructureBounds())
    assert not bounds.contains(RealBounds(0.0, 2.0, ''))
    assert not bounds.contains(None)
    with pytest.raises(TypeError):
        bounds.contains('c1(C=O)cc(OC)c(O)cc1')
    with pytest.raises(TypeError):
        bounds.contains(
            'InChI=1/C8H8O3/c1-11-8-4-6(5-9)2-3-7(8)10/h2-5,10H,1H3')
def test_type_mismatch():
    """Test that incompatible types cannot be matched against RealBounds."""
    bounds = RealBounds(0, 1, default_units="meters")
    assert not bounds.contains(IntegerBounds(0, 1))
    assert not bounds.contains(None)
    with pytest.raises(TypeError):
        bounds.contains([.33, .66])
def test_contains():
    """Test basic contains logic."""
    bounds = CategoricalBounds(categories={"spam", "eggs"})
    assert bounds.contains(CategoricalBounds(categories={"spam"}))
    assert not bounds.contains(CategoricalBounds(categories={"spam", "foo"}))
    assert not bounds.contains(RealBounds(0.0, 2.0, ''))
    assert not bounds.contains(None)
    with pytest.raises(TypeError):
        bounds.contains({"spam", "eggs"})

    from gemd.entity.value import NominalCategorical

    assert bounds.contains(NominalCategorical("spam"))
    assert not bounds.contains(NominalCategorical("foo"))
def test_contains():
    """Test basic contains logic."""
    bounds = CompositionBounds(components={"spam", "eggs"})
    assert bounds.contains(CompositionBounds(components={"spam"}))
    assert not bounds.contains(CompositionBounds(components={"foo"}))
    assert not bounds.contains(RealBounds(0.0, 2.0, ''))
    assert not bounds.contains(None)
    with pytest.raises(TypeError):
        bounds.contains({"spam"})

    from gemd.entity.value import NominalComposition

    assert bounds.contains(NominalComposition({"spam": 0.2, "eggs": 0.8}))
    assert not bounds.contains(NominalComposition({"foo": 1.0}))
def test_contains():
    """Make sure unit conversions are applied to bounds for contains."""
    dim = RealBounds(lower_bound=0, upper_bound=100, default_units="degC")
    dim2 = RealBounds(lower_bound=33, upper_bound=200, default_units="degF")
    assert dim.contains(dim2)

    from gemd.entity.value import NominalReal

    assert dim.contains(NominalReal(5, 'degC'))
    assert not dim.contains(NominalReal(5, 'K'))
Beispiel #22
0
def test_contains():
    """Test basic contains logic."""
    bounds = MolecularStructureBounds()
    assert bounds.contains(MolecularStructureBounds())
    assert not bounds.contains(RealBounds(0.0, 2.0, ''))
    assert not bounds.contains(None)
    with pytest.raises(TypeError):
        bounds.contains('c1(C=O)cc(OC)c(O)cc1')
    with pytest.raises(TypeError):
        bounds.contains(
            'InChI=1/C8H8O3/c1-11-8-4-6(5-9)2-3-7(8)10/h2-5,10H,1H3')

    from gemd.entity.value import Smiles, NominalInteger

    assert bounds.contains(Smiles('c1(C=O)cc(OC)c(O)cc1'))
    assert not bounds.contains(NominalInteger(5))
def test_recursive_foreach():
    """Test that recursive_foreach() applies a method to every object."""
    new_tag = "Extra tag"

    def func(base_ent):
        """Adds a specific tag to the object."""
        base_ent.tags.extend([new_tag])
        return

    param_template = ParameterTemplate("a param template", bounds=RealBounds(0, 100, ''))
    meas_template = MeasurementTemplate("Measurement template", parameters=[param_template])
    parameter = Parameter(name="A parameter", value=NormalReal(mean=17, std=1, units=''))
    measurement = MeasurementSpec(name="name", parameters=parameter, template=meas_template)
    test_dict = {"foo": measurement}
    recursive_foreach(test_dict, func, apply_first=True)

    for ent in [param_template, meas_template, measurement]:
        assert new_tag in ent.tags
def test_recursive_foreach():
    """Test that recursive foreach will actually walk through a material history."""
    mat_run = MaterialRun("foo")
    process_run = ProcessRun("bar")
    IngredientRun(process=process_run, material=mat_run)
    output = MaterialRun("material", process=process_run)

    # property templates are trickier than templates because they are referenced in attributes
    template = PropertyTemplate("prop", bounds=RealBounds(0, 1, ""))
    prop = Property("prop", value=NominalReal(1.0, ""), template=template)
    MeasurementRun("check", material=output, properties=prop)

    types = []
    recursive_foreach(output, lambda x: types.append(x.typ))

    expected = [
        "ingredient_run", "material_run", "material_run", "process_run",
        "measurement_run", "property_template"
    ]
    assert sorted(types) == sorted(expected)
Beispiel #25
0
def make_templates(template_scope=DEMO_TEMPLATE_SCOPE):
    """Build all templates needed for the table."""
    tmpl = dict()

    # Attribute Templates
    attribute_feed = {
        "Formula": [PropertyTemplate,
                    CompositionBounds(components=EmpiricalFormula.all_elements())],
        "Crystallinity": [ConditionTemplate,
                          CategoricalBounds(
                              ['Amorphous', 'Polycrystalline', 'Single crystalline']
                          )],
        "Color": [PropertyTemplate,
                  CategoricalBounds(
                      ['Amber', 'Black', 'Blue', 'Bluish', 'Bronze', 'Brown', 'Brown-Black',
                       'Copper-Red', 'Dark Brown', 'Dark Gray', 'Dark Green', 'Dark Red', 'Gray',
                       'Light Gray', 'Ocher', 'Orange', 'Orange-Red', 'Pale Yellow', 'Red',
                       'Red-Yellow', 'Violet', 'White', 'Yellow', 'Yellow-Orange', 'Yellow-White']
                  )],
        "Band gap": [PropertyTemplate,
                     RealBounds(lower_bound=0.001, upper_bound=100, default_units='eV')],
        "Temperature": [ConditionTemplate,
                        RealBounds(lower_bound=1, upper_bound=1000, default_units='K')],
        "Temperature derivative of band gap": [PropertyTemplate,
                                               RealBounds(lower_bound=-0.01, upper_bound=0.01,
                                                          default_units='eV/K')],
        "Lasing": [PropertyTemplate,
                   CategoricalBounds(['True', 'False'])],
        "Cathodoluminescence": [PropertyTemplate,
                                CategoricalBounds(['True', 'False'])],
        "Mechanical luminescence": [PropertyTemplate,
                                    CategoricalBounds(['True', 'False'])],
        "Photoluminescence": [PropertyTemplate,
                              CategoricalBounds(['True', 'False'])],
        "Electroluminescence": [PropertyTemplate,
                                CategoricalBounds(['True', 'False'])],
        "Thermoluminescence": [PropertyTemplate,
                               CategoricalBounds(['True', 'False'])],
        "Morphology": [ConditionTemplate,
                       CategoricalBounds(['Thin film', 'Bulk'])],
        "Electric field polarization": [ConditionTemplate,
                                        CategoricalBounds(['Parallel to A axis',
                                                           'Parallel to B axis',
                                                           'Parallel to C axis',
                                                           'Perpendicular to B axis',
                                                           'Perpendicular to C axis'])],
        "Phase": [ConditionTemplate,
                  CategoricalBounds(['A', 'B', 'B1', 'B2', 'Fused quartz', 'Natural diamond',
                                     'Rutile', 'Sapphire', 'Synthetic quartz'])],
        "Crystal system": [ConditionTemplate,
                           CategoricalBounds(['Cubic', 'Hexagonal', 'Orthorhombic', 'Tetragonal',
                                              'Trigonal'])],
        "Transition": [ConditionTemplate,
                       CategoricalBounds(['Direct', 'Excitonic', 'Indirect'])],
        "Bands": [ConditionTemplate,
                  CategoricalBounds(['G1 to X1', 'G15 to G1', 'G15 to X1', 'G25 to G1',
                                     'G25 to G12', 'G25 to G15', 'G6 to G8', 'G8 to G6+',
                                     'L6+ to L6-'])]
    }
    for (name, (typ, bounds)) in attribute_feed.items():
        assert name not in tmpl
        tmpl[name] = typ(name=name,
                         bounds=bounds,
                         uids={template_scope: name},
                         tags=['citrine::demo::template::attribute']
                         )

    # Object Templates
    object_feed = {
        "Sample preparation": [
            ProcessTemplate,
            dict()
        ],
        "Chemical": [
            MaterialTemplate,
            {"properties": [tmpl["Formula"]]}
        ],
        "Band gap measurement": [
            MeasurementTemplate,
            {"properties": [tmpl["Band gap"],
                            tmpl["Temperature derivative of band gap"],
                            tmpl["Color"],
                            tmpl["Lasing"],
                            tmpl["Cathodoluminescence"],
                            tmpl["Mechanical luminescence"],
                            tmpl["Photoluminescence"],
                            tmpl["Electroluminescence"],
                            tmpl["Thermoluminescence"]
                            ],
             "conditions": [tmpl["Temperature"],
                            tmpl["Crystallinity"],
                            tmpl["Morphology"],
                            tmpl["Electric field polarization"],
                            tmpl["Phase"],
                            tmpl["Crystal system"],
                            tmpl["Transition"],
                            tmpl["Bands"]
                            ]
             }
        ],
    }
    for (name, (typ, kw_args)) in object_feed.items():
        assert name not in tmpl
        tmpl[name] = typ(name=name,
                         uids={template_scope: name},
                         tags=['citrine::demo::template::object'],
                         **kw_args)

    return tmpl
def test_incompatible_types():
    """Make sure that incompatible types aren't contained or validated."""
    int_bounds = IntegerBounds(0, 1)

    assert not int_bounds.contains(RealBounds(0.0, 1.0, ''))
 XOR(name="terminal name or sample_type",
     headers=["Root", "Info"],
     variables=[
         RootInfo(name="terminal name",
                  headers=["Root", "Name"],
                  field="name"),
         RootInfo(name="terminal name",
                  headers=["Root", "Sample Type"],
                  field="sample_type")
     ]),
 AttributeByTemplate(name="density",
                     headers=["density"],
                     template=LinkByUID(scope="templates", id="density"),
                     attribute_constraints=[[
                         LinkByUID(scope="templates", id="density"),
                         RealBounds(0, 100, "g/cm**3")
                     ]]),
 AttributeByTemplateAfterProcessTemplate(
     name="density",
     headers=["density"],
     attribute_template=LinkByUID(scope="template", id="density"),
     process_template=LinkByUID(scope="template", id="process")),
 AttributeByTemplateAndObjectTemplate(
     name="density",
     headers=["density"],
     attribute_template=LinkByUID(scope="template", id="density"),
     object_template=LinkByUID(scope="template", id="object")),
 AttributeInOutput(
     name="density",
     headers=["density"],
     attribute_template=LinkByUID(scope="template", id="density"),
def test_get_object_id_from_data_concepts_id_is_none():
    template = ConditionTemplate(name='test', bounds=RealBounds(0.0, 1.0, ''))
    template.uids = {'id': None}

    with pytest.raises(ValueError):
        get_object_id(template)
def test_get_object_id_from_data_concepts():
    uid = str(uuid.uuid4())
    template = ConditionTemplate(name='test',
                                 bounds=RealBounds(0.0, 1.0, ''),
                                 uids={'id': uid})
    assert uid == get_object_id(template)
Beispiel #30
0
from citrine.resources.process_template import ProcessTemplate, ProcessTemplateCollection
from citrine.resources.property_template import PropertyTemplate, PropertyTemplateCollection
from citrine.resources.response import Response
from citrine.resources.workflow_executions import WorkflowExecution, WorkflowExecutionStatus

arbitrary_uuid = uuid.uuid4()

resource_string_data = [
    (IngredientRun, {}, "<Ingredient run None>"),
    (IngredientSpec, {'name': 'foo'}, "<Ingredient spec 'foo'>"),
    (MaterialSpec, {'name': 'foo'}, "<Material spec 'foo'>"),
    (MaterialTemplate, {'name': 'foo'}, "<Material template 'foo'>"),
    (MeasurementRun, {'name': 'foo'}, "<Measurement run 'foo'>"),
    (MeasurementSpec, {'name': 'foo'}, "<Measurement spec 'foo'>"),
    (MeasurementTemplate, {'name': 'foo'}, "<Measurement template 'foo'>"),
    (ParameterTemplate, {'name': 'foo', 'bounds': RealBounds(0, 1, '')}, "<Parameter template 'foo'>"),
    (ProcessRun, {'name': 'foo'}, "<Process run 'foo'>"),
    (ProcessSpec, {'name': 'foo'}, "<Process spec 'foo'>"),
    (ProcessTemplate, {'name': 'foo'}, "<Process template 'foo'>"),
    (PropertyTemplate, {'name': 'foo', 'bounds': RealBounds(0, 1, '')}, "<Property template 'foo'>"),
    (ConditionTemplate, {'name': 'foo', 'bounds': RealBounds(0, 1, '')}, "<Condition template 'foo'>"),
    (Response, {'status_code': 200}, "<Response '200'>"),
    (WorkflowExecution,
     {'uid': arbitrary_uuid, 'project_id': arbitrary_uuid, 'workflow_id': arbitrary_uuid, 'version_number': 1},
     "<WorkflowExecution '{}'>".format(arbitrary_uuid)),
    (WorkflowExecutionStatus, {'status': 'Failed', 'session': None}, "<WorkflowExecutionStatus 'Failed'>"),
]

resource_type_data = [
    (IngredientRunCollection, IngredientRun),
    (IngredientSpecCollection, IngredientSpec),