diff --git a/data/src/entity_manager/structures.py b/data/src/entity_manager/structures.py index e2526d331a..f4fde90d39 100644 --- a/data/src/entity_manager/structures.py +++ b/data/src/entity_manager/structures.py @@ -4620,7 +4620,7 @@ def _load_lazy(self, name): # attribute is not going to be "lazy loaded" again) return attribute - def _load_lazy_attr(self, name): + def _load_lazy_attr(self, name, force = False): """ Loads a lazy loaded base attribute, this will be used in entities that have been loaded using polymorphism and for @@ -4629,12 +4629,20 @@ def _load_lazy_attr(self, name): Loading the attribute will trigger a concrete loading of the entity values and all of them will be updated. + An optional flag may be used to force the setting of already + defined/set attributes (from the upper layers in hierarchy). + This method accesses the data source so it's considered to be an "expensive" operation. @type name: String @param name: The name of the attribute to be used as reference in the loading of the concrete class (lazy load). + @type force: bool + @param force: If the setting of attributes should be forced + meaning that attributes that are already set in the entity + may be overriden by new values from data source, this is + valid for the upper layers of the class hierarchy. @rtype: Object @return: The value for the attribute that triggered the concrete load of the class (lazy load). @@ -4682,11 +4690,13 @@ def _load_lazy_attr(self, name): # in case the current name in iteration is # not present in the (new) entity no need to # retrieve it and set it in the entity - if not _entity.has_value(name): - # continues the loop the name is not present - # in the (new) entity it's impossible to update - # it in the entity - continue + if not _entity.has_value(name): continue + + # in case the value is already set in the base + # no duplicate setting should occur (would possibly + # revert changed to entity), note that in case the + # force flag is set the value is set the same way + if self.has_value(name) and not force: continue # retrieves the value for the current # name in the (new) entity and sets it in diff --git a/data/src/entity_manager/test.py b/data/src/entity_manager/test.py index a3c8b119ba..0ff038f769 100644 --- a/data/src/entity_manager/test.py +++ b/data/src/entity_manager/test.py @@ -1306,6 +1306,60 @@ def test_validate_relation(self): valid_relation = self.entity_manager.validate_relation(person, "parent") self.assertEqual(valid_relation, True) + def test_lazy_attribute(self): + # creates the required entity classes in the data source + self.entity_manager.create(mocks.Person) + self.entity_manager.create(mocks.Dog) + + # creates a person entity with it's default attributes and + # saves it into the data source + person = mocks.Person() + person.object_id = 1 + person.status = 1 + person.name = "name_person" + self.entity_manager.save(person) + + # verifies that the data remains unchanged after + # the saving (persistence) + self.assertEqual(person.object_id, 1) + self.assertEqual(person.status, 1) + self.assertEqual(person.name, "name_person") + + # retrieves the person using a polymorphic query so + # that only the root entity fields are populated and + # then verifies that the result is valid/set + saved_person = self.entity_manager.get(mocks.RootEntity, 1) + self.assertNotEqual(saved_person, None) + + # detaches the person from the data source and verifies + # that the root entity level fields are set but the person + # level ones are not (polymorphic query) + saved_person.detach() + self.assertEqual(saved_person.object_id, 1) + self.assertEqual(saved_person.status, 1) + self.assertEqual(saved_person.name, None) + + # attaches the person back to the data source (enabling lazy + # attribute evaluation) and verifies that the person level + # attributes are now accessible and that the top level ones + # (root entity) remain the same after lazy attribute evaluation + saved_person.attach() + saved_person.status = 2 + self.assertEqual(saved_person.name, "name_person") + self.assertEqual(saved_person.status, 2) + + # runs the original test one more time be using the root entity + # level of retrieval and then forces the loading of the lazy + # attribute (should populate also upper layers) and verifies + # that the status value is returned to the original value because + # of the forced loading of the upper layer attributes + saved_person = self.entity_manager.get(mocks.RootEntity, 1) + self.assertNotEqual(saved_person, None) + saved_person.status = 2 + saved_person._load_lazy_attr("name", force = True) + self.assertEqual(saved_person.name, "name_person") + self.assertEqual(saved_person.status, 1) + def test_abstract(self): # creates the required entity classes in the data source self.entity_manager.create(mocks.RootEntityAbstract)