/boto-2.5.2/boto/sdb/db/property.py
Python | 703 lines | 664 code | 17 blank | 22 comment | 6 complexity | 52016ea2491f0956f43ee647b001fcc4 MD5 | raw file
- # Copyright (c) 2006,2007,2008 Mitch Garnaat http://garnaat.org/
- #
- # Permission is hereby granted, free of charge, to any person obtaining a
- # copy of this software and associated documentation files (the
- # "Software"), to deal in the Software without restriction, including
- # without limitation the rights to use, copy, modify, merge, publish, dis-
- # tribute, sublicense, and/or sell copies of the Software, and to permit
- # persons to whom the Software is furnished to do so, subject to the fol-
- # lowing conditions:
- #
- # The above copyright notice and this permission notice shall be included
- # in all copies or substantial portions of the Software.
- #
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
- # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
- # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
- # SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
- # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
- # IN THE SOFTWARE.
- import datetime
- from key import Key
- from boto.utils import Password
- from boto.sdb.db.query import Query
- import re
- import boto
- import boto.s3.key
- from boto.sdb.db.blob import Blob
- class Property(object):
- data_type = str
- type_name = ''
- name = ''
- verbose_name = ''
- def __init__(self, verbose_name=None, name=None, default=None,
- required=False, validator=None, choices=None, unique=False):
- self.verbose_name = verbose_name
- self.name = name
- self.default = default
- self.required = required
- self.validator = validator
- self.choices = choices
- if self.name:
- self.slot_name = '_' + self.name
- else:
- self.slot_name = '_'
- self.unique = unique
- def __get__(self, obj, objtype):
- if obj:
- obj.load()
- return getattr(obj, self.slot_name)
- else:
- return None
- def __set__(self, obj, value):
- self.validate(value)
- # Fire off any on_set functions
- try:
- if obj._loaded and hasattr(obj, "on_set_%s" % self.name):
- fnc = getattr(obj, "on_set_%s" % self.name)
- value = fnc(value)
- except Exception:
- boto.log.exception("Exception running on_set_%s" % self.name)
- setattr(obj, self.slot_name, value)
- def __property_config__(self, model_class, property_name):
- self.model_class = model_class
- self.name = property_name
- self.slot_name = '_' + self.name
- def default_validator(self, value):
- if isinstance(value, basestring) or value == self.default_value():
- return
- if not isinstance(value, self.data_type):
- raise TypeError('Validation Error, %s.%s expecting %s, got %s' % (self.model_class.__name__, self.name, self.data_type, type(value)))
- def default_value(self):
- return self.default
- def validate(self, value):
- if self.required and value == None:
- raise ValueError('%s is a required property' % self.name)
- if self.choices and value and not value in self.choices:
- raise ValueError('%s not a valid choice for %s.%s' % (value, self.model_class.__name__, self.name))
- if self.validator:
- self.validator(value)
- else:
- self.default_validator(value)
- return value
- def empty(self, value):
- return not value
- def get_value_for_datastore(self, model_instance):
- return getattr(model_instance, self.name)
- def make_value_from_datastore(self, value):
- return value
- def get_choices(self):
- if callable(self.choices):
- return self.choices()
- return self.choices
- def validate_string(value):
- if value == None:
- return
- elif isinstance(value, str) or isinstance(value, unicode):
- if len(value) > 1024:
- raise ValueError('Length of value greater than maxlength')
- else:
- raise TypeError('Expecting String, got %s' % type(value))
- class StringProperty(Property):
- type_name = 'String'
- def __init__(self, verbose_name=None, name=None, default='',
- required=False, validator=validate_string,
- choices=None, unique=False):
- Property.__init__(self, verbose_name, name, default, required,
- validator, choices, unique)
- class TextProperty(Property):
- type_name = 'Text'
- def __init__(self, verbose_name=None, name=None, default='',
- required=False, validator=None, choices=None,
- unique=False, max_length=None):
- Property.__init__(self, verbose_name, name, default, required,
- validator, choices, unique)
- self.max_length = max_length
- def validate(self, value):
- value = super(TextProperty, self).validate(value)
- if not isinstance(value, str) and not isinstance(value, unicode):
- raise TypeError('Expecting Text, got %s' % type(value))
- if self.max_length and len(value) > self.max_length:
- raise ValueError('Length of value greater than maxlength %s' % self.max_length)
- class PasswordProperty(StringProperty):
- """
- Hashed property whose original value can not be
- retrieved, but still can be compared.
- Works by storing a hash of the original value instead
- of the original value. Once that's done all that
- can be retrieved is the hash.
- The comparison
- obj.password == 'foo'
- generates a hash of 'foo' and compares it to the
- stored hash.
- Underlying data type for hashing, storing, and comparing
- is boto.utils.Password. The default hash function is
- defined there ( currently sha512 in most cases, md5
- where sha512 is not available )
- It's unlikely you'll ever need to use a different hash
- function, but if you do, you can control the behavior
- in one of two ways:
- 1) Specifying hashfunc in PasswordProperty constructor
- import hashlib
- class MyModel(model):
- password = PasswordProperty(hashfunc=hashlib.sha224)
- 2) Subclassing Password and PasswordProperty
- class SHA224Password(Password):
- hashfunc=hashlib.sha224
- class SHA224PasswordProperty(PasswordProperty):
- data_type=MyPassword
- type_name="MyPassword"
- class MyModel(Model):
- password = SHA224PasswordProperty()
- """
- data_type = Password
- type_name = 'Password'
- def __init__(self, verbose_name=None, name=None, default='', required=False,
- validator=None, choices=None, unique=False, hashfunc=None):
- """
- The hashfunc parameter overrides the default hashfunc in boto.utils.Password.
- The remaining parameters are passed through to StringProperty.__init__"""
- StringProperty.__init__(self, verbose_name, name, default, required,
- validator, choices, unique)
- self.hashfunc = hashfunc
- def make_value_from_datastore(self, value):
- p = self.data_type(value, hashfunc=self.hashfunc)
- return p
- def get_value_for_datastore(self, model_instance):
- value = StringProperty.get_value_for_datastore(self, model_instance)
- if value and len(value):
- return str(value)
- else:
- return None
- def __set__(self, obj, value):
- if not isinstance(value, self.data_type):
- p = self.data_type(hashfunc=self.hashfunc)
- p.set(value)
- value = p
- Property.__set__(self, obj, value)
- def __get__(self, obj, objtype):
- return self.data_type(StringProperty.__get__(self, obj, objtype), hashfunc=self.hashfunc)
- def validate(self, value):
- value = Property.validate(self, value)
- if isinstance(value, self.data_type):
- if len(value) > 1024:
- raise ValueError('Length of value greater than maxlength')
- else:
- raise TypeError('Expecting %s, got %s' % (type(self.data_type), type(value)))
- class BlobProperty(Property):
- data_type = Blob
- type_name = "blob"
- def __set__(self, obj, value):
- if value != self.default_value():
- if not isinstance(value, Blob):
- oldb = self.__get__(obj, type(obj))
- id = None
- if oldb:
- id = oldb.id
- b = Blob(value=value, id=id)
- value = b
- Property.__set__(self, obj, value)
- class S3KeyProperty(Property):
- data_type = boto.s3.key.Key
- type_name = 'S3Key'
- validate_regex = "^s3:\/\/([^\/]*)\/(.*)$"
- def __init__(self, verbose_name=None, name=None, default=None,
- required=False, validator=None, choices=None, unique=False):
- Property.__init__(self, verbose_name, name, default, required,
- validator, choices, unique)
- def validate(self, value):
- value = super(S3KeyProperty, self).validate(value)
- if value == self.default_value() or value == str(self.default_value()):
- return self.default_value()
- if isinstance(value, self.data_type):
- return
- match = re.match(self.validate_regex, value)
- if match:
- return
- raise TypeError('Validation Error, expecting %s, got %s' % (self.data_type, type(value)))
- def __get__(self, obj, objtype):
- value = Property.__get__(self, obj, objtype)
- if value:
- if isinstance(value, self.data_type):
- return value
- match = re.match(self.validate_regex, value)
- if match:
- s3 = obj._manager.get_s3_connection()
- bucket = s3.get_bucket(match.group(1), validate=False)
- k = bucket.get_key(match.group(2))
- if not k:
- k = bucket.new_key(match.group(2))
- k.set_contents_from_string("")
- return k
- else:
- return value
- def get_value_for_datastore(self, model_instance):
- value = Property.get_value_for_datastore(self, model_instance)
- if value:
- return "s3://%s/%s" % (value.bucket.name, value.name)
- else:
- return None
- class IntegerProperty(Property):
- data_type = int
- type_name = 'Integer'
- def __init__(self, verbose_name=None, name=None, default=0, required=False,
- validator=None, choices=None, unique=False, max=2147483647, min=-2147483648):
- Property.__init__(self, verbose_name, name, default, required, validator, choices, unique)
- self.max = max
- self.min = min
- def validate(self, value):
- value = int(value)
- value = Property.validate(self, value)
- if value > self.max:
- raise ValueError('Maximum value is %d' % self.max)
- if value < self.min:
- raise ValueError('Minimum value is %d' % self.min)
- return value
- def empty(self, value):
- return value is None
- def __set__(self, obj, value):
- if value == "" or value == None:
- value = 0
- return Property.__set__(self, obj, value)
- class LongProperty(Property):
- data_type = long
- type_name = 'Long'
- def __init__(self, verbose_name=None, name=None, default=0, required=False,
- validator=None, choices=None, unique=False):
- Property.__init__(self, verbose_name, name, default, required, validator, choices, unique)
- def validate(self, value):
- value = long(value)
- value = Property.validate(self, value)
- min = -9223372036854775808
- max = 9223372036854775807
- if value > max:
- raise ValueError('Maximum value is %d' % max)
- if value < min:
- raise ValueError('Minimum value is %d' % min)
- return value
- def empty(self, value):
- return value is None
- class BooleanProperty(Property):
- data_type = bool
- type_name = 'Boolean'
- def __init__(self, verbose_name=None, name=None, default=False, required=False,
- validator=None, choices=None, unique=False):
- Property.__init__(self, verbose_name, name, default, required, validator, choices, unique)
- def empty(self, value):
- return value is None
- class FloatProperty(Property):
- data_type = float
- type_name = 'Float'
- def __init__(self, verbose_name=None, name=None, default=0.0, required=False,
- validator=None, choices=None, unique=False):
- Property.__init__(self, verbose_name, name, default, required, validator, choices, unique)
- def validate(self, value):
- value = float(value)
- value = Property.validate(self, value)
- return value
- def empty(self, value):
- return value is None
- class DateTimeProperty(Property):
- """This class handles both the datetime.datetime object
- And the datetime.date objects. It can return either one,
- depending on the value stored in the database"""
- data_type = datetime.datetime
- type_name = 'DateTime'
- def __init__(self, verbose_name=None, auto_now=False, auto_now_add=False, name=None,
- default=None, required=False, validator=None, choices=None, unique=False):
- Property.__init__(self, verbose_name, name, default, required, validator, choices, unique)
- self.auto_now = auto_now
- self.auto_now_add = auto_now_add
- def default_value(self):
- if self.auto_now or self.auto_now_add:
- return self.now()
- return Property.default_value(self)
- def validate(self, value):
- if value == None:
- return
- if isinstance(value, datetime.date):
- return value
- return super(DateTimeProperty, self).validate(value)
- def get_value_for_datastore(self, model_instance):
- if self.auto_now:
- setattr(model_instance, self.name, self.now())
- return Property.get_value_for_datastore(self, model_instance)
- def now(self):
- return datetime.datetime.utcnow()
- class DateProperty(Property):
- data_type = datetime.date
- type_name = 'Date'
- def __init__(self, verbose_name=None, auto_now=False, auto_now_add=False, name=None,
- default=None, required=False, validator=None, choices=None, unique=False):
- Property.__init__(self, verbose_name, name, default, required, validator, choices, unique)
- self.auto_now = auto_now
- self.auto_now_add = auto_now_add
- def default_value(self):
- if self.auto_now or self.auto_now_add:
- return self.now()
- return Property.default_value(self)
- def validate(self, value):
- value = super(DateProperty, self).validate(value)
- if value == None:
- return
- if not isinstance(value, self.data_type):
- raise TypeError('Validation Error, expecting %s, got %s' % (self.data_type, type(value)))
- def get_value_for_datastore(self, model_instance):
- if self.auto_now:
- setattr(model_instance, self.name, self.now())
- val = Property.get_value_for_datastore(self, model_instance)
- if isinstance(val, datetime.datetime):
- val = val.date()
- return val
- def now(self):
- return datetime.date.today()
- class TimeProperty(Property):
- data_type = datetime.time
- type_name = 'Time'
- def __init__(self, verbose_name=None, name=None,
- default=None, required=False, validator=None, choices=None, unique=False):
- Property.__init__(self, verbose_name, name, default, required, validator, choices, unique)
- def validate(self, value):
- value = super(TimeProperty, self).validate(value)
- if value is None:
- return
- if not isinstance(value, self.data_type):
- raise TypeError('Validation Error, expecting %s, got %s' % (self.data_type, type(value)))
- class ReferenceProperty(Property):
- data_type = Key
- type_name = 'Reference'
- def __init__(self, reference_class=None, collection_name=None,
- verbose_name=None, name=None, default=None, required=False, validator=None, choices=None, unique=False):
- Property.__init__(self, verbose_name, name, default, required, validator, choices, unique)
- self.reference_class = reference_class
- self.collection_name = collection_name
- def __get__(self, obj, objtype):
- if obj:
- value = getattr(obj, self.slot_name)
- if value == self.default_value():
- return value
- # If the value is still the UUID for the referenced object, we need to create
- # the object now that is the attribute has actually been accessed. This lazy
- # instantiation saves unnecessary roundtrips to SimpleDB
- if isinstance(value, str) or isinstance(value, unicode):
- value = self.reference_class(value)
- setattr(obj, self.name, value)
- return value
- def __set__(self, obj, value):
- """Don't allow this object to be associated to itself
- This causes bad things to happen"""
- if value != None and (obj.id == value or (hasattr(value, "id") and obj.id == value.id)):
- raise ValueError("Can not associate an object with itself!")
- return super(ReferenceProperty, self).__set__(obj, value)
- def __property_config__(self, model_class, property_name):
- Property.__property_config__(self, model_class, property_name)
- if self.collection_name is None:
- self.collection_name = '%s_%s_set' % (model_class.__name__.lower(), self.name)
- if hasattr(self.reference_class, self.collection_name):
- raise ValueError('duplicate property: %s' % self.collection_name)
- setattr(self.reference_class, self.collection_name,
- _ReverseReferenceProperty(model_class, property_name, self.collection_name))
- def check_uuid(self, value):
- # This does a bit of hand waving to "type check" the string
- t = value.split('-')
- if len(t) != 5:
- raise ValueError
- def check_instance(self, value):
- try:
- obj_lineage = value.get_lineage()
- cls_lineage = self.reference_class.get_lineage()
- if obj_lineage.startswith(cls_lineage):
- return
- raise TypeError('%s not instance of %s' % (obj_lineage, cls_lineage))
- except:
- raise ValueError('%s is not a Model' % value)
- def validate(self, value):
- if self.validator:
- self.validator(value)
- if self.required and value == None:
- raise ValueError('%s is a required property' % self.name)
- if value == self.default_value():
- return
- if not isinstance(value, str) and not isinstance(value, unicode):
- self.check_instance(value)
- class _ReverseReferenceProperty(Property):
- data_type = Query
- type_name = 'query'
- def __init__(self, model, prop, name):
- self.__model = model
- self.__property = prop
- self.collection_name = prop
- self.name = name
- self.item_type = model
- def __get__(self, model_instance, model_class):
- """Fetches collection of model instances of this collection property."""
- if model_instance is not None:
- query = Query(self.__model)
- if isinstance(self.__property, list):
- props = []
- for prop in self.__property:
- props.append("%s =" % prop)
- return query.filter(props, model_instance)
- else:
- return query.filter(self.__property + ' =', model_instance)
- else:
- return self
- def __set__(self, model_instance, value):
- """Not possible to set a new collection."""
- raise ValueError('Virtual property is read-only')
- class CalculatedProperty(Property):
- def __init__(self, verbose_name=None, name=None, default=None,
- required=False, validator=None, choices=None,
- calculated_type=int, unique=False, use_method=False):
- Property.__init__(self, verbose_name, name, default, required,
- validator, choices, unique)
- self.calculated_type = calculated_type
- self.use_method = use_method
- def __get__(self, obj, objtype):
- value = self.default_value()
- if obj:
- try:
- value = getattr(obj, self.slot_name)
- if self.use_method:
- value = value()
- except AttributeError:
- pass
- return value
- def __set__(self, obj, value):
- """Not possible to set a new AutoID."""
- pass
- def _set_direct(self, obj, value):
- if not self.use_method:
- setattr(obj, self.slot_name, value)
- def get_value_for_datastore(self, model_instance):
- if self.calculated_type in [str, int, bool]:
- value = self.__get__(model_instance, model_instance.__class__)
- return value
- else:
- return None
- class ListProperty(Property):
- data_type = list
- type_name = 'List'
- def __init__(self, item_type, verbose_name=None, name=None, default=None, **kwds):
- if default is None:
- default = []
- self.item_type = item_type
- Property.__init__(self, verbose_name, name, default=default, required=True, **kwds)
- def validate(self, value):
- if self.validator:
- self.validator(value)
- if value is not None:
- if not isinstance(value, list):
- value = [value]
- if self.item_type in (int, long):
- item_type = (int, long)
- elif self.item_type in (str, unicode):
- item_type = (str, unicode)
- else:
- item_type = self.item_type
- for item in value:
- if not isinstance(item, item_type):
- if item_type == (int, long):
- raise ValueError('Items in the %s list must all be integers.' % self.name)
- else:
- raise ValueError('Items in the %s list must all be %s instances' %
- (self.name, self.item_type.__name__))
- return value
- def empty(self, value):
- return value is None
- def default_value(self):
- return list(super(ListProperty, self).default_value())
- def __set__(self, obj, value):
- """Override the set method to allow them to set the property to an instance of the item_type instead of requiring a list to be passed in"""
- if self.item_type in (int, long):
- item_type = (int, long)
- elif self.item_type in (str, unicode):
- item_type = (str, unicode)
- else:
- item_type = self.item_type
- if isinstance(value, item_type):
- value = [value]
- elif value == None: # Override to allow them to set this to "None" to remove everything
- value = []
- return super(ListProperty, self).__set__(obj, value)
- class MapProperty(Property):
- data_type = dict
- type_name = 'Map'
- def __init__(self, item_type=str, verbose_name=None, name=None, default=None, **kwds):
- if default is None:
- default = {}
- self.item_type = item_type
- Property.__init__(self, verbose_name, name, default=default, required=True, **kwds)
- def validate(self, value):
- value = super(MapProperty, self).validate(value)
- if value is not None:
- if not isinstance(value, dict):
- raise ValueError('Value must of type dict')
- if self.item_type in (int, long):
- item_type = (int, long)
- elif self.item_type in (str, unicode):
- item_type = (str, unicode)
- else:
- item_type = self.item_type
- for key in value:
- if not isinstance(value[key], item_type):
- if item_type == (int, long):
- raise ValueError('Values in the %s Map must all be integers.' % self.name)
- else:
- raise ValueError('Values in the %s Map must all be %s instances' %
- (self.name, self.item_type.__name__))
- return value
- def empty(self, value):
- return value is None
- def default_value(self):
- return {}