diff --git a/src/caoscrawler/identifiable_adapters.py b/src/caoscrawler/identifiable_adapters.py index b89e985472a818c41f91732ea7fda0f0244b512a..06ce43979908e8fc1716068bc936e6d53b605d59 100644 --- a/src/caoscrawler/identifiable_adapters.py +++ b/src/caoscrawler/identifiable_adapters.py @@ -33,6 +33,16 @@ from .utils import has_parent logger = logging.getLogger(__name__) +def class Identifiable(): + def __init__(self, record_type, name=None, properties=None, backrefs=None): + self.record_type = record_type + self.name = name + if properties is None: + self.properties = [] + if backrefs is None: + self.backrefs = [] + + def convert_value(value): """ Returns a string representation of the value that is suitable to be used in the query @@ -86,7 +96,7 @@ class IdentifiableAdapter(metaclass=ABCMeta): """ @staticmethod - def create_query_for_identifiable(ident: db.Record): + def create_query_for_identifiable(ident: Identifiable): """ This function is taken from the old crawler: caosdb-advanced-user-tools/src/caosadvancedtools/crawler.py @@ -94,41 +104,29 @@ class IdentifiableAdapter(metaclass=ABCMeta): uses the properties of ident to create a query that can determine whether the required record already exists. """ - - if len(ident.parents) != 1: - raise RuntimeError( - "Multiple parents for identifiables not supported.") - - query_string = "FIND Record " + ident.get_parents()[0].name - if ident.get_property("is_referenced_by") is not None: - query_string += (" WHICH IS REFERENCED BY " - + ident.get_property("is_referenced_by").value + " AND") + query_string = "FIND Record " + ident.record_type + for ref in ident.backrefs: + query_string += (" WHICH IS REFERENCED BY " + ref + " AND") query_string += " WITH " - if ident.name is None and len(ident.get_properties()) == 0: - raise ValueError( - "The identifiable must have features to identify it.") - if ident.name is not None: query_string += "name='{}'".format(ident.name) - if len(ident.get_properties()) > 0: + if len(ident.properties) > 0: query_string += " AND " query_string += IdentifiableAdapter.create_property_query(ident) return query_string @staticmethod - def create_property_query(entity: db.Entity): + def create_property_query(entity: Identifiable): query_string = "" - for p in entity.get_properties(): - if p.name == "is_referenced_by": - continue - elif p.value is None: - query_string += "'" + p.name + "' IS NULL AND " - elif isinstance(p.value, list): - for v in p.value: - query_string += ("'" + p.name + "'='" + + for pname, pvalue in entity.properties.items(): + if pvalue is None: + query_string += "'" + pname + "' IS NULL AND " + elif isinstance(pvalue, list): + for v in pvalue: + query_string += ("'" + pname + "'='" + convert_value(v) + "' AND ") # TODO: (for review) @@ -142,8 +140,8 @@ class IdentifiableAdapter(metaclass=ABCMeta): # IdentifiableAdapter.create_property_query(p.value) + # ") AND ") else: - query_string += ("'" + p.name + "'='" + - convert_value(p.value) + "' AND ") + query_string += ("'" + pname + "'='" + + convert_value(pvalue) + "' AND ") # remove the last AND return query_string[:-4] @@ -185,8 +183,6 @@ class IdentifiableAdapter(metaclass=ABCMeta): identifiable.path = record.path return identifiable - # ------------------------- - # TODO: Review usage of referencing_entities def get_identifiable(self, record: db.Record, referencing_entities=None): """ retrieve the registred identifiable and fill the property values to create an @@ -196,24 +192,19 @@ class IdentifiableAdapter(metaclass=ABCMeta): if record.role == "File": return self.get_identifiable_for_file(record) - # TODO: refactoring for backref - # Change this function to return a new datastructure (object) containting: - # a list of properties - # the parent name - # a (list of) backref(s) registered_identifiable = self.get_registered_identifiable(record) if registered_identifiable is None: return None if referencing_entities is None: referencing_entities = {} - identifiable = db.Record(name=record.name) if len(registered_identifiable.parents) != 1: raise RuntimeError("Multiple parents for identifiables" "not supported.") - identifiable.add_parent(registered_identifiable.parents[0]) property_name_list_A = [] property_name_list_B = [] + identifiable_props = {} + identifiable_backrefs = [] # fill the values: for prop in registered_identifiable.properties: @@ -230,7 +221,7 @@ class IdentifiableAdapter(metaclass=ABCMeta): rtname = self.get_recordtype_name(prop.value) if (id(record) in referencing_entities and rtname in referencing_entities[id(record)]): - identifiable.add_property("is_referenced_by", referencing_entities[rtname]) + identifiable_backrefs.append(referencing_entities[rtname]) continue record_prop = record.get_property(prop.name) if record_prop is None: @@ -240,14 +231,7 @@ class IdentifiableAdapter(metaclass=ABCMeta): f"The following record is missing an identifying property:" f"RECORD\n{record}\nIdentifying PROPERTY\n{prop.name}" ) - newval = record_prop.value - record_prop_new = db.Property(name=record_prop.name, - id=record_prop.id, - description=record_prop.description, - datatype=record_prop.datatype, - value=newval, - unit=record_prop.unit) - identifiable.add_property(record_prop_new) + identifiable_props[record_prop.name] = record_prop.value property_name_list_A.append(prop.name) # check for multi properties in the record: @@ -258,7 +242,11 @@ class IdentifiableAdapter(metaclass=ABCMeta): raise RuntimeError( "Multi properties used in identifiables can cause unpredictable results.") - return identifiable + return Identifiable(registered_identifiable.parents[0], + name=record.name, + properties=identifiable_props, + backrefs=identifiable_backrefs + ) @abstractmethod def retrieve_identified_record_for_identifiable(self, identifiable: db.Record, @@ -292,8 +280,7 @@ class IdentifiableAdapter(metaclass=ABCMeta): if identifiable.role == "File": return self.get_file(identifiable) - return self.retrieve_identified_record_for_identifiable( - identifiable, referencing_entities=referencing_entities) + return self.retrieve_identified_record_for_identifiable(identifiable) class LocalStorageIdentifiableAdapter(IdentifiableAdapter): @@ -404,8 +391,7 @@ class LocalStorageIdentifiableAdapter(IdentifiableAdapter): return False return True - def retrieve_identified_record_for_identifiable(self, identifiable: - db.Record, referencing_entities=None): + def retrieve_identified_record_for_identifiable(self, identifiable: Identifiable): candidates = [] for record in self._records: if self.check_record(record, identifiable): @@ -490,9 +476,8 @@ class CaosDBIdentifiableAdapter(IdentifiableAdapter): return record return record.id - def retrieve_identified_record_for_identifiable(self, identifiable: db.Record, - referencing_entities=None): - query_string = self.create_query_for_identifiable(identifiable, referencing_entities) + def retrieve_identified_record_for_identifiable(self, identifiable: Identifiable): + query_string = self.create_query_for_identifiable(identifiable) candidates = db.execute_query(query_string) if len(candidates) > 1: raise RuntimeError(