Source code for aiida.backends.tests.orm.implementation.test_nodes

# -*- coding: utf-8 -*-
###########################################################################
# Copyright (c), The AiiDA team. All rights reserved.                     #
# This file is part of the AiiDA code.                                    #
#                                                                         #
# The code is hosted on GitHub at https://github.com/aiidateam/aiida-core #
# For further information on the license, see the LICENSE.txt file        #
# For further information please visit http://www.aiida.net               #
###########################################################################
# pylint: disable=too-many-public-methods
"""Unit tests for the BackendNode and BackendNodeCollection classes."""
from __future__ import division
from __future__ import print_function
from __future__ import absolute_import

from collections import OrderedDict
from datetime import datetime
from uuid import UUID

from aiida.backends.testbase import AiidaTestCase
from aiida.common import timezone
from aiida.common import exceptions


[docs]class TestBackendNode(AiidaTestCase): """Test BackendNode."""
[docs] @classmethod def setUpClass(cls, *args, **kwargs): super(TestBackendNode, cls).setUpClass(*args, **kwargs) cls.computer = cls.computer.backend_entity # Unwrap the `Computer` instance to `BackendComputer` cls.user = cls.backend.users.create(email='tester@localhost').store()
[docs] def setUp(self): super(TestBackendNode, self).setUp() self.node_type = '' self.node_label = 'label' self.node_description = 'description' self.node = self.backend.nodes.create( node_type=self.node_type, user=self.user, computer=self.computer, label=self.node_label, description=self.node_description )
[docs] def create_node(self): return self.backend.nodes.create(node_type=self.node_type, user=self.user)
[docs] def test_creation(self): """Test creation of a BackendNode and all its properties.""" node = self.backend.nodes.create( node_type=self.node_type, user=self.user, label=self.node_label, description=self.node_description ) # Before storing self.assertIsNone(node.id) self.assertIsNone(node.pk) self.assertTrue(isinstance(node.uuid, str)) self.assertTrue(isinstance(node.ctime, datetime)) self.assertIsNone(node.mtime) self.assertIsNone(node.process_type) self.assertEqual(node.attributes, dict()) self.assertEqual(node.extras, dict()) self.assertEqual(node.node_type, self.node_type) self.assertEqual(node.label, self.node_label) self.assertEqual(node.description, self.node_description) # Store the node.ctime before the store as a reference now = timezone.now() node_ctime_before_store = node.ctime self.assertTrue(now > node.ctime, '{} is not smaller than now {}'.format(node.ctime, now)) node.store() node_ctime = node.ctime node_mtime = node.mtime # The node.ctime should have been unchanged, but the node.mtime should have changed self.assertEqual(node.ctime, node_ctime_before_store) self.assertIsNotNone(node.mtime) self.assertTrue(now < node.mtime, '{} is not larger than now {}'.format(node.mtime, now)) # After storing self.assertTrue(isinstance(node.id, int)) self.assertTrue(isinstance(node.pk, int)) self.assertTrue(isinstance(node.uuid, str)) self.assertTrue(isinstance(node.ctime, datetime)) self.assertTrue(isinstance(node.mtime, datetime)) self.assertIsNone(node.process_type) self.assertEqual(node.attributes, dict()) self.assertEqual(node.extras, dict()) self.assertEqual(node.node_type, self.node_type) self.assertEqual(node.label, self.node_label) self.assertEqual(node.description, self.node_description) # Try to construct a UUID from the UUID value to prove that it has a valid UUID UUID(node.uuid) # Change a column, which should trigger the save, update the mtime but leave the ctime untouched node.label = 'test' self.assertEqual(node.ctime, node_ctime) self.assertTrue(node.mtime > node_mtime)
[docs] def test_creation_with_time(self): """ Test creation of a BackendNode when passing the mtime and the ctime. The passed ctime and mtime should be respected since it is important for the correct import of nodes at the AiiDA import/export. """ from aiida.tools.importexport.dbimport.backends.utils import deserialize_attributes ctime = deserialize_attributes('2019-02-27T16:20:12.245738', 'date') mtime = deserialize_attributes('2019-02-27T16:27:14.798838', 'date') node = self.backend.nodes.create( node_type=self.node_type, user=self.user, label=self.node_label, description=self.node_description, mtime=mtime, ctime=ctime ) # Check that the ctime and mtime are the given ones self.assertEqual(node.ctime, ctime) self.assertEqual(node.mtime, mtime) node.store() # Check that the given values remain even after storing self.assertEqual(node.ctime, ctime) self.assertEqual(node.mtime, mtime)
[docs] def test_mtime(self): """Test the `mtime` is automatically updated when a database field is updated.""" node = self.node.store() node_mtime = node.mtime node.label = 'changed label' self.assertTrue(node.mtime > node_mtime)
[docs] def test_clone(self): """Test the `clone` method.""" node = self.node.store() clone = node.clone() # Check that the clone is unstored, i.e. has *no* id, has a different UUID, but all other props are the same self.assertIsNone(clone.id) self.assertNotEqual(clone.uuid, node.uuid) self.assertEqual(clone.label, node.label) self.assertEqual(clone.description, node.description) self.assertEqual(clone.user.id, node.user.id) self.assertEqual(clone.computer.id, node.computer.id) self.assertEqual(clone.attributes, node.attributes) self.assertEqual(clone.extras, node.extras)
[docs] def test_property_setters(self): """Test the property setters of a BackendNode.""" label = 'new label' description = 'new description' self.node.label = label self.node.description = description self.assertEqual(self.node.label, label) self.assertEqual(self.node.description, description)
[docs] def test_computer_methods(self): """Test the computer methods of a BackendNode.""" new_computer = self.backend.computers.create(name='localhost2', hostname='localhost').store() self.assertEqual(self.node.computer.id, self.computer.id) self.node.computer = new_computer self.assertEqual(self.node.computer.id, new_computer.id)
[docs] def test_user_methods(self): """Test the user methods of a BackendNode.""" new_user = self.backend.users.create(email='newuser@localhost').store() self.assertEqual(self.node.user.id, self.user.id) self.node.user = new_user self.assertEqual(self.node.user.id, new_user.id)
[docs] def test_get_set_attribute(self): """Test the `get_attribute` and `set_attribute` method of a BackendNode.""" attribute_1_name = 'a' attribute_2_name = 'b' attribute_3_name = 'c' attribute_1_value = '1' attribute_2_value = '2' attribute_3_value = '3' with self.assertRaises(AttributeError): self.node.get_attribute(attribute_1_name) self.assertFalse(self.node.is_stored) self.node.set_attribute(attribute_1_name, attribute_1_value) # Check that the attribute is set, but the node is not stored self.assertFalse(self.node.is_stored) self.assertEqual(self.node.get_attribute(attribute_1_name), attribute_1_value) self.node.store() # Check that the attribute is set, and the node is stored self.assertTrue(self.node.is_stored) self.assertEqual(self.node.get_attribute(attribute_1_name), attribute_1_value) self.node.set_attribute(attribute_2_name, attribute_2_value) self.assertEqual(self.node.get_attribute(attribute_1_name), attribute_1_value) self.assertEqual(self.node.get_attribute(attribute_2_name), attribute_2_value) reloaded = self.backend.nodes.get(self.node.pk) self.assertEqual(self.node.get_attribute(attribute_1_name), attribute_1_value) self.assertEqual(self.node.get_attribute(attribute_2_name), attribute_2_value) reloaded.set_attribute(attribute_3_name, attribute_3_value) self.assertEqual(reloaded.get_attribute(attribute_1_name), attribute_1_value) self.assertEqual(reloaded.get_attribute(attribute_2_name), attribute_2_value) self.assertEqual(reloaded.get_attribute(attribute_3_name), attribute_3_value) # Check deletion of a single reloaded.delete_attribute(attribute_1_name) with self.assertRaises(AttributeError): reloaded.get_attribute(attribute_1_name) self.assertEqual(reloaded.get_attribute(attribute_2_name), attribute_2_value) self.assertEqual(reloaded.get_attribute(attribute_3_name), attribute_3_value) with self.assertRaises(AttributeError): self.node.get_attribute(attribute_1_name)
[docs] def test_get_set_extras(self): """Test the `get_extras` and `set_extras` method of a BackendNode.""" extra_1_name = 'a' extra_2_name = 'b' extra_3_name = 'c' extra_1_value = '1' extra_2_value = '2' extra_3_value = '3' with self.assertRaises(AttributeError): self.node.get_extra(extra_1_name) self.assertFalse(self.node.is_stored) self.node.set_extra(extra_1_name, extra_1_value) # Check that the extra is set, but the node is not stored self.assertFalse(self.node.is_stored) self.assertEqual(self.node.get_extra(extra_1_name), extra_1_value) self.node.store() # Check that the extra is set, and the node is stored self.assertTrue(self.node.is_stored) self.assertEqual(self.node.get_extra(extra_1_name), extra_1_value) self.node.set_extra(extra_2_name, extra_2_value) self.assertEqual(self.node.get_extra(extra_1_name), extra_1_value) self.assertEqual(self.node.get_extra(extra_2_name), extra_2_value) reloaded = self.backend.nodes.get(self.node.pk) self.assertEqual(self.node.get_extra(extra_1_name), extra_1_value) self.assertEqual(self.node.get_extra(extra_2_name), extra_2_value) reloaded.set_extra(extra_3_name, extra_3_value) self.assertEqual(reloaded.get_extra(extra_1_name), extra_1_value) self.assertEqual(reloaded.get_extra(extra_2_name), extra_2_value) self.assertEqual(reloaded.get_extra(extra_3_name), extra_3_value) # Check deletion of a single reloaded.delete_extra(extra_1_name) with self.assertRaises(AttributeError): reloaded.get_extra(extra_1_name) self.assertEqual(reloaded.get_extra(extra_2_name), extra_2_value) self.assertEqual(reloaded.get_extra(extra_3_name), extra_3_value) with self.assertRaises(AttributeError): self.node.get_extra(extra_1_name)
[docs] def test_attributes(self): """Test the `BackendNode.attributes` property.""" node = self.create_node() self.assertEqual(node.attributes, {}) node.set_attribute('attribute', 'value') self.assertEqual(node.attributes, {'attribute': 'value'}) node.store() self.assertEqual(node.attributes, {'attribute': 'value'})
[docs] def test_get_attribute(self): """Test the `BackendNode.get_attribute` method.""" node = self.create_node() with self.assertRaises(AttributeError): node.get_attribute('attribute') node.set_attribute('attribute', 'value') self.assertEqual(node.get_attribute('attribute'), 'value') node.store() self.assertEqual(node.get_attribute('attribute'), 'value')
[docs] def test_get_attribute_many(self): """Test the `BackendNode.get_attribute_many` method.""" node = self.create_node() with self.assertRaises(AttributeError): node.get_attribute_many(['attribute']) node.set_attribute_many({'attribute': 'value', 'another': 'case'}) with self.assertRaises(AttributeError): node.get_attribute_many(['attribute', 'unexisting']) self.assertEqual(node.get_attribute_many(['attribute', 'another']), ['value', 'case']) node.store() self.assertEqual(node.get_attribute_many(['attribute', 'another']), ['value', 'case'])
[docs] def test_set_attribute(self): """Test the `BackendNode.set_attribute` method.""" node = self.create_node() # When not stored, `set_attribute` will not clean values, so the following should be allowed node.set_attribute('attribute_invalid', object()) node.set_attribute('attribute_valid', 'value') # Calling store should cause the values to be cleaned which should raise with self.assertRaises(exceptions.ValidationError): node.store() # Replace the original invalid with a valid value node.set_attribute('attribute_invalid', 'actually valid') node.store() self.assertEqual(node.get_attribute_many(['attribute_invalid', 'attribute_valid']), ['actually valid', 'value']) # Raises immediately when setting it if already stored with self.assertRaises(exceptions.ValidationError): node.set_attribute('attribute', object())
[docs] def test_set_attribute_many(self): """Test the `BackendNode.set_attribute_many` method.""" # Calling `set_attribute_many` on an unstored node node = self.create_node() # When not stored, `set_attribute` will not clean values, so the following should be allowed node.set_attribute_many({'attribute_invalid': object(), 'attribute_valid': 'value'}) # Calling store should cause the values to be cleaned which should raise with self.assertRaises(exceptions.ValidationError): node.store() # Replace the original invalid with a valid value node.set_attribute_many({'attribute_invalid': 'actually valid'}) node.store() self.assertEqual(node.get_attribute_many(['attribute_invalid', 'attribute_valid']), ['actually valid', 'value']) attributes = OrderedDict() attributes['another_attribute'] = 'value' attributes['attribute_invalid'] = object() # Raises immediately when setting it if already stored with self.assertRaises(exceptions.ValidationError): node.set_attribute_many(attributes) self.assertTrue('another_attribute' not in node.attributes) attributes = {'attribute_one': 1, 'attribute_two': 2} # Calling `set_attribute_many` on a stored node node = self.create_node() node.store() node.set_attribute_many(attributes) self.assertEqual(node.attributes, attributes)
[docs] def test_reset_attributes(self): """Test the `BackendNode.reset_attributes` method.""" node = self.create_node() attributes_before = {'attribute_one': 1, 'attribute_two': 2} attributes_after = {'attribute_three': 3, 'attribute_four': 4} # Reset attributes on an unstored node node.set_attribute_many(attributes_before) self.assertEqual(node.attributes, attributes_before) node.reset_attributes(attributes_after) self.assertEqual(node.attributes, attributes_after) # Reset attributes on stored node node = self.create_node() node.store() node.set_attribute_many(attributes_before) self.assertEqual(node.attributes, attributes_before) node.reset_attributes(attributes_after) self.assertEqual(node.attributes, attributes_after)
[docs] def test_delete_attribute(self): """Test the `BackendNode.delete_attribute` method.""" node = self.create_node() with self.assertRaises(AttributeError): node.delete_attribute('notexisting') node.set_attribute('attribute', 'value') node.delete_attribute('attribute') self.assertEqual(node.attributes, {}) # Now for a stored node node = self.create_node().store() with self.assertRaises(AttributeError): node.delete_attribute('notexisting') node.set_attribute('attribute', 'value') node.delete_attribute('attribute') self.assertEqual(node.attributes, {})
[docs] def test_delete_attribute_many(self): """Test the `BackendNode.delete_attribute_many` method.""" node = self.create_node() attributes = {'attribute_one': 1, 'attribute_two': 2} with self.assertRaises(AttributeError): node.delete_attribute_many(['notexisting', 'some']) node.set_attribute_many(attributes) with self.assertRaises(AttributeError): node.delete_attribute_many(['attribute_one', 'notexisting']) # Because one key failed during delete, none of the attributes should have been deleted self.assertTrue('attribute_one' in node.attributes) # Now delete the keys that actually should exist node.delete_attribute_many(attributes.keys()) self.assertEqual(node.attributes, {}) # Now for a stored node node = self.create_node().store() with self.assertRaises(AttributeError): node.delete_attribute_many(['notexisting', 'some']) node.set_attribute_many(attributes) with self.assertRaises(AttributeError): node.delete_attribute_many(['attribute_one', 'notexisting']) # Because one key failed during delete, none of the attributes should have been deleted self.assertTrue('attribute_one' in node.attributes) # Now delete the keys that actually should exist node.delete_attribute_many(attributes.keys()) self.assertEqual(node.attributes, {})
[docs] def test_clear_attributes(self): """Test the `BackendNode.clear_attributes` method.""" node = self.create_node() attributes = {'attribute_one': 1, 'attribute_two': 2} node.set_attribute_many(attributes) self.assertEqual(node.attributes, attributes) node.clear_attributes() self.assertEqual(node.attributes, {}) # Now for a stored node node = self.create_node().store() node.set_attribute_many(attributes) self.assertEqual(node.attributes, attributes) node.clear_attributes() self.assertEqual(node.attributes, {})
[docs] def test_attribute_items(self): """Test the `BackendNode.attribute_items` generator.""" node = self.create_node() attributes = {'attribute_one': 1, 'attribute_two': 2} node.set_attribute_many(attributes) self.assertEqual(attributes, dict(node.attributes_items())) # Repeat for a stored node node = self.create_node().store() attributes = {'attribute_one': 1, 'attribute_two': 2} node.set_attribute_many(attributes) self.assertEqual(attributes, dict(node.attributes_items()))
[docs] def test_attribute_keys(self): """Test the `BackendNode.attribute_keys` generator.""" node = self.create_node() attributes = {'attribute_one': 1, 'attribute_two': 2} node.set_attribute_many(attributes) self.assertEqual(set(attributes), set(node.attributes_keys())) # Repeat for a stored node node = self.create_node().store() attributes = {'attribute_one': 1, 'attribute_two': 2} node.set_attribute_many(attributes) self.assertEqual(set(attributes), set(node.attributes_keys()))
[docs] def test_attribute_flush_specifically(self): """Test that changing `attributes` only flushes that property and does not affect others like extras. Regression fix for #3338 """ node = self.create_node().store() extras = {'extra_one': 1, 'extra_two': 2} node.set_extra_many(extras) node.store() # Load the stored node in memory in another instance and add another extra reloaded = self.backend.nodes.get(node.pk) reloaded.set_extra('extra_three', 3) # The original memory instance should now not include `extra_three`. Changing an attribute should only flush # the attributes and not the whole node state, which would override for example also the extras. node.set_attribute('attribute_one', 1) # Reload the node yet again and verify that the `extra_three` extra is still there rereloaded = self.backend.nodes.get(node.pk) self.assertIn('extra_three', rereloaded.extras.keys())
[docs] def test_extras(self): """Test the `BackendNode.extras` property.""" node = self.create_node() self.assertEqual(node.extras, {}) node.set_extra('extra', 'value') self.assertEqual(node.extras, {'extra': 'value'}) node.store() self.assertEqual(node.extras, {'extra': 'value'})
[docs] def test_get_extra(self): """Test the `BackendNode.get_extra` method.""" node = self.create_node() with self.assertRaises(AttributeError): node.get_extra('extra') node.set_extra('extra', 'value') self.assertEqual(node.get_extra('extra'), 'value') node.store() self.assertEqual(node.get_extra('extra'), 'value')
[docs] def test_get_extra_many(self): """Test the `BackendNode.get_extra_many` method.""" node = self.create_node() with self.assertRaises(AttributeError): node.get_extra_many(['extra']) node.set_extra_many({'extra': 'value', 'another': 'case'}) with self.assertRaises(AttributeError): node.get_extra_many(['extra', 'unexisting']) self.assertEqual(node.get_extra_many(['extra', 'another']), ['value', 'case']) node.store() self.assertEqual(node.get_extra_many(['extra', 'another']), ['value', 'case'])
[docs] def test_set_extra(self): """Test the `BackendNode.set_extra` method.""" node = self.create_node() # When not stored, `set_extra` will not clean values, so the following should be allowed node.set_extra('extra_invalid', object()) node.set_extra('extra_valid', 'value') # Calling store should cause the values to be cleaned which should raise with self.assertRaises(exceptions.ValidationError): node.store() # Replace the original invalid with a valid value node.set_extra('extra_invalid', 'actually valid') node.store() self.assertEqual(node.get_extra_many(['extra_invalid', 'extra_valid']), ['actually valid', 'value']) # Raises immediately when setting it if already stored with self.assertRaises(exceptions.ValidationError): node.set_extra('extra', object())
[docs] def test_set_extra_many(self): """Test the `BackendNode.set_extra_many` method.""" # Calling `set_extra_many` on an unstored node node = self.create_node() # When not stored, `set_extra` will not clean values, so the following should be allowed node.set_extra_many({'extra_invalid': object(), 'extra_valid': 'value'}) # Calling store should cause the values to be cleaned which should raise with self.assertRaises(exceptions.ValidationError): node.store() # Replace the original invalid with a valid value node.set_extra_many({'extra_invalid': 'actually valid'}) node.store() self.assertEqual(node.get_extra_many(['extra_invalid', 'extra_valid']), ['actually valid', 'value']) extras = OrderedDict() extras['another_extra'] = 'value' extras['extra_invalid'] = object() # Raises immediately when setting it if already stored with self.assertRaises(exceptions.ValidationError): node.set_extra_many(extras) self.assertTrue('another_extra' not in node.extras) extras = {'extra_one': 1, 'extra_two': 2} # Calling `set_extra_many` on a stored node node = self.create_node() node.store() node.set_extra_many(extras) self.assertEqual(node.extras, extras)
[docs] def test_reset_extras(self): """Test the `BackendNode.reset_extras` method.""" node = self.create_node() extras_before = {'extra_one': 1, 'extra_two': 2} extras_after = {'extra_three': 3, 'extra_four': 4} # Reset extras on an unstored node node.set_extra_many(extras_before) self.assertEqual(node.extras, extras_before) node.reset_extras(extras_after) self.assertEqual(node.extras, extras_after) # Reset extras on stored node node = self.create_node() node.store() node.set_extra_many(extras_before) self.assertEqual(node.extras, extras_before) node.reset_extras(extras_after) self.assertEqual(node.extras, extras_after)
[docs] def test_delete_extra(self): """Test the `BackendNode.delete_extra` method.""" node = self.create_node() with self.assertRaises(AttributeError): node.delete_extra('notexisting') node.set_extra('extra', 'value') node.delete_extra('extra') self.assertEqual(node.extras, {}) # Now for a stored node node = self.create_node().store() with self.assertRaises(AttributeError): node.delete_extra('notexisting') node.set_extra('extra', 'value') node.delete_extra('extra') self.assertEqual(node.extras, {})
[docs] def test_delete_extra_many(self): """Test the `BackendNode.delete_extra_many` method.""" node = self.create_node() extras = {'extra_one': 1, 'extra_two': 2} with self.assertRaises(AttributeError): node.delete_extra_many(['notexisting', 'some']) node.set_extra_many(extras) with self.assertRaises(AttributeError): node.delete_extra_many(['extra_one', 'notexisting']) # Because one key failed during delete, none of the extras should have been deleted self.assertTrue('extra_one' in node.extras) # Now delete the keys that actually should exist node.delete_extra_many(extras.keys()) self.assertEqual(node.extras, {}) # Now for a stored node node = self.create_node().store() with self.assertRaises(AttributeError): node.delete_extra_many(['notexisting', 'some']) node.set_extra_many(extras) with self.assertRaises(AttributeError): node.delete_extra_many(['extra_one', 'notexisting']) # Because one key failed during delete, none of the extras should have been deleted self.assertTrue('extra_one' in node.extras) # Now delete the keys that actually should exist node.delete_extra_many(extras.keys()) self.assertEqual(node.extras, {})
[docs] def test_clear_extras(self): """Test the `BackendNode.clear_extras` method.""" node = self.create_node() extras = {'extra_one': 1, 'extra_two': 2} node.set_extra_many(extras) self.assertEqual(node.extras, extras) node.clear_extras() self.assertEqual(node.extras, {}) # Now for a stored node node = self.create_node().store() node.set_extra_many(extras) self.assertEqual(node.extras, extras) node.clear_extras() self.assertEqual(node.extras, {})
[docs] def test_extra_items(self): """Test the `BackendNode.extra_items` generator.""" node = self.create_node() extras = {'extra_one': 1, 'extra_two': 2} node.set_extra_many(extras) self.assertEqual(extras, dict(node.extras_items())) # Repeat for a stored node node = self.create_node().store() extras = {'extra_one': 1, 'extra_two': 2} node.set_extra_many(extras) self.assertEqual(extras, dict(node.extras_items()))
[docs] def test_extra_keys(self): """Test the `BackendNode.extra_keys` generator.""" node = self.create_node() extras = {'extra_one': 1, 'extra_two': 2} node.set_extra_many(extras) self.assertEqual(set(extras), set(node.extras_keys())) # Repeat for a stored node node = self.create_node().store() extras = {'extra_one': 1, 'extra_two': 2} node.set_extra_many(extras) self.assertEqual(set(extras), set(node.extras_keys()))
[docs] def test_extra_flush_specifically(self): """Test that changing `extras` only flushes that property and does not affect others like attributes. Regression fix for #3338 """ node = self.create_node().store() attributes = {'attribute_one': 1, 'attribute_two': 2} node.set_attribute_many(attributes) node.store() # Load the stored node in memory in another instance and add another attribute reloaded = self.backend.nodes.get(node.pk) reloaded.set_attribute('attribute_three', 3) # The original memory instance should now not include `attribute_three`. Changing an extra should only flush # the extras and not the whole node state, which would override for example also the attributes. node.set_extra('extra_one', 1) # Reload the node yet again and verify that the `attribute_three` attribute is still there rereloaded = self.backend.nodes.get(node.pk) self.assertIn('attribute_three', rereloaded.attributes.keys())