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

243 lines
6.7 KiB
Python

#column field types
from datetime import datetime
import re
from uuid import uuid1, uuid4
from cqlengine.exceptions import ValidationError
class BaseValueManager(object):
def __init__(self, instance, column, value):
self.instance = instance
self.column = column
self.initial_value = value
self.value = value
@property
def deleted(self):
return self.value is None and self.initial_value is not None
def getval(self):
return self.value
def setval(self, val):
self.value = val
def delval(self):
self.value = None
def get_property(self):
_get = lambda slf: self.getval()
_set = lambda slf, val: self.setval(val)
_del = lambda slf: self.delval()
if self.column.can_delete:
return property(_get, _set, _del)
else:
return property(_get, _set)
class Column(object):
#the cassandra type this column maps to
db_type = None
value_manager = BaseValueManager
instance_counter = 0
def __init__(self, primary_key=False, index=False, db_field=None, default=None, required=True):
"""
:param primary_key: bool flag, indicates this column is a primary key. The first primary key defined
on a model is the partition key, all others are cluster keys
:param index: bool flag, indicates an index should be created for this column
:param db_field: the fieldname this field will map to in the database
:param default: the default value, can be a value or a callable (no args)
:param required: boolean, is the field required?
"""
self.primary_key = primary_key
self.index = index
self.db_field = db_field
self.default = default
self.required = required
#the column name in the model definition
self.column_name = None
self.value = None
#keep track of instantiation order
self.position = Column.instance_counter
Column.instance_counter += 1
def validate(self, value):
"""
Returns a cleaned and validated value. Raises a ValidationError
if there's a problem
"""
if value is None:
if self.has_default:
return self.get_default()
elif self.required:
raise ValidationError('{} - None values are not allowed'.format(self.column_name or self.db_field))
return value
def to_python(self, value):
"""
Converts data from the database into python values
raises a ValidationError if the value can't be converted
"""
return value
def to_database(self, value):
"""
Converts python value into database value
"""
if value is None and self.has_default:
return self.get_default()
return value
@property
def has_default(self):
return bool(self.default)
@property
def is_primary_key(self):
return self.primary_key
@property
def can_delete(self):
return not self.primary_key
def get_default(self):
if self.has_default:
if callable(self.default):
return self.default()
else:
return self.default
def get_column_def(self):
"""
Returns a column definition for CQL table definition
"""
return '{} {}'.format(self.db_field_name, self.db_type)
def set_column_name(self, name):
"""
Sets the column name during document class construction
This value will be ignored if db_field is set in __init__
"""
self.column_name = name
@property
def db_field_name(self):
""" Returns the name of the cql name of this column """
return self.db_field or self.column_name
@property
def db_index_name(self):
""" Returns the name of the cql index """
return 'index_{}'.format(self.db_field_name)
class Bytes(Column):
db_type = 'blob'
class Ascii(Column):
db_type = 'ascii'
class Text(Column):
db_type = 'text'
class Integer(Column):
db_type = 'int'
def validate(self, value):
val = super(Integer, self).validate(value)
try:
return long(val)
except (TypeError, ValueError):
raise ValidationError("{} can't be converted to integral value".format(value))
def to_python(self, value):
return self.validate(value)
def to_database(self, value):
return self.validate(value)
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
return datetime.fromtimestamp(value)
def to_database(self, value):
value = super(DateTime, self).to_database(value)
if not isinstance(value, datetime):
raise ValidationError("'{}' is not a datetime object".format(value))
return value.strftime('%Y-%m-%d %H:%M:%S')
class UUID(Column):
"""
Type 1 or 4 UUID
"""
db_type = 'uuid'
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)
from uuid import UUID as _UUID
if isinstance(val, _UUID): return val
if not self.re_uuid.match(val):
raise ValidationError("{} is not a valid uuid".format(value))
return _UUID(val)
class Boolean(Column):
db_type = 'boolean'
def to_python(self, value):
return bool(value)
def to_database(self, value):
return bool(value)
class Float(Column):
db_type = 'double'
def __init__(self, double_precision=True, **kwargs):
self.db_type = 'double' if double_precision else 'float'
super(Float, self).__init__(**kwargs)
def validate(self, value):
try:
return float(value)
except (TypeError, ValueError):
raise ValidationError("{} is not a valid float".format(value))
def to_python(self, value):
return self.validate(value)
def to_database(self, value):
return self.validate(value)
class Decimal(Column):
db_type = 'decimal'
class Counter(Column):
#TODO: counter field
def __init__(self, **kwargs):
super(Counter, self).__init__(**kwargs)
raise NotImplementedError
class ForeignKey(Column):
#TODO: Foreign key field
def __init__(self, **kwargs):
super(ForeignKey, self).__init__(**kwargs)
raise NotImplementedError