def test_dictionary_substitution():
    """substitute_objects() should substitute LinkByUIDs that occur in dict keys and values."""
    proc = ProcessRun("A process", uids={'id': '123'})
    mat = MaterialRun("A material", uids={'generic id': '38f8jf'})

    proc_link = LinkByUID.from_entity(proc)
    mat_link = LinkByUID.from_entity(mat)
    index = {(mat_link.scope.lower(), mat_link.id): mat,
             (proc_link.scope.lower(), proc_link.id): proc}

    test_dict = {LinkByUID.from_entity(proc): LinkByUID.from_entity(mat)}
    subbed = substitute_objects(test_dict, index)
    k, v = next((k, v) for k, v in subbed.items())
    assert k == proc
    assert v == mat
예제 #2
0
 def make_link(entity: BaseEntity):
     if len(entity.uids) == 0:
         raise ValueError("No UID for {}".format(entity))
     elif native_uid and native_uid in entity.uids:
         return LinkByUID(native_uid, entity.uids[native_uid])
     else:
         return LinkByUID.from_entity(entity)
def test_tuple_sub():
    """substitute_objects() should correctly substitute tuple values."""
    proc = ProcessRun('foo', uids={'id': '123'})
    proc_link = LinkByUID.from_entity(proc)
    index = {(proc_link.scope, proc_link.id): proc}
    tup = (proc_link,)
    subbed = substitute_objects(tup, index)
    assert subbed[0] == proc
예제 #4
0
def test_scope_substitution():
    """Test that the native id gets serialized, when specified."""
    native_id = 'id1'
    # Create measurement and material with two ids
    mat = MaterialRun("A material", uids={
        native_id: str(uuid4()), "an_id": str(uuid4()), "another_id": str(uuid4())})
    meas = MeasurementRun("A measurement", material=mat, uids={
        "some_id": str(uuid4()), native_id: str(uuid4()), "an_id": str(uuid4())})

    # Turn the material pointer into a LinkByUID using native_id
    subbed = substitute_links(meas, scope=native_id)
    assert subbed.material == LinkByUID.from_entity(mat, scope=native_id)

    # Put the measurement into a list and convert that into a LinkByUID using native_id
    measurements_list = [meas]
    subbed = substitute_links(measurements_list, scope=native_id)
    assert subbed == [LinkByUID.from_entity(meas, scope=native_id)]
def test_template_access():
    """A material run's template should be equal to its spec's template."""
    template = MaterialTemplate("material template", uids={'id': str(uuid4())})
    spec = MaterialSpec("A spec", uids={'id': str(uuid4())}, template=template)
    mat = MaterialRun("A run", uids=['id', str(uuid4())], spec=spec)
    assert mat.template == template

    mat.spec = LinkByUID.from_entity(spec)
    assert mat.template is None
예제 #6
0
def test_template_access():
    """A process run's template should be equal to its spec's template."""
    template = ProcessTemplate("process template", uids={'id': str(uuid4())})
    spec = ProcessSpec("A spec", uids={'id': str(uuid4())}, template=template)
    proc = ProcessRun("A run", uids={'id': str(uuid4())}, spec=spec)
    assert proc.template == template

    proc.spec = LinkByUID.from_entity(spec)
    assert proc.template is None
예제 #7
0
def test_object_key_substitution():
    """Test that client can copy a dictionary in which keys are BaseEntity objects."""
    spec = ProcessSpec("A process spec", uids={'id': str(uuid4()), 'auto': str(uuid4())})
    run1 = ProcessRun("A process run", spec=spec, uids={'id': str(uuid4()), 'auto': str(uuid4())})
    run2 = ProcessRun("Another process run", spec=spec, uids={'id': str(uuid4())})
    process_dict = {spec: [run1, run2]}

    subbed = substitute_links(process_dict, scope='auto')
    for key, value in subbed.items():
        assert key == LinkByUID.from_entity(spec, scope='auto')
        assert LinkByUID.from_entity(run1, scope='auto') in value
        assert LinkByUID.from_entity(run2) in value

    reverse_process_dict = {run2: spec}
    subbed = substitute_links(reverse_process_dict, scope='auto')
    for key, value in subbed.items():
        assert key == LinkByUID.from_entity(run2)
        assert value == LinkByUID.from_entity(spec, scope='auto')
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))

    copy = loads(dumps(root))
    assert copy.process.ingredients[0].material == copy.process.ingredients[
        1].material
def test_make_index():
    """Test functionality of make_index method."""
    ps1 = ProcessSpec(name="hello", uids={"test_scope": "test_value"})
    pr1 = ProcessRun(
        name="world",
        spec=LinkByUID(scope="test_scope", id="test_value"),
        uids={"test_scope": "another_test_value",
              "other_test": "also_valid"
              },
    )
    ms1 = MaterialSpec(
        name="material",
        process=LinkByUID(scope="test_scope", id="test_value"),
        uids={"second_scope": "this_is_an_id"},
    )
    mr1 = MaterialRun(
        name="material_run",
        spec=LinkByUID(scope="second_scope", id="this_is_an_id"),
        process=LinkByUID(scope="test_scope", id="another_test_value"),
    )

    pr2 = ProcessRun(
        name="goodbye",
        spec=LinkByUID.from_entity(ps1),
        uids={"test_scope": "the_other_value"},
    )
    mr2 = MaterialRun(
        name="cruel",
        spec=LinkByUID.from_entity(ms1),
        process=LinkByUID.from_entity(pr2),
    )

    gems = [ps1, pr1, ms1, mr1, pr2, mr2]
    gem_index = make_index(gems)
    for gem in gems:
        for scope in gem.uids:
            assert (scope, gem.uids[scope]) in gem_index
            assert gem_index[(scope, gem.uids[scope])] == gem

    # Make sure substitute_objects can consume the index
    subbed = substitute_objects(mr1, gem_index)
    assert subbed.spec.uids == ms1.uids
예제 #10
0
def test_template_access():
    """A measurement run's template should be equal to its spec's template."""
    template = MeasurementTemplate("measurement template",
                                   uids={'id': str(uuid4())})
    spec = MeasurementSpec("A spec",
                           uids={'id': str(uuid4())},
                           template=template)
    meas = MeasurementRun("A run", uids={'id': str(uuid4())}, spec=spec)
    assert meas.template == template

    meas.spec = LinkByUID.from_entity(spec)
    assert meas.template is None
예제 #11
0
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, scope='id'))

    # Paranoid assertions about equality's symmetry since it's implemented in 2 places
    assert root.process.ingredients[0].material == root.process.ingredients[
        1].material
    assert root.process.ingredients[0].material.__eq__(
        root.process.ingredients[1].material)
    assert root.process.ingredients[1].material.__eq__(
        root.process.ingredients[0].material)

    # Verify hash collision on equal LinkByUIDs
    assert LinkByUID.from_entity(leaf) in {LinkByUID.from_entity(leaf)}

    copy = loads(dumps(root))
    assert copy.process.ingredients[0].material == copy.process.ingredients[
        1].material
예제 #12
0
def test_from_entity():
    """Test permutations of LinkByUID.from_entity arguments."""
    run = MaterialRun(name='leaf', process=ProcessRun(name='leaf proc'))
    assert LinkByUID.from_entity(run).scope == 'auto'
    assert LinkByUID.from_entity(run, scope='missing').scope == 'auto'
    assert len(run.uids) == 1

    run.uids['foo'] = 'bar'
    link1 = LinkByUID.from_entity(run, scope='foo')
    assert (link1.scope, link1.id) == ('foo', 'bar')

    with pytest.deprecated_call():
        assert LinkByUID.from_entity(run, 'foo').scope == 'foo'

    with pytest.deprecated_call():
        assert LinkByUID.from_entity(run, name='foo').scope == 'foo'

    with pytest.raises(ValueError):
        LinkByUID.from_entity(run, name='scope1', scope='scope2')
예제 #13
0
def _async_gemd_batch_delete(
        id_list: List[Union[LinkByUID, UUID, str, BaseEntity]],
        project_id: UUID,
        session: Session,
        dataset_id: Optional[UUID],
        timeout: float = 2 * 60,
        polling_delay: float = 1.0) -> List[Tuple[LinkByUID, ApiError]]:
    """
    Shared implementation of Async GEMD Batch deletion.

    See documentation for _gemd_batch_delete. The only difference is that this version polls for
    an asynchronous result and can tolerate a very long runtime that the synchronous version
    cannot. Because this version can tolerate a long runtime, this versions allows for the
    removal of attribute templates.

    Parameters
    ----------
    id_list: List[Union[LinkByUID, UUID, str, BaseEntity]]
        A list of the IDs of data objects to be removed. They can be passed
        as a LinkByUID tuple, a UUID, a string, or the object itself. A UUID
        or string is assumed to be a Citrine ID, whereas a LinkByUID or
        BaseEntity can also be used to provide an external ID.

    project_id: UUID
        The Project ID to use in the delete request.

    session: Session
        The Citrine session.

    dataset_id: Optional[UUID] = None
        An optional dataset ID, which if provided will mandate that all GEMD objects
        must be within the given dataset.

    timeout: float
        Amount of time to wait on the job (in seconds) before giving up. Defaults
        to 2 minutes. Note that this number has no effect on the underlying job
        itself, which can also time out server-side.

    polling_delay: float
        How long to delay between each polling retry attempt.

    Returns
    -------
    List[Tuple[LinkByUID, ApiError]]
        A list of (LinkByUID, api_error) for each failure to delete an object.
        Note that this method doesn't raise an exception if an object fails to be
        deleted.

    """
    scoped_uids = []
    for uid in id_list:  # And now normalize to id/scope pairs
        if isinstance(uid, BaseEntity):
            link_by_uid = LinkByUID.from_entity(uid, CITRINE_SCOPE)
            scoped_uids.append({
                'scope': link_by_uid.scope,
                'id': link_by_uid.id
            })
        elif isinstance(uid, LinkByUID):
            scoped_uids.append({'scope': uid.scope, 'id': uid.id})
        elif isinstance(uid, UUID):
            scoped_uids.append({'scope': 'id', 'id': uid})
        elif isinstance(uid, str):
            try:
                scoped_uids.append({'scope': 'id', 'id': UUID(uid)})
            except ValueError:
                raise TypeError("{} does not look like a UUID".format(uid))
        else:
            raise TypeError(
                "id_list must contain only LinkByUIDs, UUIDs, strings, or BaseEntities"
            )

    body = {'ids': scoped_uids}

    if dataset_id is not None:
        body.update({'dataset_id': str(dataset_id)})

    path = '/projects/{project_id}/gemd/async-batch-delete'.format(
        **{"project_id": project_id})
    response = session.post_resource(path, body)

    job_id = response["job_id"]

    return _poll_for_async_batch_delete_result(project_id, session, job_id,
                                               timeout, polling_delay)