diff --git a/src/caosdb/high_level_api.py b/src/caosdb/high_level_api.py index 1fa458d5426182c00c470d656fc0d2d4466e3867..8facccff02e825ed7e186cab09eb89ce799e31a3 100644 --- a/src/caosdb/high_level_api.py +++ b/src/caosdb/high_level_api.py @@ -41,8 +41,11 @@ from .apiutils import get_type_of_entity_with from typing import Any, Optional, List, Union, Dict +import yaml + from dataclasses import dataclass, fields from datetime import datetime +from dateutil import parser @dataclass class CaosDBPropertyMetaData: @@ -101,6 +104,9 @@ class CaosDBPythonEntity(object): self._description: Optional[str] = None self._version: Optional[str] = None + self._file: Optional[str] = None + self._path: Optional[str] = None + # name: name of property, value: property metadata self._properties_metadata: Dict[CaosDBPropertyMetaData] = dict() @@ -130,6 +136,28 @@ class CaosDBPythonEntity(object): def name(self, val: str): self._name = val + @property + def file(self): + """ + Getter for the file. + """ + return self._file + + @name.setter + def file(self, val: str): + self._file = val + + @property + def path(self): + """ + Getter for the path. + """ + return self._path + + @name.setter + def path(self, val: str): + self._path = val + @property def description(self): """ @@ -148,6 +176,10 @@ class CaosDBPythonEntity(object): """ return self._version + @version.setter + def version(self, val: str): + self._version = val + # @staticmethod # def _get_new_id(): # """ @@ -159,7 +191,7 @@ class CaosDBPythonEntity(object): # return CaosDBPythonEntity._last_id - def _set_property_from_entity(self, ent: db.Entity): + def _set_property_from_entity(self, ent: db.Entity, importance: str): """ Set a new property using an entity from the normal python API. @@ -168,14 +200,17 @@ class CaosDBPythonEntity(object): """ val = self._type_converted_value(ent.value, ent.datatype) - metadata = self.get_property_metadata(ent.name) - for prop_name in fields(metadata): - k = prop_name.name - metadata.__setattr__(k, ent.__getattribute__(k)) self.set_property( ent.name, val, datatype=ent.datatype) + metadata = self.get_property_metadata(ent.name) + for prop_name in fields(metadata): + k = prop_name.name + if k == "importance": + metadata.importance = importance + else: + metadata.__setattr__(k, ent.__getattribute__(k)) def get_property_metadata(self, prop_name: str) -> CaosDBPropertyMetaData: """ @@ -322,8 +357,7 @@ class CaosDBPythonEntity(object): """ if isinstance(val, datetime): return val - # TODO: try different representations - return datetime.strptime("%Y-%m-%d %H:%M:%S", val) + return parser.parse(val) def get_property(self, name: str): """ @@ -352,12 +386,18 @@ class CaosDBPythonEntity(object): return [att] def add_parent(self, parent: Union[ - CaosDBPythonUnresolvedParent, "CaosDBPythonRecordType"]): + CaosDBPythonUnresolvedParent, "CaosDBPythonRecordType", str]): """ Add a parent to this entity. Either using an unresolved parent or using a real record type. + + Strings as argument for parent will automatically be converted to an + unresolved parent. """ + if isinstance(parent, str): + parent = CaosDBPythonUnresolvedParent(name=parent) + if self.has_parent(parent): raise RuntimeError("Duplicate parent.") self._parents.append(parent) @@ -374,16 +414,6 @@ class CaosDBPythonEntity(object): return True return False - # 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()): for i in self._references: if isinstance(self._references[i], list): @@ -416,22 +446,59 @@ class CaosDBPythonEntity(object): new_object.resolve_references(deep, visited) self.__setattr__(i, new_object) - def __str__(self, indent=1, name=None): - if name is None: - result = str(self.__class__.__name__) + "\n" - else: - result = name + "\n" + def get_properties(self): + """ + Return the names of all properties. + """ - for p in self._properties: - value = self.__getattribute__(p) + return [p for p in self.__dict__ + if p not in self._forbidden] + - if isinstance(value, CaosDBPythonEntity): - result += indent * "\t" + \ - value.__str__(indent=indent + 1, name=p) + def serialize(self, without_metadata: bool = False): + """ + Serialize necessary information into a dict. + """ + metadata: dict[str, Any] = dict() + properties = dict() + parents = list() + + for parent in self._parents: + if isinstance(parent, CaosDBPythonEntity): + parents.append(parent.serialize()) + elif isinstance(parent, CaosDBPythonUnresolvedParent): + parents.append({"name": parent.name, "id": parent.id, + "unresolved": True}) + else: + raise RuntimeError("Incompatible class used as parent.") + + for p in self.get_properties(): + m = self.get_property_metadata(p) + metadata[p] = dict() + for f in fields(m): + metadata[p][f.name] = m.__getattribute__(f.name) + + val = self.get_property(p) + if isinstance(val, CaosDBPythonUnresolvedReference): + properties[p] = {"id": val.id, "unresolved": True} + elif isinstance(val, CaosDBPythonEntity): + properties[p] = val.serialize(without_metadata) else: - result += indent * "\t" + p + "\n" + properties[p] = val + if without_metadata: + return { + "properties": properties, + "parents": parents} + return { + "metadata": metadata, + "properties": properties, + "parents": parents} - return result + def __str__(self): + return yaml.dump(self.serialize(False)) + + def __repr__(self): + return yaml.dump(self.serialize(True)) class CaosDBPythonRecord(CaosDBPythonEntity): @@ -452,20 +519,22 @@ class CaosDBPythonFile(CaosDBPythonEntity): self._file = f.download(target) -def _single_convert_to_python_object(robj, entity): - robj._id = entity.id - - for i in entity.properties: - robj._set_property_from_entity(i) +def _single_convert_to_python_object(robj: CaosDBPythonEntity, + entity: db.Entity): + robj.id = entity.id + robj.name = entity.name + robj.description = entity.description + robj.version = entity.version - for i in entity.parents: - robj._add_parent(i) + for prop in entity.properties: + robj._set_property_from_entity(prop, entity.get_importance(prop)) - if entity.path is not None: - robj._path = entity.path + for parent in entity.parents: + robj.add_parent(CaosDBPythonUnresolvedParent(id=parent.id, + name=parent.name)) - if entity.file is not None: - robj._file = entity.file + robj.path = entity.path + robj.file = entity.file return robj diff --git a/unittests/test_high_level_api.py b/unittests/test_high_level_api.py index 8796046908ba24973a33574baaa4cd281453a9eb..b6d2f28e5fcb47735369117b4e3903fea6e3319a 100644 --- a/unittests/test_high_level_api.py +++ b/unittests/test_high_level_api.py @@ -1,5 +1,3 @@ -# -*- encoding: utf-8 -*- -# # This file is a part of the CaosDB Project. # # Copyright (C) 2018 Research Group Biomedical Physics, @@ -72,4 +70,6 @@ def test_convert_record(): obj = convert_to_python_object(r) assert obj.a == 42 assert obj.b == "test" - breakpoint() + + with pytest.raises(RuntimeError): + obj.add_parent("bla")