Skip to content
Snippets Groups Projects
Commit 86e2f1ef authored by Alexander Schlemmer's avatar Alexander Schlemmer
Browse files

ENH: implemented support of lists of references

parent 5dea7ae2
No related branches found
No related tags found
2 merge requests!57RELEASE 0.7.3,!52F refactor high level api
Pipeline #19936 failed
...@@ -243,6 +243,12 @@ class CaosDBPythonEntity(object): ...@@ -243,6 +243,12 @@ class CaosDBPythonEntity(object):
ent : db.Entity ent : db.Entity
The entity to be set. The entity to be set.
""" """
if ent.name is None:
raise RuntimeError("Setting properties without name is impossible.")
if ent.name in self.get_properties():
raise RuntimeError("Multiproperty not implemented yet.")
val = self._type_converted_value(ent.value, ent.datatype, val = self._type_converted_value(ent.value, ent.datatype,
references) references)
...@@ -357,7 +363,7 @@ class CaosDBPythonEntity(object): ...@@ -357,7 +363,7 @@ class CaosDBPythonEntity(object):
pr: str pr: str
The datatype according to the database entry. The datatype according to the database entry.
""" """
if not is_list_datatype(pr): if not is_list_datatype(pr) and not isinstance(val, list):
raise RuntimeError("Not a list.") raise RuntimeError("Not a list.")
return [ return [
...@@ -382,6 +388,8 @@ class CaosDBPythonEntity(object): ...@@ -382,6 +388,8 @@ class CaosDBPythonEntity(object):
# case which does not depend on pr # case which does not depend on pr
# TODO: we might need to pass through the reference container # TODO: we might need to pass through the reference container
return convert_to_python_object(val, references) return convert_to_python_object(val, references)
elif isinstance(val, list):
return self._type_converted_list(val, pr, references)
elif pr is None: elif pr is None:
return val return val
elif pr == DOUBLE: elif pr == DOUBLE:
...@@ -491,6 +499,30 @@ class CaosDBPythonEntity(object): ...@@ -491,6 +499,30 @@ class CaosDBPythonEntity(object):
return True return True
return False return False
def _resolve_caosdb_python_unresolved_reference(self, propval, deep,
references, visited):
# This does not make sense for unset ids:
if propval.id is None:
raise RuntimeError("Unresolved property reference without an ID.")
# have we encountered this id before:
if propval.id in visited:
# self.__setattr__(prop, visited[propval.id])
# don't do the lookup in the references container
return visited[propval.id]
# lookup in container:
for ent in references:
# Entities in container without an ID will be skipped:
if ent.id is not None and ent.id == propval.id:
# resolve this entity:
obj = convert_to_python_object(ent, references)
visited[propval.id] = obj
# self.__setattr__(prop, visited[propval.id])
if deep:
obj.resolve_references(deep, references, visited)
return obj
return propval
def resolve_references(self, deep: bool, references: db.Container, def resolve_references(self, deep: bool, references: db.Container,
visited: dict[Union[str, int], visited: dict[Union[str, int],
"CaosDBPythonEntity"] = None): "CaosDBPythonEntity"] = None):
...@@ -520,27 +552,24 @@ class CaosDBPythonEntity(object): ...@@ -520,27 +552,24 @@ class CaosDBPythonEntity(object):
# Resolve all previously unresolved attributes that are entities: # Resolve all previously unresolved attributes that are entities:
if deep and isinstance(propval, CaosDBPythonEntity): if deep and isinstance(propval, CaosDBPythonEntity):
propval.resolve_references(deep, references) propval.resolve_references(deep, references)
elif isinstance(propval, list):
resolvedelements = []
for element in propval:
if deep and isinstance(element, CaosDBPythonEntity):
element.resolve_references(deep, references)
resolvedelements.append(element)
if isinstance(element, CaosDBPythonUnresolvedReference):
resolvedelements.append(
self._resolve_caosdb_python_unresolved_reference(element, deep,
references, visited))
else:
resolvedelements.append(element)
self.__setattr__(prop, resolvedelements)
elif isinstance(propval, CaosDBPythonUnresolvedReference): elif isinstance(propval, CaosDBPythonUnresolvedReference):
# This does not make sense for unset ids: val = self._resolve_caosdb_python_unresolved_reference(propval, deep,
if propval.id is None: references, visited)
raise RuntimeError("Unresolved property reference without an ID.") self.__setattr__(prop, val)
# have we encountered this id before:
if propval.id in visited:
self.__setattr__(prop, visited[propval.id])
# don't do the lookup in the references container
continue
# lookup in container:
for ent in references:
# Entities in container without an ID will be skipped:
if ent.id is not None and ent.id == propval.id:
# resolve this entity:
obj = convert_to_python_object(ent, references)
visited[propval.id] = obj
self.__setattr__(prop, visited[propval.id])
if deep:
obj.resolve_references(deep, references, visited)
break
def get_properties(self): def get_properties(self):
""" """
...@@ -599,6 +628,19 @@ class CaosDBPythonEntity(object): ...@@ -599,6 +628,19 @@ class CaosDBPythonEntity(object):
properties[p] = {"id": val.id, "unresolved": True} properties[p] = {"id": val.id, "unresolved": True}
elif isinstance(val, CaosDBPythonEntity): elif isinstance(val, CaosDBPythonEntity):
properties[p] = val.serialize(without_metadata) properties[p] = val.serialize(without_metadata)
elif isinstance(val, list):
serializedelements = []
for element in val:
if isinstance(element, CaosDBPythonUnresolvedReference):
elm = dict()
elm["id"] = element.id
elm["unresolved"] = True
serializedelements.append(elm)
elif isinstance(element, CaosDBPythonEntity):
serializedelements.append(element.serialize(without_metadata))
else:
serializedelements.append(element)
properties[p] = serializedelements
else: else:
properties[p] = val properties[p] = val
...@@ -717,7 +759,9 @@ def _single_convert_to_entity(entity: db.Entity, ...@@ -717,7 +759,9 @@ def _single_convert_to_entity(entity: db.Entity,
propval = _single_convert_to_entity( propval = _single_convert_to_entity(
standard_type_for_high_level_type(propval)(), propval) standard_type_for_high_level_type(propval)(), propval)
elif isinstance(propval, list): elif isinstance(propval, list):
raise NotImplementedError() # propval = []
if not isinstance(propval[0], int):
raise NotImplementedError()
entity.add_property( entity.add_property(
name=prop, name=prop,
...@@ -768,3 +812,44 @@ def convert_to_python_object(entity: Union[db.Container, db.Entity], ...@@ -768,3 +812,44 @@ def convert_to_python_object(entity: Union[db.Container, db.Entity],
return _single_convert_to_python_object( return _single_convert_to_python_object(
high_level_type_for_standard_type(entity)(), entity, references) high_level_type_for_standard_type(entity)(), entity, references)
def new_high_level_entity_for_record_type(entity: db.RecordType,
importance_level: str,
name: str = None,
deep: bool = True,
references: Optional[db.Container] = None):
"""
Create an new record in high level format based on a record type in standard format.
entity: db.RecordType
The record type to initialize the new record from.
importance_level: str
None, obligatory, recommended or suggested
Initialize new properties up to this level.
Properties in the record type with no importance will be added
regardless of the importance_level.
name: str
Name of the new record.
"""
r = db.Record(name=name)
r.add_parent(entity)
impmap = {
None: 0, "SUGGESTED": 3, "RECOMMENDED": 2, "OBLIGATORY": 1}
for prop in entity.properties:
imp = entity.get_importance(prop)
if imp is not None and impmap[importance_level] < impmap[imp]:
continue
r.add_property(prop)
if deep:
raise NotImplementedError("Recursive creation is not possible at the moment.")
return convert_to_python_object(r)
...@@ -25,12 +25,17 @@ ...@@ -25,12 +25,17 @@
import caosdb as db import caosdb as db
from caosdb.high_level_api import (convert_to_entity, convert_to_python_object) from caosdb.high_level_api import (convert_to_entity, convert_to_python_object,
new_high_level_entity_for_record_type)
from caosdb.high_level_api import (CaosDBPythonUnresolvedParent, from caosdb.high_level_api import (CaosDBPythonUnresolvedParent,
CaosDBPythonUnresolvedReference, CaosDBPythonUnresolvedReference,
CaosDBPythonRecord, CaosDBPythonFile) CaosDBPythonRecord, CaosDBPythonFile)
from caosdb.apiutils import compare_entities from caosdb.apiutils import compare_entities
from caosdb.common.datatype import (is_list_datatype,
get_list_datatype,
is_reference)
import pytest import pytest
from lxml import etree from lxml import etree
import os import os
...@@ -396,3 +401,111 @@ def test_files(): ...@@ -396,3 +401,111 @@ def test_files():
assert p.datatype == db.FILE assert p.datatype == db.FILE
assert p.value.file == "/local/path/test.dat" assert p.value.file == "/local/path/test.dat"
assert p.value.path == "test.dat" assert p.value.path == "test.dat"
@pytest.mark.xfail
def test_record_generator():
rt = db.RecordType(name="Simulation")
rt.add_property(name="a", datatype=db.INTEGER)
rt.add_property(name="b", datatype=db.DOUBLE)
rt.add_property(name="inputfile", datatype=db.FILE)
simrt = db.RecordType(name="SimOutput")
rt.add_property(name="outputfile", datatype="SimOutput")
obj = new_high_level_entity_for_record_type(
rt, "SUGGESTED", "", True)
print(obj)
assert False
def test_list_types():
r = db.Record()
r.add_property(name="a", value=[1, 2, 4])
assert get_list_datatype(r.get_property("a").datatype) is None
obj = convert_to_python_object(r)
assert type(obj.a) == list
assert len(obj.a) == 3
assert 4 in obj.a
assert obj.get_property_metadata("a").datatype is None
conv = convert_to_entity(obj)
prop = r.get_property("a")
assert prop.value == [1, 2, 4]
assert prop.datatype is None
r.get_property("a").datatype = db.LIST(db.INTEGER)
assert r.get_property("a").datatype == "LIST<INTEGER>"
obj = convert_to_python_object(r)
assert type(obj.a) == list
assert len(obj.a) == 3
assert 4 in obj.a
assert obj.get_property_metadata("a").datatype == "LIST<INTEGER>"
conv = convert_to_entity(obj)
prop = r.get_property("a")
assert prop.value == [1, 2, 4]
assert obj.get_property_metadata("a").datatype == "LIST<INTEGER>"
# List of referenced objects:
r = db.Record()
r.add_property(name="a", value=[1, 2, 4], datatype="LIST<TestReference>")
obj = convert_to_python_object(r)
assert type(obj.a) == list
assert len(obj.a) == 3
assert obj.get_property_metadata("a").datatype == "LIST<TestReference>"
for i in range(3):
assert type(obj.a[i]) == CaosDBPythonUnresolvedReference
assert obj.a == [CaosDBPythonUnresolvedReference(id=i) for i in [1, 2, 4]]
# Try resolving:
# Should not work:
obj.resolve_references(False, db.Container())
assert type(obj.a) == list
assert len(obj.a) == 3
assert obj.get_property_metadata("a").datatype == "LIST<TestReference>"
for i in range(3):
assert type(obj.a[i]) == CaosDBPythonUnresolvedReference
assert obj.a == [CaosDBPythonUnresolvedReference(id=i) for i in [1, 2, 4]]
references = db.Container()
for i in [1, 2, 4]:
ref = db.Record(id=i)
ref.add_property(name="val", value=str(i) + " bla")
references.append(ref)
obj.resolve_references(False, references)
assert type(obj.a) == list
assert len(obj.a) == 3
assert obj.get_property_metadata("a").datatype == "LIST<TestReference>"
for i in range(3):
assert type(obj.a[i]) == CaosDBPythonRecord
assert obj.a[0].val == "1 bla"
# Conversion with embedded records:
r2 = db.Record()
r2.add_property(name="a", value=4)
r3 = db.Record()
r3.add_property(name="b", value=8)
r = db.Record()
r.add_property(name="a", value=[r2, r3])
obj = convert_to_python_object(r)
assert type(obj.a) == list
assert len(obj.a) == 2
assert obj.a[0].a == 4
assert obj.a[1].b == 8
# Serialization
text = str(obj)
text2 = str(convert_to_python_object(r2)).split("\n")
print(text)
# cut away first two characters in text
text = [line[4:] for line in text.split("\n")]
for line in text2:
assert line in text
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment