From 4a977005f3569d1a946d7021486c1ba465b14a08 Mon Sep 17 00:00:00 2001 From: Jon Haddad Date: Fri, 14 Jun 2013 17:52:13 -0700 Subject: [PATCH] fixed #67 (validation on keyname typos) #66 (removed defaults on columns) and #57 and removed autoid --- changelog | 4 +++ cqlengine/columns.py | 12 ------- cqlengine/models.py | 24 ++++++++------ cqlengine/tests/columns/test_validation.py | 19 +++++++++--- .../tests/model/test_class_construction.py | 31 ++++++++++++++----- .../tests/model/test_equality_operations.py | 2 ++ cqlengine/tests/model/test_model_io.py | 10 ++++++ 7 files changed, 69 insertions(+), 33 deletions(-) diff --git a/changelog b/changelog index 8111f51f..3d94f5fb 100644 --- a/changelog +++ b/changelog @@ -1,5 +1,9 @@ CHANGELOG +0.4.0 +* removed default values from all column types +* explicit primary key is required (automatic id removed) + 0.3.3 * added abstract base class models diff --git a/cqlengine/columns.py b/cqlengine/columns.py index d864780c..67112eb0 100644 --- a/cqlengine/columns.py +++ b/cqlengine/columns.py @@ -236,9 +236,6 @@ class Integer(Column): class DateTime(Column): db_type = 'timestamp' - def __init__(self, **kwargs): - super(DateTime, self).__init__(**kwargs) - def to_python(self, value): if isinstance(value, datetime): return value @@ -265,8 +262,6 @@ class DateTime(Column): class Date(Column): db_type = 'timestamp' - def __init__(self, **kwargs): - super(Date, self).__init__(**kwargs) def to_python(self, value): if isinstance(value, datetime): @@ -294,9 +289,6 @@ class UUID(Column): re_uuid = re.compile(r'[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}') - def __init__(self, default=lambda:uuid4(), **kwargs): - super(UUID, self).__init__(default=default, **kwargs) - def validate(self, value): val = super(UUID, self).validate(value) if val is None: return @@ -319,10 +311,6 @@ class TimeUUID(UUID): db_type = 'timeuuid' - def __init__(self, **kwargs): - kwargs.setdefault('default', lambda: uuid1()) - super(TimeUUID, self).__init__(**kwargs) - class Boolean(Column): db_type = 'boolean' diff --git a/cqlengine/models.py b/cqlengine/models.py index 64faf6a4..7240bf16 100644 --- a/cqlengine/models.py +++ b/cqlengine/models.py @@ -2,7 +2,7 @@ from collections import OrderedDict import re from cqlengine import columns -from cqlengine.exceptions import ModelException, CQLEngineException +from cqlengine.exceptions import ModelException, CQLEngineException, ValidationError from cqlengine.query import QuerySet, DMLQuery from cqlengine.query import DoesNotExist as _DoesNotExist from cqlengine.query import MultipleObjectsReturned as _MultipleObjectsReturned @@ -50,7 +50,7 @@ class BaseModel(object): """ The base model class, don't inherit from this, inherit from Model, defined below """ - + class DoesNotExist(_DoesNotExist): pass class MultipleObjectsReturned(_MultipleObjectsReturned): pass @@ -60,12 +60,17 @@ class BaseModel(object): #however, you can also define them manually here table_name = None - #the keyspace for this model + #the keyspace for this model keyspace = None read_repair_chance = 0.1 def __init__(self, **values): self._values = {} + + extra_columns = set(values.keys()) - set(self._columns.keys()) + if extra_columns: + raise ValidationError("Incorrect columns passed: {}".format(extra_columns)) + for name, column in self._columns.items(): value = values.get(name, None) if value is not None: value = column.to_python(value) @@ -111,11 +116,11 @@ class BaseModel(object): else: camelcase = re.compile(r'([a-z])([A-Z])') ccase = lambda s: camelcase.sub(lambda v: '{}_{}'.format(v.group(1), v.group(2).lower()), s) - + module = cls.__module__.split('.') if module: cf_name = ccase(module[-1]) + '_' - + cf_name += ccase(cls.__name__) #trim to less than 48 characters or cassandra will complain cf_name = cf_name[-48:] @@ -140,15 +145,15 @@ class BaseModel(object): @classmethod def create(cls, **kwargs): return cls.objects.create(**kwargs) - + @classmethod def all(cls): return cls.objects.all() - + @classmethod def filter(cls, **kwargs): return cls.objects.filter(**kwargs) - + @classmethod def get(cls, **kwargs): return cls.objects.get(**kwargs) @@ -226,8 +231,7 @@ class ModelMetaClass(type): #prepend primary key if one hasn't been defined if not is_abstract and not any([v.primary_key for k,v in column_definitions]): - k,v = 'id', columns.UUID(primary_key=True) - column_definitions = [(k,v)] + column_definitions + raise ModelDefinitionException("At least 1 primary key is required.") has_partition_keys = any(v.partition_key for (k, v) in column_definitions) diff --git a/cqlengine/tests/columns/test_validation.py b/cqlengine/tests/columns/test_validation.py index 24c17397..acbf9099 100644 --- a/cqlengine/tests/columns/test_validation.py +++ b/cqlengine/tests/columns/test_validation.py @@ -3,6 +3,7 @@ from datetime import datetime, timedelta from datetime import date from datetime import tzinfo from decimal import Decimal as D +from uuid import uuid4, uuid1 from cqlengine import ValidationError from cqlengine.tests.base import BaseCassEngTestCase @@ -119,7 +120,7 @@ class TestDecimal(BaseCassEngTestCase): class TestTimeUUID(BaseCassEngTestCase): class TimeUUIDTest(Model): test_id = Integer(primary_key=True) - timeuuid = TimeUUID() + timeuuid = TimeUUID(default=uuid1()) @classmethod def setUpClass(cls): @@ -132,6 +133,10 @@ class TestTimeUUID(BaseCassEngTestCase): delete_table(cls.TimeUUIDTest) def test_timeuuid_io(self): + """ + ensures that + :return: + """ t0 = self.TimeUUIDTest.create(test_id=0) t1 = self.TimeUUIDTest.get(test_id=0) @@ -139,8 +144,8 @@ class TestTimeUUID(BaseCassEngTestCase): class TestInteger(BaseCassEngTestCase): class IntegerTest(Model): - test_id = UUID(primary_key=True) - value = Integer(default=0) + test_id = UUID(primary_key=True, default=lambda:uuid4()) + value = Integer(default=0, required=True) def test_default_zero_fields_validate(self): """ Tests that integer columns with a default value of 0 validate """ @@ -190,7 +195,13 @@ class TestText(BaseCassEngTestCase): - +class TestExtraFieldsRaiseException(BaseCassEngTestCase): + class TestModel(Model): + id = UUID(primary_key=True, default=uuid4) + + def test_extra_field(self): + with self.assertRaises(ValidationError): + self.TestModel.create(bacon=5000) diff --git a/cqlengine/tests/model/test_class_construction.py b/cqlengine/tests/model/test_class_construction.py index a37b6baa..3e249be2 100644 --- a/cqlengine/tests/model/test_class_construction.py +++ b/cqlengine/tests/model/test_class_construction.py @@ -1,8 +1,9 @@ +from uuid import uuid4 from cqlengine.query import QueryException from cqlengine.tests.base import BaseCassEngTestCase from cqlengine.exceptions import ModelException, CQLEngineException -from cqlengine.models import Model +from cqlengine.models import Model, ModelDefinitionException from cqlengine import columns import cqlengine @@ -18,6 +19,7 @@ class TestModelClassFunction(BaseCassEngTestCase): """ class TestModel(Model): + id = columns.UUID(primary_key=True, default=lambda:uuid4()) text = columns.Text() #check class attibutes @@ -38,6 +40,7 @@ class TestModelClassFunction(BaseCassEngTestCase): -the db_map allows columns """ class WildDBNames(Model): + id = columns.UUID(primary_key=True, default=lambda:uuid4()) content = columns.Text(db_field='words_and_whatnot') numbers = columns.Integer(db_field='integers_etc') @@ -61,17 +64,26 @@ class TestModelClassFunction(BaseCassEngTestCase): """ class Stuff(Model): + id = columns.UUID(primary_key=True, default=lambda:uuid4()) words = columns.Text() content = columns.Text() numbers = columns.Integer() self.assertEquals(Stuff._columns.keys(), ['id', 'words', 'content', 'numbers']) + def test_exception_raised_when_creating_class_without_pk(self): + with self.assertRaises(ModelDefinitionException): + class TestModel(Model): + count = columns.Integer() + text = columns.Text(required=False) + + def test_value_managers_are_keeping_model_instances_isolated(self): """ Tests that instance value managers are isolated from other instances """ class Stuff(Model): + id = columns.UUID(primary_key=True, default=lambda:uuid4()) num = columns.Integer() inst1 = Stuff(num=5) @@ -86,6 +98,7 @@ class TestModelClassFunction(BaseCassEngTestCase): Tests that fields defined on the super class are inherited properly """ class TestModel(Model): + id = columns.UUID(primary_key=True, default=lambda:uuid4()) text = columns.Text() class InheritedModel(TestModel): @@ -124,6 +137,7 @@ class TestModelClassFunction(BaseCassEngTestCase): Test compound partition key definition """ class ModelWithPartitionKeys(cqlengine.Model): + id = columns.UUID(primary_key=True, default=lambda:uuid4()) c1 = cqlengine.Text(primary_key=True) p1 = cqlengine.Text(partition_key=True) p2 = cqlengine.Text(partition_key=True) @@ -144,6 +158,7 @@ class TestModelClassFunction(BaseCassEngTestCase): def test_del_attribute_is_assigned_properly(self): """ Tests that columns that can be deleted have the del attribute """ class DelModel(Model): + id = columns.UUID(primary_key=True, default=lambda:uuid4()) key = columns.Integer(primary_key=True) data = columns.Integer(required=False) @@ -156,9 +171,10 @@ class TestModelClassFunction(BaseCassEngTestCase): """ Tests that DoesNotExist exceptions are not the same exception between models """ class Model1(Model): - pass + id = columns.UUID(primary_key=True, default=lambda:uuid4()) + class Model2(Model): - pass + id = columns.UUID(primary_key=True, default=lambda:uuid4()) try: raise Model1.DoesNotExist @@ -171,7 +187,8 @@ class TestModelClassFunction(BaseCassEngTestCase): def test_does_not_exist_inherits_from_superclass(self): """ Tests that a DoesNotExist exception can be caught by it's parent class DoesNotExist """ class Model1(Model): - pass + id = columns.UUID(primary_key=True, default=lambda:uuid4()) + class Model2(Model1): pass @@ -184,14 +201,14 @@ class TestModelClassFunction(BaseCassEngTestCase): assert False, "Model2 exception should not be caught by Model1" class TestManualTableNaming(BaseCassEngTestCase): - + class RenamedTest(cqlengine.Model): keyspace = 'whatever' table_name = 'manual_name' - + id = cqlengine.UUID(primary_key=True) data = cqlengine.Text() - + def test_proper_table_naming(self): assert self.RenamedTest.column_family_name(include_keyspace=False) == 'manual_name' assert self.RenamedTest.column_family_name(include_keyspace=True) == 'whatever.manual_name' diff --git a/cqlengine/tests/model/test_equality_operations.py b/cqlengine/tests/model/test_equality_operations.py index 4d5b9521..a3e592b3 100644 --- a/cqlengine/tests/model/test_equality_operations.py +++ b/cqlengine/tests/model/test_equality_operations.py @@ -1,4 +1,5 @@ from unittest import skip +from uuid import uuid4 from cqlengine.tests.base import BaseCassEngTestCase from cqlengine.management import create_table @@ -7,6 +8,7 @@ from cqlengine.models import Model from cqlengine import columns class TestModel(Model): + id = columns.UUID(primary_key=True, default=lambda:uuid4()) count = columns.Integer() text = columns.Text(required=False) diff --git a/cqlengine/tests/model/test_model_io.py b/cqlengine/tests/model/test_model_io.py index 08e77ded..08d41d8b 100644 --- a/cqlengine/tests/model/test_model_io.py +++ b/cqlengine/tests/model/test_model_io.py @@ -8,10 +8,18 @@ from cqlengine.models import Model from cqlengine import columns class TestModel(Model): + id = columns.UUID(primary_key=True, default=lambda:uuid4()) count = columns.Integer() text = columns.Text(required=False) a_bool = columns.Boolean(default=False) +class TestModel(Model): + id = columns.UUID(primary_key=True, default=lambda:uuid4()) + count = columns.Integer() + text = columns.Text(required=False) + a_bool = columns.Boolean(default=False) + + class TestModelIO(BaseCassEngTestCase): @classmethod @@ -34,6 +42,8 @@ class TestModelIO(BaseCassEngTestCase): for cname in tm._columns.keys(): self.assertEquals(getattr(tm, cname), getattr(tm2, cname)) + + def test_model_updating_works_properly(self): """ Tests that subsequent saves after initial model creation work