adding support for abstract base classes

This commit is contained in:
Blake Eggleston
2013-06-05 14:21:58 -07:00
parent 750c58c27c
commit 634c0e9ae0
4 changed files with 93 additions and 6 deletions

View File

@@ -46,6 +46,10 @@ def delete_keyspace(name):
execute("DROP KEYSPACE {}".format(name))
def create_table(model, create_missing_keyspace=True):
if model.__abstract__:
raise CQLEngineException("cannot create table from abstract model")
#construct query string
cf_name = model.column_family_name()
raw_cf_name = model.column_family_name(include_keyspace=False)

View File

@@ -2,7 +2,7 @@ from collections import OrderedDict
import re
from cqlengine import columns
from cqlengine.exceptions import ModelException
from cqlengine.exceptions import ModelException, CQLEngineException
from cqlengine.query import QuerySet, DMLQuery
from cqlengine.query import DoesNotExist as _DoesNotExist
from cqlengine.query import MultipleObjectsReturned as _MultipleObjectsReturned
@@ -29,6 +29,8 @@ class hybrid_classmethod(object):
class QuerySetDescriptor(object):
def __get__(self, obj, model):
if model.__abstract__:
raise CQLEngineException('cannot execute queries against abstract models')
return QuerySet(model)
class BaseModel(object):
@@ -183,6 +185,8 @@ class ModelMetaClass(type):
for k,v in getattr(base, '_defined_columns', {}).items():
inherited_columns.setdefault(k,v)
#short circuit __abstract__ inheritance
is_abstract = attrs['__abstract__'] = attrs.get('__abstract__', False)
def _transform_column(col_name, col_obj):
column_dict[col_name] = col_obj
@@ -208,7 +212,7 @@ class ModelMetaClass(type):
defined_columns = OrderedDict(column_definitions)
#prepend primary key if one hasn't been defined
if not any([v.primary_key for k,v in column_definitions]):
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
@@ -228,7 +232,9 @@ class ModelMetaClass(type):
clustering_keys = OrderedDict(k for k in primary_keys.items() if not k[1].partition_key)
#setup partition key shortcut
assert partition_keys
if len(partition_keys) == 0:
if not is_abstract:
raise ModelException("at least one partition key must be defined")
if len(partition_keys) == 1:
pk_name = partition_keys.keys()[0]
attrs['pk'] = attrs[pk_name]
@@ -294,6 +300,7 @@ class Model(BaseModel):
the db name for the column family can be set as the attribute db_name, or
it will be genertaed from the class name
"""
__abstract__ = True
__metaclass__ = ModelMetaClass

View File

@@ -1,6 +1,7 @@
from cqlengine.query import QueryException
from cqlengine.tests.base import BaseCassEngTestCase
from cqlengine.exceptions import ModelException
from cqlengine.exceptions import ModelException, CQLEngineException
from cqlengine.models import Model
from cqlengine import columns
import cqlengine
@@ -199,8 +200,79 @@ class TestManualTableNaming(BaseCassEngTestCase):
class InheritedTest(self.RenamedTest): pass
assert InheritedTest.table_name is None
class AbstractModel(Model):
__abstract__ = True
class ConcreteModel(AbstractModel):
pkey = columns.Integer(primary_key=True)
data = columns.Integer()
class AbstractModelWithCol(Model):
__abstract__ = True
pkey = columns.Integer(primary_key=True)
class ConcreteModelWithCol(AbstractModelWithCol):
data = columns.Integer()
class AbstractModelWithFullCols(Model):
__abstract__ = True
pkey = columns.Integer(primary_key=True)
data = columns.Integer()
class TestAbstractModelClasses(BaseCassEngTestCase):
def test_id_field_is_not_created(self):
""" Tests that an id field is not automatically generated on abstract classes """
assert not hasattr(AbstractModel, 'id')
assert not hasattr(AbstractModelWithCol, 'id')
def test_id_field_is_not_created_on_subclass(self):
assert not hasattr(ConcreteModel, 'id')
def test_abstract_attribute_is_not_inherited(self):
""" Tests that __abstract__ attribute is not inherited """
assert not ConcreteModel.__abstract__
assert not ConcreteModelWithCol.__abstract__
def test_attempting_to_save_abstract_model_fails(self):
""" Attempting to save a model from an abstract model should fail """
with self.assertRaises(CQLEngineException):
AbstractModelWithFullCols.create(pkey=1, data=2)
def test_attempting_to_create_abstract_table_fails(self):
""" Attempting to create a table from an abstract model should fail """
from cqlengine.management import create_table
with self.assertRaises(CQLEngineException):
create_table(AbstractModelWithFullCols)
def test_attempting_query_on_abstract_model_fails(self):
""" Tests attempting to execute query with an abstract model fails """
with self.assertRaises(CQLEngineException):
iter(AbstractModelWithFullCols.objects(pkey=5)).next()
def test_abstract_columns_are_inherited(self):
""" Tests that columns defined in the abstract class are inherited into the concrete class """
assert hasattr(ConcreteModelWithCol, 'pkey')
assert isinstance(ConcreteModelWithCol.pkey, property)
assert isinstance(ConcreteModelWithCol._columns['pkey'], columns.Column)
def test_concrete_class_table_creation_cycle(self):
""" Tests that models with inherited abstract classes can be created, and have io performed """
from cqlengine.management import create_table, delete_table
create_table(ConcreteModelWithCol)
w1 = ConcreteModelWithCol.create(pkey=5, data=6)
w2 = ConcreteModelWithCol.create(pkey=6, data=7)
r1 = ConcreteModelWithCol.get(pkey=5)
r2 = ConcreteModelWithCol.get(pkey=6)
assert w1.pkey == r1.pkey
assert w1.data == r1.data
assert w2.pkey == r2.pkey
assert w2.data == r2.data
delete_table(ConcreteModelWithCol)

View File

@@ -128,6 +128,10 @@ Model Methods
Model Attributes
================
.. attribute:: Model.__abstract__
*Optional.* Indicates that this model is only intended to be used as a base class for other models. You can't create tables for abstract models, but checks around schema validity are skipped during class construction.
.. attribute:: Model.table_name
*Optional.* Sets the name of the CQL table for this model. If left blank, the table name will be the name of the model, with it's module name as it's prefix. Manually defined table names are not inherited.