diff --git a/src/caosdb/apiutils.py b/src/caosdb/apiutils.py index 391edfed8231a09bd2014ac321e830bc1477919b..a4c4c1f9d67235b71fae65139d612d5bcbac7f3f 100644 --- a/src/caosdb/apiutils.py +++ b/src/caosdb/apiutils.py @@ -58,16 +58,20 @@ def new_record(record_type, name=None, description=None, r = Record(name) r.add_parent(rt) + if tempid is not None: r.id = tempid + if description is not None: r.description = description # Add all additional properties, treat iterables als multiple # additions. + for k, v in kwargs.items(): if hasattr(v, "encode") or not isinstance(v, Iterable): v = [v] + for vv in v: p = Property(k) p.retrieve() @@ -76,17 +80,21 @@ def new_record(record_type, name=None, description=None, if insert: r.insert() + return r def get_type_of_entity_with(id_): objs = Query("FIND Entity WHICH HAS id={id_}".format(id_=id_)).execute() + if len(objs) == 0: raise RuntimeError("ID {} not found.".format(id_)) + if len(objs) > 1: raise RuntimeError( "ID {} is not unique. This is probably a bug in the CaosDB server." .format(id_)) obj = objs[0] + if isinstance(obj, Record): return Record elif isinstance(obj, RecordType): @@ -125,6 +133,7 @@ class CaosDBPythonEntity(object): @staticmethod def _get_id(): CaosDBPythonEntity._last_id -= 1 + return CaosDBPythonEntity._last_id def _set_property_from_entity(self, ent): @@ -140,34 +149,41 @@ class CaosDBPythonEntity(object): overwrite: Use this if you definitely only want one property with that name (set to True). """ self._datatypes[name] = datatype + if isinstance(name, Entity): name = name.name + if name in self._forbidden: raise RuntimeError("Entity cannot be converted to a corresponding " "Python representation. Name of property " + name + " is forbidden!") already_exists = (name in dir(self)) + if already_exists and not overwrite: # each call to _set_property checks first if it already exists # if yes: Turn the attribute into a list and # place all the elements into that list. att = self.__getattribute__(name) + if isinstance(att, list): pass else: old_att = self.__getattribute__(name) self.__setattr__(name, [old_att]) + if is_reference: self._references[name] = [ self._references[name]] att = self.__getattribute__(name) att.append(value) + if is_reference: self._references[name].append(int(value)) else: if is_reference: self._references[name] = value self.__setattr__(name, value) + if not (already_exists and overwrite): self._properties.add(name) @@ -181,6 +197,7 @@ class CaosDBPythonEntity(object): prrealpre = pr.replace("<", "<").replace(">", ">") prreal = prrealpre[prrealpre.index("<") + 1:prrealpre.rindex(">")] lst = [self._type_converted_value(i, prreal) for i in val] + return ([i[0] for i in lst], lst[0][1]) def _type_converted_value(self, val, pr): @@ -191,6 +208,7 @@ class CaosDBPythonEntity(object): - the converted value - True if the value has to be interpreted as an id acting as a reference """ + if val is None: return (None, False) elif pr == DOUBLE: @@ -219,6 +237,7 @@ class CaosDBPythonEntity(object): element are indistinguishable from simple types in this representation.""" att = self.__getattribute__(name) + if isinstance(att, list): return att else: @@ -237,10 +256,12 @@ class CaosDBPythonEntity(object): def get_parent_names(self): new_plist = [] + for p in self._parents: obj_type = get_type_of_entity_with(p) ent = obj_type(id=p).retrieve() new_plist.append(ent.name) + return new_plist def resolve_references(self, deep=False, visited=dict()): @@ -249,24 +270,28 @@ class CaosDBPythonEntity(object): for j in range(len(self._references[i])): new_id = self._references[i][j] obj_type = get_type_of_entity_with(new_id) + if new_id in visited: new_object = visited[new_id] else: ent = obj_type(id=new_id).retrieve() new_object = convert_to_python_object(ent) visited[new_id] = new_object + if deep: new_object.resolve_references(deep, visited) self.__getattribute__(i)[j] = new_object else: new_id = self._references[i] obj_type = get_type_of_entity_with(new_id) + if new_id in visited: new_object = visited[new_id] else: ent = obj_type(id=new_id).retrieve() new_object = convert_to_python_object(ent) visited[new_id] = new_object + if deep: new_object.resolve_references(deep, visited) self.__setattr__(i, new_object) @@ -276,13 +301,16 @@ class CaosDBPythonEntity(object): result = str(self.__class__.__name__) + "\n" else: result = name + "\n" + for p in self._properties: value = self.__getattribute__(p) + if isinstance(value, CaosDBPythonEntity): result += indent * "\t" + \ value.__str__(indent=indent + 1, name=p) else: result += indent * "\t" + p + "\n" + return result @@ -306,29 +334,38 @@ class CaosDBPythonFile(CaosDBPythonEntity): def _single_convert_to_python_object(robj, entity): robj._id = entity.id + for i in entity.properties: robj._set_property_from_entity(i) + for i in entity.parents: robj._add_parent(i) + if entity.path is not None: robj._path = entity.path + if entity.file is not None: robj._file = entity.file # if entity.pickup is not None: # robj.pickup = entity.pickup + return robj def _single_convert_to_entity(entity, robj, **kwargs): if robj._id is not None: entity.id = robj._id + if robj._path is not None: entity.path = robj._path + if robj._file is not None: entity.file = robj._file + if robj.pickup is not None: entity.pickup = robj.pickup children = [] + for parent in robj._parents: if sys.version_info[0] < 3: if hasattr(parent, "encode"): @@ -344,9 +381,11 @@ def _single_convert_to_entity(entity, robj, **kwargs): def add_property(entity, prop, name, recursive=False, datatype=None): if datatype is None: raise ArgumentError("datatype must not be None") + if isinstance(prop, CaosDBPythonEntity): entity.add_property(name=name, value=str( prop._id), datatype=datatype) + if recursive and not prop.do_not_expand: return convert_to_entity(prop, recursive=recursive) else: @@ -355,16 +394,20 @@ def _single_convert_to_entity(entity, robj, **kwargs): if isinstance(prop, float) or isinstance(prop, int): prop = str(prop) entity.add_property(name=name, value=prop, datatype=datatype) + return [] for prop in robj._properties: value = robj.__getattribute__(prop) + if isinstance(value, list): if robj._datatypes[prop][0:4] == "LIST": lst = [] + for v in value: if isinstance(v, CaosDBPythonEntity): lst.append(v._id) + if recursive and not v.do_not_expand: children.append(convert_to_entity( v, recursive=recursive)) @@ -392,12 +435,14 @@ def _single_convert_to_entity(entity, robj, **kwargs): prop, datatype=robj._datatypes[prop], **kwargs)) + return [entity] + children def convert_to_entity(python_object, **kwargs): if isinstance(python_object, Container): # Create a list of objects: + return [convert_to_python_object(i, **kwargs) for i in python_object] elif isinstance(python_object, CaosDBPythonRecord): return _single_convert_to_entity(Record(), python_object, **kwargs) @@ -415,8 +460,10 @@ def convert_to_entity(python_object, **kwargs): def convert_to_python_object(entity): """""" + if isinstance(entity, Container): # Create a list of objects: + return [convert_to_python_object(i) for i in entity] elif isinstance(entity, Record): return _single_convert_to_python_object(CaosDBPythonRecord(), entity) @@ -485,64 +532,80 @@ COMPARED = ["name", "role", "datatype", "description", "importance"] def compare_entities(old_entity, new_entity): + olddiff = {} + newdiff = {} description = "" - # if old_entity is new_entity: - # return description + + if old_entity is new_entity: + return (olddiff, newdiff) + for attr in COMPARED: try: - old_entity.__getattribute__(attr) - r1_attr_exists = True + oldattr = old_entity.__getattribute__(attr) + old_entity_attr_exists = True except BaseException: - r1_attr_exists = False + old_entity_attr_exists = False + oldattr = None try: - new_entity.__getattribute__(attr) - r2_attr_exists = True + newattr =new_entity.__getattribute__(attr) + new_entity_attr_exists = True except BaseException: - r2_attr_exists = False - if r1_attr_exists ^ r2_attr_exists: - description += attr + "only exists in one\n" - continue - if not r1_attr_exists and not r2_attr_exists: + new_entity_attr_exists = False + newattr = None + + if not old_entity_attr_exists and not new_entity_attr_exists: continue + + if old_entity_attr_exists ^ new_entity_attr_exists: + olddiff[attr] = old_entity.__getattribute__(attr) + newdiff[attr] = old_entity.__getattribute__(attr) + if old_entity.__getattribute__(attr) != new_entity.__getattribute__(attr): description += attr + " differs:\n" description += str(old_entity.__getattribute__(attr)) + "\n" description += str(new_entity.__getattribute__(attr)) + "\n" # properties + if (len(old_entity.properties) > 0) ^ (len(new_entity.properties) > 0): description += "only one has properties\n" else: for prop in old_entity.properties: matching = [p for p in new_entity.properties if p.name == prop.name] + if len(matching) == 0: description += "new_entity is missing the property '" + prop.name + "'\n" elif len(matching) == 1: if (old_entity.get_importance(prop.name) != new_entity.get_importance(prop.name)): description += "importance of '" + prop.name + "' differs\n" + if ((prop.datatype is not None and matching[0].datatype is not None) and (prop.datatype != matching[0].datatype)): description += "datatype of '" + prop.name + "' differs\n" else: raise NotImplementedError() + for prop in new_entity.properties: if len([0 for p in old_entity.properties if p.name == prop.name]) == 0: description += "old_entity is missing the property '" + prop.name + "'\n" # parents + if ((len(old_entity.parents) > 0) ^ (len(new_entity.parents) > 0)): description += "only one has parents\n" else: for par in old_entity.parents: matching = [p for p in new_entity.parents if p.name == par.name] + if len(matching) == 0: description += "new_entity is missing the parent '" + par.name + "'\n" elif len(matching) == 1: description += compare_entities(par, matching[0]) else: raise NotImplementedError() + for par in new_entity.parents: if len([0 for p in old_entity.parents if p.name == par.name]) == 0: description += "old_entity is missing the parent '" + par.name + "'\n"