diff --git a/src/caosadvancedtools/cfood.py b/src/caosadvancedtools/cfood.py index 3ec1b6ed7ef5263db8349419225ca3ebf20f52fa..8d387beacd4d889bd5107ea30194c10a446b1e79 100644 --- a/src/caosadvancedtools/cfood.py +++ b/src/caosadvancedtools/cfood.py @@ -39,6 +39,7 @@ treat the match. This occurs in basically three steps: import logging import re +import warnings from abc import ABCMeta, abstractmethod import caosdb as db @@ -367,7 +368,8 @@ def assure_object_is_in_list(obj, containing_object, property_name, datatype=datatype) if not isinstance(containing_object.get_property(property_name).value, list): - containing_object.get_property(property_name).value = [containing_object.get_property(property_name).value] + containing_object.get_property(property_name).value = [ + containing_object.get_property(property_name).value] containing_object.get_property(property_name).datatype = datatype current_list = containing_object.get_property(property_name).value @@ -394,11 +396,11 @@ def assure_object_is_in_list(obj, containing_object, property_name, if contained: logger.debug("{} is in {} of entity {}".format( - o, property_name, containing_object.id)) + o, property_name, containing_object.id)) else: logger.debug("UPDATE: Appending {} to {} of entity {}".format( - o, property_name, containing_object.id)) + o, property_name, containing_object.id)) current_list.append(o) update = True @@ -446,7 +448,7 @@ def assure_has_parent(entity, parent, to_be_updated=None, contained = False for el in parents: - if el.name == parent: + if el.name.lower() == parent.lower(): contained = True break @@ -463,9 +465,6 @@ def assure_has_parent(entity, parent, to_be_updated=None, if to_be_updated is None: get_ids_for_entities_with_names([entity]) - # TODO move the unique argument? - # TODO find a better way then force? - if force: entity.update(unique=unique) else: @@ -474,18 +473,81 @@ def assure_has_parent(entity, parent, to_be_updated=None, to_be_updated.append(entity) -def assure_has_property(entity, name, value, to_be_updated=None, - datatype=None): +def assure_parents_are(entity, parents, to_be_updated=None, + force=False, unique=True): """ - Checks whether `entity` has a property `name` with the value `value`. + Checks whether `entity` has the provided parents (and only those). If this is the case this function ends. Otherwise the entity is assigned - a new parent. The list to_be_updated is supplied, the entity is added to + the new parents and the old ones are discarded. + + Note that parent matching occurs based on names. + + If the list to_be_updated is supplied, the entity is added to the list in order to indicate, that the entity `entity` should be updated. Otherwise it is directly updated + + parents: single string or list of strings + """ + + if not isinstance(parents, list): + parents = [parents] + + for i, e in enumerate(parents): + if isinstance(e, db.Entity): + if e.name is None: + raise Exception("Entity should have name") + parents[i] = e.name + + if ([p.name.lower() for p in entity.get_parents()] + == [p.lower() for p in parents]): + + logger.debug("entity {} has parents {}".format(entity.id, parents)) + + return + + logger.debug("UPDATE: Adding parent {} to entity {}".format(parents, + entity.id)) + + while len(entity.parents) > 0: + entity.parents.pop() + + for parent in parents: + entity.add_parent(parent) + + if to_be_updated is None: + get_ids_for_entities_with_names([entity]) + + if force: + entity.update(unique=unique) + else: + guard.safe_update(entity, unique=unique) + else: + to_be_updated.append(entity) + + +def assure_has_property(entity, name, value, to_be_updated=None, + datatype=None, setproperty=False): + """Checks whether `entity` has a property `name` with the value + `value`. + + If this is the case this function ends. Otherwise the entity is + assigned a new parent. + + Note that property matching occurs based on names. + + If the list to_be_updated is supplied, the entity is added to the + list in order to indicate, that the entity `entity` should be + updated. Otherwise it is directly updated + + setproperty: boolean, if True, overwrite existing properties. + """ if name.lower() == "description": + warnings.warn("Do not use assure_has_property with 'description'. " + "Use assure_has_description.", DeprecationWarning) + if entity.description == value: return else: @@ -508,6 +570,10 @@ def assure_has_property(entity, name, value, to_be_updated=None, name.lower()] contained = False + if setproperty and len(possible_properties) > 1: + raise ValueError("Trying to set the property value of {} but more" + " than one such properties exist.".format(name)) + if isinstance(value, db.Entity): value = value.id @@ -519,7 +585,7 @@ def assure_has_property(entity, name, value, to_be_updated=None, if contained: logger.debug("entity {} has property {} with value {}".format( - entity.id, name, value)) + entity.id, name, value)) return @@ -527,6 +593,9 @@ def assure_has_property(entity, name, value, to_be_updated=None, "UPDATE: Adding property {} with value {} to entity {}".format( name, value, entity.id)) + if setproperty and possible_properties: + entity.properties.remove(possible_properties[0]) + if datatype is None: entity.add_property(name=name, value=value) else: @@ -540,6 +609,23 @@ def assure_has_property(entity, name, value, to_be_updated=None, to_be_updated.append(entity) +def assure_property_is(entity, name, value, datatype=None, to_be_updated=None, + force=False): + """ + Checks whether `entity` has a Property `name` with the given value. + + If this is the case this function ends. Otherwise the entity is assigned + a new property or an existing one is updated. + + If the list to_be_updated is supplied, the entity is added to + the list in order to indicate, that the entity `entity` should be updated. + Otherwise it is directly updated + """ + + assure_has_property(entity, name, value, to_be_updated=to_be_updated, + datatype=datatype, setproperty=True) + + def insert_id_based_on_name(entity): if entity.name is not None and (entity.id is None or entity.id < 0): if isinstance(entity, db.Property): diff --git a/unittests/test_cfood.py b/unittests/test_cfood.py index 8b75b82f445a0800d42f9c5d1b3e4f3f8d41d128..0545a44784e8d571aebcbc759a2b8a51eb7cea0b 100644 --- a/unittests/test_cfood.py +++ b/unittests/test_cfood.py @@ -28,6 +28,7 @@ import caosdb as db from caosadvancedtools.cfood import (AbstractCFood, AbstractFileCFood, CMeal, assure_has_parent, assure_has_property, assure_object_is_in_list, + assure_parents_are, assure_property_is, get_entity_for_path) from caosadvancedtools.crawler import FileCrawler from caosadvancedtools.example_cfood import ExampleCFood @@ -156,6 +157,64 @@ class InsertionTest(unittest.TestCase): value=new_int, to_be_updated=to_be_updated) assert to_be_updated[0] is entity + def test_property_is(self): + """Test properties with string, int, float, and Boolean values""" + entity = db.Record() + to_be_updated = [] + int_name = "Test int" + types_and_values = { + int_name: ("INT", 5), + "Test float": (db.DOUBLE, 3.14), + "Test bool": (db.BOOLEAN, True), + "Test string": (db.TEXT, "bla") + } + + for name, ty_val in types_and_values.items(): + entity.add_property(name=name, datatype=ty_val[0], + value=ty_val[1]) + assure_property_is(entity=entity, name=name, + value=ty_val[1], to_be_updated=to_be_updated) + assert len(to_be_updated) == 0 + + # Test overwriting existing values + types_and_values = { + int_name: ("INT", 7), + "Test float": (db.DOUBLE, 3.34), + "Test bool": (db.BOOLEAN, False), + "Test string": (db.TEXT, "blasdkfjlj") + } + + for i, (name, ty_val) in enumerate(types_and_values.items()): + assure_property_is(entity=entity, name=name, + value=ty_val[1], to_be_updated=to_be_updated) + assert len(to_be_updated) == i+1 + assert entity.get_property(name).value == ty_val[1] + entity.add_property(name="Test float", datatype=db.DOUBLE, value=3.2) + self.assertRaises(Exception, assure_property_is, entity=entity, + name="Test float", + value=4.2, to_be_updated=to_be_updated) + + def test_parents_are(self): + entity = db.Record() + to_be_updated = [] + assure_parents_are(entity, "parent", to_be_updated) + assert to_be_updated[0] is entity + assure_parents_are(entity, "parent", to_be_updated) + assert len(to_be_updated) == 1 + assure_parents_are(entity, ["parent", "other_parent"], to_be_updated) + assert len(to_be_updated) == 2 + ps = [p.name for p in entity.get_parents()] + assert "parent" in ps + assert "other_parent" in ps + assure_parents_are(entity, ["parent", "other_parent"], to_be_updated) + assert len(to_be_updated) == 2 + assure_parents_are(entity, "yet_another_parent", to_be_updated) + assert len(to_be_updated) == 3 + ps = [p.name for p in entity.get_parents()] + assert "yet_another_parent" in ps + assert "parent" not in ps + assert "other_parent" not in ps + class DependendTest(unittest.TestCase): def test(self):