# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from datetime import datetime, timedelta import json import logging import six import sys import traceback from uuid import uuid4 from cassandra import WriteTimeout, OperationTimedOut import cassandra.cqlengine.columns as columns from cassandra.cqlengine.functions import get_total_seconds from cassandra.cqlengine.models import Model, ValidationError from cassandra.cqlengine.management import sync_table, drop_table from tests.integration import CASSANDRA_IP from tests.integration.cqlengine import is_prepend_reversed from tests.integration.cqlengine.base import BaseCassEngTestCase from tests.integration import greaterthancass20, CASSANDRA_VERSION log = logging.getLogger(__name__) class TestSetModel(Model): partition = columns.UUID(primary_key=True, default=uuid4) int_set = columns.Set(columns.Integer, required=False) text_set = columns.Set(columns.Text, required=False) class JsonTestColumn(columns.Column): db_type = 'text' def to_python(self, value): if value is None: return if isinstance(value, six.string_types): return json.loads(value) else: return value def to_database(self, value): if value is None: return return json.dumps(value) class TestSetColumn(BaseCassEngTestCase): @classmethod def setUpClass(cls): drop_table(TestSetModel) sync_table(TestSetModel) @classmethod def tearDownClass(cls): drop_table(TestSetModel) def test_add_none_fails(self): self.assertRaises(ValidationError, TestSetModel.create, **{'int_set': set([None])}) def test_empty_set_initial(self): """ tests that sets are set() by default, should never be none :return: """ m = TestSetModel.create() m.int_set.add(5) m.save() def test_deleting_last_item_should_succeed(self): m = TestSetModel.create() m.int_set.add(5) m.save() m.int_set.remove(5) m.save() m = TestSetModel.get(partition=m.partition) self.assertTrue(5 not in m.int_set) def test_blind_deleting_last_item_should_succeed(self): m = TestSetModel.create() m.int_set.add(5) m.save() TestSetModel.objects(partition=m.partition).update(int_set=set()) m = TestSetModel.get(partition=m.partition) self.assertTrue(5 not in m.int_set) def test_empty_set_retrieval(self): m = TestSetModel.create() m2 = TestSetModel.get(partition=m.partition) m2.int_set.add(3) def test_io_success(self): """ Tests that a basic usage works as expected """ m1 = TestSetModel.create(int_set=set((1, 2)), text_set=set(('kai', 'andreas'))) m2 = TestSetModel.get(partition=m1.partition) self.assertIsInstance(m2.int_set, set) self.assertIsInstance(m2.text_set, set) self.assertIn(1, m2.int_set) self.assertIn(2, m2.int_set) self.assertIn('kai', m2.text_set) self.assertIn('andreas', m2.text_set) def test_type_validation(self): """ Tests that attempting to use the wrong types will raise an exception """ self.assertRaises(ValidationError, TestSetModel.create, **{'int_set': set(('string', True)), 'text_set': set((1, 3.0))}) def test_element_count_validation(self): """ Tests that big collections are detected and raise an exception. """ while True: try: TestSetModel.create(text_set=set(str(uuid4()) for i in range(65535))) break except WriteTimeout: ex_type, ex, tb = sys.exc_info() log.warning("{0}: {1} Backtrace: {2}".format(ex_type.__name__, ex, traceback.extract_tb(tb))) del tb except OperationTimedOut: #This will happen if the host is remote self.assertFalse(CASSANDRA_IP.startswith("127.0.0.")) self.assertRaises(ValidationError, TestSetModel.create, **{'text_set': set(str(uuid4()) for i in range(65536))}) def test_partial_updates(self): """ Tests that partial udpates work as expected """ m1 = TestSetModel.create(int_set=set((1, 2, 3, 4))) m1.int_set.add(5) m1.int_set.remove(1) self.assertEqual(m1.int_set, set((2, 3, 4, 5))) m1.save() m2 = TestSetModel.get(partition=m1.partition) self.assertEqual(m2.int_set, set((2, 3, 4, 5))) def test_instantiation_with_column_class(self): """ Tests that columns instantiated with a column class work properly and that the class is instantiated in the constructor """ column = columns.Set(columns.Text) self.assertIsInstance(column.value_col, columns.Text) def test_instantiation_with_column_instance(self): """ Tests that columns instantiated with a column instance work properly """ column = columns.Set(columns.Text(min_length=100)) self.assertIsInstance(column.value_col, columns.Text) def test_to_python(self): """ Tests that to_python of value column is called """ column = columns.Set(JsonTestColumn) val = set((1, 2, 3)) db_val = column.to_database(val) self.assertEqual(db_val, set(json.dumps(v) for v in val)) py_val = column.to_python(db_val) self.assertEqual(py_val, val) def test_default_empty_container_saving(self): """ tests that the default empty container is not saved if it hasn't been updated """ pkey = uuid4() # create a row with set data TestSetModel.create(partition=pkey, int_set=set((3, 4))) # create another with no set data TestSetModel.create(partition=pkey) m = TestSetModel.get(partition=pkey) self.assertEqual(m.int_set, set((3, 4))) class TestListModel(Model): partition = columns.UUID(primary_key=True, default=uuid4) int_list = columns.List(columns.Integer, required=False) text_list = columns.List(columns.Text, required=False) class TestListColumn(BaseCassEngTestCase): @classmethod def setUpClass(cls): drop_table(TestListModel) sync_table(TestListModel) @classmethod def tearDownClass(cls): drop_table(TestListModel) def test_initial(self): tmp = TestListModel.create() tmp.int_list.append(1) def test_initial_retrieve(self): tmp = TestListModel.create() tmp2 = TestListModel.get(partition=tmp.partition) tmp2.int_list.append(1) def test_io_success(self): """ Tests that a basic usage works as expected """ m1 = TestListModel.create(int_list=[1, 2], text_list=['kai', 'andreas']) m2 = TestListModel.get(partition=m1.partition) self.assertIsInstance(m2.int_list, list) self.assertIsInstance(m2.text_list, list) self.assertEqual(len(m2.int_list), 2) self.assertEqual(len(m2.text_list), 2) self.assertEqual(m2.int_list[0], 1) self.assertEqual(m2.int_list[1], 2) self.assertEqual(m2.text_list[0], 'kai') self.assertEqual(m2.text_list[1], 'andreas') def test_type_validation(self): """ Tests that attempting to use the wrong types will raise an exception """ self.assertRaises(ValidationError, TestListModel.create, **{'int_list': ['string', True], 'text_list': [1, 3.0]}) def test_element_count_validation(self): """ Tests that big collections are detected and raise an exception. """ while True: try: TestListModel.create(text_list=[str(uuid4()) for i in range(65535)]) break except WriteTimeout: ex_type, ex, tb = sys.exc_info() log.warn("{0}: {1} Backtrace: {2}".format(ex_type.__name__, ex, traceback.extract_tb(tb))) del tb self.assertRaises(ValidationError, TestListModel.create, **{'text_list': [str(uuid4()) for _ in range(65536)]}) def test_partial_updates(self): """ Tests that partial udpates work as expected """ full = list(range(10)) initial = full[3:7] m1 = TestListModel.create(int_list=initial) m1.int_list = full m1.save() if is_prepend_reversed(): expected = full[2::-1] + full[3:] else: expected = full m2 = TestListModel.get(partition=m1.partition) self.assertEqual(list(m2.int_list), expected) def test_instantiation_with_column_class(self): """ Tests that columns instantiated with a column class work properly and that the class is instantiated in the constructor """ column = columns.List(columns.Text) self.assertIsInstance(column.value_col, columns.Text) def test_instantiation_with_column_instance(self): """ Tests that columns instantiated with a column instance work properly """ column = columns.List(columns.Text(min_length=100)) self.assertIsInstance(column.value_col, columns.Text) def test_to_python(self): """ Tests that to_python of value column is called """ column = columns.List(JsonTestColumn) val = [1, 2, 3] db_val = column.to_database(val) self.assertEqual(db_val, [json.dumps(v) for v in val]) py_val = column.to_python(db_val) self.assertEqual(py_val, val) def test_default_empty_container_saving(self): """ tests that the default empty container is not saved if it hasn't been updated """ pkey = uuid4() # create a row with list data TestListModel.create(partition=pkey, int_list=[1, 2, 3, 4]) # create another with no list data TestListModel.create(partition=pkey) m = TestListModel.get(partition=pkey) self.assertEqual(m.int_list, [1, 2, 3, 4]) def test_remove_entry_works(self): pkey = uuid4() tmp = TestListModel.create(partition=pkey, int_list=[1, 2]) tmp.int_list.pop() tmp.update() tmp = TestListModel.get(partition=pkey) self.assertEqual(tmp.int_list, [1]) def test_update_from_non_empty_to_empty(self): pkey = uuid4() tmp = TestListModel.create(partition=pkey, int_list=[1, 2]) tmp.int_list = [] tmp.update() tmp = TestListModel.get(partition=pkey) self.assertEqual(tmp.int_list, []) def test_insert_none(self): pkey = uuid4() self.assertRaises(ValidationError, TestListModel.create, **{'partition': pkey, 'int_list': [None]}) def test_blind_list_updates_from_none(self): """ Tests that updates from None work as expected """ m = TestListModel.create(int_list=None) expected = [1, 2] m.int_list = expected m.save() m2 = TestListModel.get(partition=m.partition) self.assertEqual(m2.int_list, expected) TestListModel.objects(partition=m.partition).update(int_list=[]) m3 = TestListModel.get(partition=m.partition) self.assertEqual(m3.int_list, []) class TestMapModel(Model): partition = columns.UUID(primary_key=True, default=uuid4) int_map = columns.Map(columns.Integer, columns.UUID, required=False) text_map = columns.Map(columns.Text, columns.DateTime, required=False) class TestMapColumn(BaseCassEngTestCase): @classmethod def setUpClass(cls): drop_table(TestMapModel) sync_table(TestMapModel) @classmethod def tearDownClass(cls): drop_table(TestMapModel) def test_empty_default(self): tmp = TestMapModel.create() tmp.int_map['blah'] = 1 def test_add_none_as_map_key(self): self.assertRaises(ValidationError, TestMapModel.create, **{'int_map': {None: uuid4()}}) def test_empty_retrieve(self): tmp = TestMapModel.create() tmp2 = TestMapModel.get(partition=tmp.partition) tmp2.int_map['blah'] = 1 def test_remove_last_entry_works(self): tmp = TestMapModel.create() tmp.text_map["blah"] = datetime.now() tmp.save() del tmp.text_map["blah"] tmp.save() tmp = TestMapModel.get(partition=tmp.partition) self.assertTrue("blah" not in tmp.int_map) def test_io_success(self): """ Tests that a basic usage works as expected """ k1 = uuid4() k2 = uuid4() now = datetime.now() then = now + timedelta(days=1) m1 = TestMapModel.create(int_map={1: k1, 2: k2}, text_map={'now': now, 'then': then}) m2 = TestMapModel.get(partition=m1.partition) self.assertTrue(isinstance(m2.int_map, dict)) self.assertTrue(isinstance(m2.text_map, dict)) self.assertTrue(1 in m2.int_map) self.assertTrue(2 in m2.int_map) self.assertEqual(m2.int_map[1], k1) self.assertEqual(m2.int_map[2], k2) self.assertAlmostEqual(get_total_seconds(now - m2.text_map['now']), 0, 2) self.assertAlmostEqual(get_total_seconds(then - m2.text_map['then']), 0, 2) def test_type_validation(self): """ Tests that attempting to use the wrong types will raise an exception """ self.assertRaises(ValidationError, TestMapModel.create, **{'int_map': {'key': 2, uuid4(): 'val'}, 'text_map': {2: 5}}) def test_element_count_validation(self): """ Tests that big collections are detected and raise an exception. """ while True: try: TestMapModel.create(text_map=dict((str(uuid4()), i) for i in range(65535))) break except WriteTimeout: ex_type, ex, tb = sys.exc_info() log.warn("{0}: {1} Backtrace: {2}".format(ex_type.__name__, ex, traceback.extract_tb(tb))) del tb self.assertRaises(ValidationError, TestMapModel.create, **{'text_map': dict((str(uuid4()), i) for i in range(65536))}) def test_partial_updates(self): """ Tests that partial udpates work as expected """ now = datetime.now() # derez it a bit now = datetime(*now.timetuple()[:-3]) early = now - timedelta(minutes=30) earlier = early - timedelta(minutes=30) later = now + timedelta(minutes=30) initial = {'now': now, 'early': earlier} final = {'later': later, 'early': early} m1 = TestMapModel.create(text_map=initial) m1.text_map = final m1.save() m2 = TestMapModel.get(partition=m1.partition) self.assertEqual(m2.text_map, final) def test_updates_from_none(self): """ Tests that updates from None work as expected """ m = TestMapModel.create(int_map=None) expected = {1: uuid4()} m.int_map = expected m.save() m2 = TestMapModel.get(partition=m.partition) self.assertEqual(m2.int_map, expected) m2.int_map = None m2.save() m3 = TestMapModel.get(partition=m.partition) self.assertNotEqual(m3.int_map, expected) def test_blind_updates_from_none(self): """ Tests that updates from None work as expected """ m = TestMapModel.create(int_map=None) expected = {1: uuid4()} m.int_map = expected m.save() m2 = TestMapModel.get(partition=m.partition) self.assertEqual(m2.int_map, expected) TestMapModel.objects(partition=m.partition).update(int_map={}) m3 = TestMapModel.get(partition=m.partition) self.assertNotEqual(m3.int_map, expected) def test_updates_to_none(self): """ Tests that setting the field to None works as expected """ m = TestMapModel.create(int_map={1: uuid4()}) m.int_map = None m.save() m2 = TestMapModel.get(partition=m.partition) self.assertEqual(m2.int_map, {}) def test_instantiation_with_column_class(self): """ Tests that columns instantiated with a column class work properly and that the class is instantiated in the constructor """ column = columns.Map(columns.Text, columns.Integer) self.assertIsInstance(column.key_col, columns.Text) self.assertIsInstance(column.value_col, columns.Integer) def test_instantiation_with_column_instance(self): """ Tests that columns instantiated with a column instance work properly """ column = columns.Map(columns.Text(min_length=100), columns.Integer()) self.assertIsInstance(column.key_col, columns.Text) self.assertIsInstance(column.value_col, columns.Integer) def test_to_python(self): """ Tests that to_python of value column is called """ column = columns.Map(JsonTestColumn, JsonTestColumn) val = {1: 2, 3: 4, 5: 6} db_val = column.to_database(val) self.assertEqual(db_val, dict((json.dumps(k), json.dumps(v)) for k, v in val.items())) py_val = column.to_python(db_val) self.assertEqual(py_val, val) def test_default_empty_container_saving(self): """ tests that the default empty container is not saved if it hasn't been updated """ pkey = uuid4() tmap = {1: uuid4(), 2: uuid4()} # create a row with set data TestMapModel.create(partition=pkey, int_map=tmap) # create another with no set data TestMapModel.create(partition=pkey) m = TestMapModel.get(partition=pkey) self.assertEqual(m.int_map, tmap) class TestCamelMapModel(Model): partition = columns.UUID(primary_key=True, default=uuid4) camelMap = columns.Map(columns.Text, columns.Integer, required=False) class TestCamelMapColumn(BaseCassEngTestCase): @classmethod def setUpClass(cls): drop_table(TestCamelMapModel) sync_table(TestCamelMapModel) @classmethod def tearDownClass(cls): drop_table(TestCamelMapModel) def test_camelcase_column(self): TestCamelMapModel.create(camelMap={'blah': 1}) class TestTupleModel(Model): partition = columns.UUID(primary_key=True, default=uuid4) int_tuple = columns.Tuple(columns.Integer, columns.Integer, columns.Integer, required=False) text_tuple = columns.Tuple(columns.Text, columns.Text, required=False) mixed_tuple = columns.Tuple(columns.Text, columns.Integer, columns.Text, required=False) @greaterthancass20 class TestTupleColumn(BaseCassEngTestCase): @classmethod def setUpClass(cls): # Skip annotations don't seem to skip class level teradown and setup methods if(CASSANDRA_VERSION >= '2.1'): drop_table(TestTupleModel) sync_table(TestTupleModel) @classmethod def tearDownClass(cls): drop_table(TestTupleModel) def test_initial(self): """ Tests creation and insertion of tuple types with models @since 3.1 @jira_ticket PYTHON-306 @expected_result Model is successfully crated @test_category object_mapper """ tmp = TestTupleModel.create() tmp.int_tuple = (1, 2, 3) def test_initial_retrieve(self): """ Tests creation and insertion of tuple types with models, and their retrieval. @since 3.1 @jira_ticket PYTHON-306 @expected_result Model is successfully crated @test_category object_mapper """ tmp = TestTupleModel.create() tmp2 = tmp.get(partition=tmp.partition) tmp2.int_tuple = (1, 2, 3) def test_io_success(self): """ Tests creation and insertion of various types with models, and their retrieval. @since 3.1 @jira_ticket PYTHON-306 @expected_result Model is successfully created and fetched correctly @test_category object_mapper """ m1 = TestTupleModel.create(int_tuple=(1, 2, 3, 5, 6), text_tuple=('kai', 'andreas'), mixed_tuple=('first', 2, 'Third')) m2 = TestTupleModel.get(partition=m1.partition) self.assertIsInstance(m2.int_tuple, tuple) self.assertIsInstance(m2.text_tuple, tuple) self.assertIsInstance(m2.mixed_tuple, tuple) self.assertEqual((1, 2, 3), m2.int_tuple) self.assertEqual(('kai', 'andreas'), m2.text_tuple) self.assertEqual(('first', 2, 'Third'), m2.mixed_tuple) def test_type_validation(self): """ Tests to make sure tuple type validation occurs @since 3.1 @jira_ticket PYTHON-306 @expected_result validation errors are raised @test_category object_mapper """ self.assertRaises(ValidationError, TestTupleModel.create, **{'int_tuple': ('string', True), 'text_tuple': ('test', 'test'), 'mixed_tuple': ('one', 2, 'three')}) self.assertRaises(ValidationError, TestTupleModel.create, **{'int_tuple': ('string', 'string'), 'text_tuple': (1, 3.0), 'mixed_tuple': ('one', 2, 'three')}) self.assertRaises(ValidationError, TestTupleModel.create, **{'int_tuple': ('string', 'string'), 'text_tuple': ('test', 'test'), 'mixed_tuple': (1, "two", 3)}) def test_instantiation_with_column_class(self): """ Tests that columns instantiated with a column class work properly and that the class is instantiated in the constructor @since 3.1 @jira_ticket PYTHON-306 @expected_result types are instantiated correctly @test_category object_mapper """ mixed_tuple = columns.Tuple(columns.Text, columns.Integer, columns.Text, required=False) self.assertIsInstance(mixed_tuple.types[0], columns.Text) self.assertIsInstance(mixed_tuple.types[1], columns.Integer) self.assertIsInstance(mixed_tuple.types[2], columns.Text) self.assertEqual(len(mixed_tuple.types), 3) def test_default_empty_container_saving(self): """ Tests that the default empty container is not saved if it hasn't been updated @since 3.1 @jira_ticket PYTHON-306 @expected_result empty tuple is not upserted @test_category object_mapper """ pkey = uuid4() # create a row with tuple data TestTupleModel.create(partition=pkey, int_tuple=(1, 2, 3)) # create another with no tuple data TestTupleModel.create(partition=pkey) m = TestTupleModel.get(partition=pkey) self.assertEqual(m.int_tuple, (1, 2, 3)) def test_updates(self): """ Tests that updates can be preformed on tuple containers @since 3.1 @jira_ticket PYTHON-306 @expected_result tuple is replaced @test_category object_mapper """ initial = (1, 2) replacement = (1, 2, 3) m1 = TestTupleModel.create(int_tuple=initial) m1.int_tuple = replacement m1.save() m2 = TestTupleModel.get(partition=m1.partition) self.assertEqual(tuple(m2.int_tuple), replacement) def test_update_from_non_empty_to_empty(self): """ Tests that explcit none updates are processed correctly on tuples @since 3.1 @jira_ticket PYTHON-306 @expected_result tuple is replaced with none @test_category object_mapper """ pkey = uuid4() tmp = TestTupleModel.create(partition=pkey, int_tuple=(1, 2, 3)) tmp.int_tuple = (None) tmp.update() tmp = TestTupleModel.get(partition=pkey) self.assertEqual(tmp.int_tuple, (None)) def test_insert_none(self): """ Tests that Tuples can be created as none @since 3.1 @jira_ticket PYTHON-306 @expected_result tuple is created as none @test_category object_mapper """ pkey = uuid4() tmp = TestTupleModel.create(partition=pkey, int_tuple=(None)) self.assertEqual((None), tmp.int_tuple) def test_blind_tuple_updates_from_none(self): """ Tests that Tuples can be updated from none @since 3.1 @jira_ticket PYTHON-306 @expected_result tuple is created as none, but upserted to contain values @test_category object_mapper """ m = TestTupleModel.create(int_tuple=None) expected = (1, 2, 3) m.int_tuple = expected m.save() m2 = TestTupleModel.get(partition=m.partition) self.assertEqual(m2.int_tuple, expected) TestTupleModel.objects(partition=m.partition).update(int_tuple=None) m3 = TestTupleModel.get(partition=m.partition) self.assertEqual(m3.int_tuple, None) class TestNestedModel(Model): partition = columns.UUID(primary_key=True, default=uuid4) list_list = columns.List(columns.List(columns.Integer), required=False) map_list = columns.Map(columns.Text, columns.List(columns.Text), required=False) set_tuple = columns.Set(columns.Tuple(columns.Integer, columns.Integer), required=False) @greaterthancass20 class TestNestedType(BaseCassEngTestCase): @classmethod def setUpClass(cls): # Skip annotations don't seem to skip class level teradown and setup methods if(CASSANDRA_VERSION >= '2.1'): drop_table(TestNestedModel) sync_table(TestNestedModel) @classmethod def tearDownClass(cls): drop_table(TestNestedModel) def test_initial(self): """ Tests creation and insertion of nested collection types with models @since 3.1 @jira_ticket PYTHON-478 @expected_result Model is successfully created @test_category object_mapper """ tmp = TestNestedModel.create() tmp.list_list = [[1, 2, 3], [2, 3, 4], [3, 4, 5]] def test_initial_retrieve(self): """ Tests creation and insertion of nested collection types with models, and their retrieval. @since 3.1 @jira_ticket PYTHON-478 @expected_result Model is successfully crated @test_category object_mapper """ tmp = TestNestedModel.create() tmp2 = tmp.get(partition=tmp.partition) tmp2.list_list = [[1, 2, 3], [2, 3, 4], [3, 4, 5]] tmp2.map_list = {'key1': ["text1", "text2", "text3"], "key2": ["text1", "text2", "text3"], "key3": ["text1", "text2", "text3"]} tmp2.set_tuple = set(((1, 2), (3, 5), (4, 5))) def test_io_success(self): """ Tests creation and insertion of various nested collection types with models, and their retrieval. @since 3.1 @jira_ticket PYTHON-378 @expected_result Model is successfully created and fetched correctly @test_category object_mapper """ list_list_master = [[1, 2, 3], [2, 3, 4], [3, 4, 5]] map_list_master = {'key1': ["text1", "text2", "text3"], "key2": ["text1", "text2", "text3"], "key3": ["text1", "text2", "text3"]} set_tuple_master = set(((1, 2), (3, 5), (4, 5))) m1 = TestNestedModel.create(list_list=list_list_master, map_list=map_list_master, set_tuple=set_tuple_master) m2 = TestNestedModel.get(partition=m1.partition) self.assertIsInstance(m2.list_list, list) self.assertIsInstance(m2.list_list[0], list) self.assertIsInstance(m2.map_list, dict) self.assertIsInstance(m2.map_list.get("key2"), list) self.assertEqual(list_list_master, m2.list_list) self.assertEqual(map_list_master, m2.map_list) self.assertEqual(set_tuple_master, m2.set_tuple) self.assertIsInstance(m2.set_tuple.pop(), tuple) def test_type_validation(self): """ Tests to make sure nested collection type validation occurs @since 3.1 @jira_ticket PYTHON-478 @expected_result validation errors are raised @test_category object_mapper """ list_list_bad_list_context = [['text', "text", "text"], ["text", "text", "text"], ["text", "text", "text"]] list_list_no_list = ['text', "text", "text"] map_list_bad_value = {'key1': [1, 2, 3], "key2": [1, 2, 3], "key3": [1, 2, 3]} map_list_bad_key = {1: ["text1", "text2", "text3"], 2: ["text1", "text2", "text3"], 3: ["text1", "text2", "text3"]} set_tuple_bad_tuple_value = set((("text", "text"), ("text", "text"), ("text", "text"))) set_tuple_not_set = ['This', 'is', 'not', 'a', 'set'] self.assertRaises(ValidationError, TestNestedModel.create, **{'list_list': list_list_bad_list_context}) self.assertRaises(ValidationError, TestNestedModel.create, **{'list_list': list_list_no_list}) self.assertRaises(ValidationError, TestNestedModel.create, **{'map_list': map_list_bad_value}) self.assertRaises(ValidationError, TestNestedModel.create, **{'map_list': map_list_bad_key}) self.assertRaises(ValidationError, TestNestedModel.create, **{'set_tuple': set_tuple_bad_tuple_value}) self.assertRaises(ValidationError, TestNestedModel.create, **{'set_tuple': set_tuple_not_set}) def test_instantiation_with_column_class(self): """ Tests that columns instantiated with a column class work properly and that the class is instantiated in the constructor @since 3.1 @jira_ticket PYTHON-478 @expected_result types are instantiated correctly @test_category object_mapper """ list_list = columns.List(columns.List(columns.Integer), required=False) map_list = columns.Map(columns.Text, columns.List(columns.Text), required=False) set_tuple = columns.Set(columns.Tuple(columns.Integer, columns.Integer), required=False) self.assertIsInstance(list_list, columns.List) self.assertIsInstance(list_list.types[0], columns.List) self.assertIsInstance(map_list.types[0], columns.Text) self.assertIsInstance(map_list.types[1], columns.List) self.assertIsInstance(set_tuple.types[0], columns.Tuple) def test_default_empty_container_saving(self): """ Tests that the default empty nested collection container is not saved if it hasn't been updated @since 3.1 @jira_ticket PYTHON-478 @expected_result empty nested collection is not upserted @test_category object_mapper """ pkey = uuid4() # create a row with tuple data list_list_master = [[1, 2, 3], [2, 3, 4], [3, 4, 5]] map_list_master = {'key1': ["text1", "text2", "text3"], "key2": ["text1", "text2", "text3"], "key3": ["text1", "text2", "text3"]} set_tuple_master = set(((1, 2), (3, 5), (4, 5))) TestNestedModel.create(partition=pkey, list_list=list_list_master, map_list=map_list_master, set_tuple=set_tuple_master) # create another with no tuple data TestNestedModel.create(partition=pkey) m = TestNestedModel.get(partition=pkey) self.assertEqual(m.list_list, list_list_master) self.assertEqual(m.map_list, map_list_master) self.assertEqual(m.set_tuple, set_tuple_master) def test_updates(self): """ Tests that updates can be preformed on nested collections @since 3.1 @jira_ticket PYTHON-478 @expected_result nested collection is replaced @test_category object_mapper """ list_list_initial = [[1, 2, 3], [2, 3, 4], [3, 4, 5]] list_list_replacement = [[1, 2, 3], [3, 4, 5]] set_tuple_initial = set(((1, 2), (3, 5), (4, 5))) map_list_initial = {'key1': ["text1", "text2", "text3"], "key2": ["text1", "text2", "text3"], "key3": ["text1", "text2", "text3"]} map_list_replacement = {'key1': ["text1", "text2", "text3"], "key3": ["text1", "text2", "text3"]} set_tuple_replacement = set(((7, 7), (7, 7), (4, 5))) m1 = TestNestedModel.create(list_list=list_list_initial, map_list=map_list_initial, set_tuple=set_tuple_initial) m1.list_list = list_list_replacement m1.map_list = map_list_replacement m1.set_tuple = set_tuple_replacement m1.save() m2 = TestNestedModel.get(partition=m1.partition) self.assertEqual(m2.list_list, list_list_replacement) self.assertEqual(m2.map_list, map_list_replacement) self.assertEqual(m2.set_tuple, set_tuple_replacement) def test_update_from_non_empty_to_empty(self): """ Tests that explcit none updates are processed correctly on tuples @since 3.1 @jira_ticket PYTHON-478 @expected_result nested collection is replaced with none @test_category object_mapper """ list_list_initial = [[1, 2, 3], [2, 3, 4], [3, 4, 5]] map_list_initial = {'key1': ["text1", "text2", "text3"], "key2": ["text1", "text2", "text3"], "key3": ["text1", "text2", "text3"]} set_tuple_initial = set(((1, 2), (3, 5), (4, 5))) tmp = TestNestedModel.create(list_list=list_list_initial, map_list=map_list_initial, set_tuple=set_tuple_initial) tmp.list_list = [] tmp.map_list = {} tmp.set_tuple = set() tmp.update() tmp = TestNestedModel.get(partition=tmp.partition) self.assertEqual(tmp.list_list, []) self.assertEqual(tmp.map_list, {}) self.assertEqual(tmp.set_tuple, set()) def test_insert_none(self): """ Tests that Tuples can be created as none @since 3.1 @jira_ticket PYTHON-478 @expected_result nested collection is created as none @test_category object_mapper """ pkey = uuid4() tmp = TestNestedModel.create(partition=pkey, list_list=(None), map_list=(None), set_tuple=(None)) self.assertEqual([], tmp.list_list) self.assertEqual({}, tmp.map_list) self.assertEqual(set(), tmp.set_tuple)