Files
deb-python-cassandra-driver/cqlengine/models.py
2012-11-25 18:28:35 -08:00

161 lines
5.6 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 = values.get(name, None)
if value is not None: value = column.to_python(value)
value_mngr = column.value_manager(self, column, value)
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.Column)]
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
#TODO: check that the defined columns don't conflict with any of the Model API's existing attributes/methods
#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