diff --git a/src/caosdb/high_level_api.py b/src/caosdb/high_level_api.py index 21e0b19a26e9a85b464b3defb59f4cf443eb8bd7..365cd1331a7a88d0931ecab8e4d3ec79e28563c2 100644 --- a/src/caosdb/high_level_api.py +++ b/src/caosdb/high_level_api.py @@ -31,15 +31,16 @@ This is refactored from apiutils. """ from caosdb.common.datatype import (BOOLEAN, DATETIME, DOUBLE, FILE, INTEGER, - REFERENCE, TEXT) + REFERENCE, TEXT, is_list_datatype, get_list_datatype) import caosdb as db from .apiutils import get_type_of_entity_with -from typing import Any, Optional +from typing import Any, Optional, List, Union, Dict from dataclasses import dataclass + @dataclass class CaosDBPropertyMetaData: # name is already the name of the attribute @@ -79,38 +80,42 @@ class CaosDBPythonEntity(object): """ Initialize a new CaosDBPythonEntity for the high level python api. - The new entity is initialized with a new negative ID using _get_id (static). - """ - - # Save a copy of the dry state - # of this object in order to be - # able to detect conflicts. - self.do_not_expand = False - self._parents = [] - self._id = CaosDBPythonEntity._get_id() - self._path = None - self._file = None - - # TODO: - # 3.) resolve references up to a specific depth (including infinity) - # 4.) resolve parents function -> partially implemented by - # get_parent_names - - self._references = {} - self._properties = set() - self._datatypes = {} - self._forbidden = dir(self) + Parents are either unresolved references or CaosDB RecordTypes. - @staticmethod - def _get_id(): + Properties are stored directly as attributes for the object. + Property metadata is maintained in a dctionary _properties_metadata that should + never be accessed directly, but only using the get_property_metadata function. + If property values are references to other objects, they will be stored as + CaosDBPythonUnresolvedReference objects that can be resolved later into + CaosDBPythonRecords. """ - Get a new negative ID for a CaosDB Entity. - The first ID is -1 and decremented with each call of this function. - """ - CaosDBPythonEntity._last_id -= 1 + # Parents are either unresolved references or CaosDB RecordTypes + self._parents: List[Union[ + CaosDBPythonUnresolvedParent, CaosDBPythonRecordType]] = [] + # self._id: int = CaosDBPythonEntity._get_new_id() + self._id: Optional[int] = None + self._name: Optional[str] = None + self._description: Optional[str] = None + self._version: Optional[str] = None + + # name: name of property, value: property metadata + self._properties_metadata: Dict[CaosDBPropertyMetaData] = dict() - return CaosDBPythonEntity._last_id + # Store all current attributes as forbidden attributes + # which must not be changed by the set_property function. + self._forbidden = dir(self) + ["_forbidden"] + + # @staticmethod + # def _get_new_id(): + # """ + # Get a new negative ID for a CaosDB Entity. + + # The first ID is -1 and decremented with each call of this function. + # """ + # CaosDBPythonEntity._last_id -= 1 + + # return CaosDBPythonEntity._last_id def _set_property_from_entity(self, ent: db.Entity): """ @@ -126,10 +131,37 @@ class CaosDBPythonEntity(object): val, reference = self._type_converted_value(val, pr) self.set_property(name, val, reference, datatype=pr) + def get_property_metadata(self, prop_name: str) -> CaosDBPropertyMetaData: + """ + Retrieve the property metadata for the property with name prop_name. + + If the property with the given name does not exist or is forbidden, raise an exception. + Else return the metadata associated with this property. + + If no metadata does exist yet for the given property, a new object will be created + and returned. + + prop_name: str + Name of the property to retrieve metadata for. + """ + + if not self.property_exists(prop_name): + raise RuntimeError("The property with name {} does not exist.".format(prop_name)) + + if prop_name not in self._properties_metadata: + self._properties_metadata[prop_name] = CaosDBPropertyMetaData() + + return self._properties_metadata[prop_name] + + def property_exists(self, prop_name: str): + """ + Check whether a property exists already. + """ + return prop_name not in self._forbidden and prop_name in self.__dict__ + def set_property(self, name: str, value: Any, - is_reference: bool = False, overwrite: bool = False, datatype: Optional[str] = None): """ @@ -144,8 +176,6 @@ class CaosDBPythonEntity(object): value: Any Value of the property. - - is_reference: bool overwrite: bool Use this if you definitely only want one property with @@ -156,8 +186,9 @@ class CaosDBPythonEntity(object): # Store the datatype (if given) in a hidden field # The datatypes will be stored in name, value pairs. - # TODO: check whether handling of lists is correct. - self._datatypes[name] = datatype + # TODO: check whether handling of lists and multi properties is correct. + metadata = self.get_property_metadata(name) + metadata.datatype = datatype if isinstance(name, db.Entity): # TODO: check, whether this is reasonable? @@ -169,7 +200,7 @@ class CaosDBPythonEntity(object): "Python representation. Name of property " + name + " is forbidden!") - already_exists = (name in dir(self)) + already_exists = self.property_exists(name) if already_exists and not overwrite: # each call to set_property checks first if it already exists @@ -183,24 +214,12 @@ class CaosDBPythonEntity(object): 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) - - add_property = set_property + # add_property = set_property def set_id(self, idx: int): """