Files
deb-python-cassandra-driver/cqlengine/models.py
Blake Eggleston 2db6426d02 added fault tolerant connection manager, which will be easily extended to support connection pooling
added support for multiple connections
removed dependence on setting keyspace name on opening connection
2012-11-24 23:11:09 -08:00

158 lines
5.4 KiB
Python

from collections import OrderedDict
from cqlengine import columns
from cqlengine.exceptions import ModelException
from cqlengine.query import QuerySet
class ModelDefinitionException(ModelException): pass
class BaseModel(object):
"""
The base model class, don't inherit from this, inherit from Model, defined below
"""
#table names will be generated automatically from it's model and package name
#however, you can alse define them manually here
db_name = None
#the keyspace for this model
keyspace = 'cqlengine'
def __init__(self, **values):
self._values = {}
for name, column in self._columns.items():
value_mngr = column.value_manager(self, column, values.get(name, None))
self._values[name] = value_mngr
@classmethod
def column_family_name(cls, include_keyspace=True):
"""
Returns the column family name if it's been defined
otherwise, it creates it from the module and class name
"""
if cls.db_name:
return cls.db_name.lower()
cf_name = cls.__module__ + '.' + cls.__name__
cf_name = cf_name.replace('.', '_')
#trim to less than 48 characters or cassandra will complain
cf_name = cf_name[-48:]
cf_name = cf_name.lower()
if not include_keyspace: return cf_name
return '{}.{}'.format(cls.keyspace, cf_name)
@property
def pk(self):
""" Returns the object's primary key """
return getattr(self, self._pk_name)
def validate(self):
""" Cleans and validates the field values """
for name, col in self._columns.items():
val = col.validate(getattr(self, name))
setattr(self, name, val)
def as_dict(self):
""" Returns a map of column names to cleaned values """
values = self._dynamic_columns or {}
for name, col in self._columns.items():
values[name] = col.to_database(getattr(self, name, None))
return values
def save(self):
is_new = self.pk is None
self.validate()
self.objects.save(self)
#delete any fields that have been deleted / set to none
return self
def delete(self):
""" Deletes this instance """
self.objects.delete_instance(self)
class ModelMetaClass(type):
def __new__(cls, name, bases, attrs):
"""
"""
#move column definitions into columns dict
#and set default column names
column_dict = OrderedDict()
primary_keys = OrderedDict()
pk_name = None
def _transform_column(col_name, col_obj):
column_dict[col_name] = col_obj
if col_obj.primary_key:
primary_keys[col_name] = col_obj
col_obj.set_column_name(col_name)
#set properties
_get = lambda self: self._values[col_name].getval()
_set = lambda self, val: self._values[col_name].setval(val)
_del = lambda self: self._values[col_name].delval()
if col_obj.can_delete:
attrs[col_name] = property(_get, _set)
else:
attrs[col_name] = property(_get, _set, _del)
column_definitions = [(k,v) for k,v in attrs.items() if isinstance(v, columns.BaseColumn)]
column_definitions = sorted(column_definitions, lambda x,y: cmp(x[1].position, y[1].position))
#prepend primary key if none has been defined
if 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
#transform column definitions
for k,v in column_definitions:
if pk_name is None and v.primary_key:
pk_name = k
_transform_column(k,v)
#setup primary key shortcut
if pk_name != 'pk':
attrs['pk'] = attrs[pk_name]
#check for duplicate column names
col_names = set()
for v in column_dict.values():
if v.db_field_name in col_names:
raise ModelException("{} defines the column {} more than once".format(name, v.db_field_name))
col_names.add(v.db_field_name)
#check for indexes on models with multiple primary keys
if len([1 for k,v in column_definitions if v.primary_key]) > 1:
if len([1 for k,v in column_definitions if v.index]) > 0:
raise ModelDefinitionException(
'Indexes on models with multiple primary keys is not supported')
#get column family name
cf_name = attrs.pop('db_name', name)
#create db_name -> model name map for loading
db_map = {}
for field_name, col in column_dict.items():
db_map[col.db_field_name] = field_name
#add management members to the class
attrs['_columns'] = column_dict
attrs['_primary_keys'] = primary_keys
attrs['_db_map'] = db_map
attrs['_pk_name'] = pk_name
attrs['_dynamic_columns'] = {}
#create the class and add a QuerySet to it
klass = super(ModelMetaClass, cls).__new__(cls, name, bases, attrs)
klass.objects = QuerySet(klass)
return klass
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
"""
__metaclass__ = ModelMetaClass