1
# Copyright (c) 2006,2007,2008 Mitch Garnaat http://garnaat.org/
3
# Permission is hereby granted, free of charge, to any person obtaining a
4
# copy of this software and associated documentation files (the
5
# "Software"), to deal in the Software without restriction, including
6
# without limitation the rights to use, copy, modify, merge, publish, dis-
7
# tribute, sublicense, and/or sell copies of the Software, and to permit
8
# persons to whom the Software is furnished to do so, subject to the fol-
11
# The above copyright notice and this permission notice shall be included
12
# in all copies or substantial portions of the Software.
14
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
16
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
17
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
18
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
24
from boto.utils import Password
25
from boto.sdb.db.query import Query
29
from boto.sdb.db.blob import Blob
31
class Property(object):
38
def __init__(self, verbose_name=None, name=None, default=None, required=False,
39
validator=None, choices=None, unique=False):
40
self.verbose_name = verbose_name
42
self.default = default
43
self.required = required
44
self.validator = validator
45
self.choices = choices
49
def __get__(self, obj, objtype):
52
return getattr(obj, self.slot_name)
56
def __set__(self, obj, value):
59
# Fire off any on_set functions
61
if obj._loaded and hasattr(obj, "on_set_%s" % self.name):
62
fnc = getattr(obj, "on_set_%s" % self.name)
65
boto.log.exception("Exception running on_set_%s" % self.name)
67
setattr(obj, self.slot_name, value)
69
def __property_config__(self, model_class, property_name):
70
self.model_class = model_class
71
self.name = property_name
72
self.slot_name = '_' + self.name
74
def default_validator(self, value):
75
if value == self.default_value():
77
if not isinstance(value, self.data_type):
78
raise TypeError, 'Validation Error, expecting %s, got %s' % (self.data_type, type(value))
80
def default_value(self):
83
def validate(self, value):
84
if self.required and value==None:
85
raise ValueError, '%s is a required property' % self.name
86
if self.choices and value and not value in self.choices:
87
raise ValueError, '%s not a valid choice for %s.%s' % (value, self.model_class.__name__, self.name)
91
self.default_validator(value)
94
def empty(self, value):
97
def get_value_for_datastore(self, model_instance):
98
return getattr(model_instance, self.name)
100
def make_value_from_datastore(self, value):
103
def get_choices(self):
104
if callable(self.choices):
105
return self.choices()
108
def validate_string(value):
109
if isinstance(value, str) or isinstance(value, unicode):
110
if len(value) > 1024:
111
raise ValueError, 'Length of value greater than maxlength'
113
raise TypeError, 'Expecting String, got %s' % type(value)
115
class StringProperty(Property):
119
def __init__(self, verbose_name=None, name=None, default='', required=False,
120
validator=validate_string, choices=None, unique=False):
121
Property.__init__(self, verbose_name, name, default, required, validator, choices, unique)
123
class TextProperty(Property):
127
def __init__(self, verbose_name=None, name=None, default='', required=False,
128
validator=None, choices=None, unique=False, max_length=None):
129
Property.__init__(self, verbose_name, name, default, required, validator, choices, unique)
130
self.max_length = max_length
132
def validate(self, value):
133
if not isinstance(value, str) and not isinstance(value, unicode):
134
raise TypeError, 'Expecting Text, got %s' % type(value)
135
if self.max_length and len(value) > self.max_length:
136
raise ValueError, 'Length of value greater than maxlength %s' % self.max_length
138
class PasswordProperty(StringProperty):
140
Hashed property who's original value can not be
141
retrieved, but still can be compaired.
144
type_name = 'Password'
146
def __init__(self, verbose_name=None, name=None, default='', required=False,
147
validator=None, choices=None, unique=False):
148
StringProperty.__init__(self, verbose_name, name, default, required, validator, choices, unique)
150
def make_value_from_datastore(self, value):
154
def get_value_for_datastore(self, model_instance):
155
value = StringProperty.get_value_for_datastore(self, model_instance)
156
if value and len(value):
161
def __set__(self, obj, value):
162
if not isinstance(value, Password):
166
Property.__set__(self, obj, value)
168
def __get__(self, obj, objtype):
169
return Password(StringProperty.__get__(self, obj, objtype))
171
def validate(self, value):
172
value = Property.validate(self, value)
173
if isinstance(value, Password):
174
if len(value) > 1024:
175
raise ValueError, 'Length of value greater than maxlength'
177
raise TypeError, 'Expecting Password, got %s' % type(value)
179
class BlobProperty(Property):
183
def __set__(self, obj, value):
184
if value != self.default_value():
185
if not isinstance(value, Blob):
186
oldb = self.__get__(obj, type(obj))
190
b = Blob(value=value, id=id)
192
Property.__set__(self, obj, value)
194
class S3KeyProperty(Property):
196
data_type = boto.s3.key.Key
198
validate_regex = "^s3:\/\/([^\/]*)\/(.*)$"
200
def __init__(self, verbose_name=None, name=None, default=None,
201
required=False, validator=None, choices=None, unique=False):
202
Property.__init__(self, verbose_name, name, default, required,
203
validator, choices, unique)
205
def validate(self, value):
206
if value == self.default_value() or value == str(self.default_value()):
207
return self.default_value()
208
if isinstance(value, self.data_type):
210
match = re.match(self.validate_regex, value)
213
raise TypeError, 'Validation Error, expecting %s, got %s' % (self.data_type, type(value))
215
def __get__(self, obj, objtype):
216
value = Property.__get__(self, obj, objtype)
218
if isinstance(value, self.data_type):
220
match = re.match(self.validate_regex, value)
222
s3 = obj._manager.get_s3_connection()
223
bucket = s3.get_bucket(match.group(1), validate=False)
224
k = bucket.get_key(match.group(2))
226
k = bucket.new_key(match.group(2))
227
k.set_contents_from_string("")
232
def get_value_for_datastore(self, model_instance):
233
value = Property.get_value_for_datastore(self, model_instance)
235
return "s3://%s/%s" % (value.bucket.name, value.name)
239
class IntegerProperty(Property):
242
type_name = 'Integer'
244
def __init__(self, verbose_name=None, name=None, default=0, required=False,
245
validator=None, choices=None, unique=False, max=2147483647, min=-2147483648):
246
Property.__init__(self, verbose_name, name, default, required, validator, choices, unique)
250
def validate(self, value):
252
value = Property.validate(self, value)
254
raise ValueError, 'Maximum value is %d' % self.max
256
raise ValueError, 'Minimum value is %d' % self.min
259
def empty(self, value):
262
class LongProperty(Property):
267
def __init__(self, verbose_name=None, name=None, default=0, required=False,
268
validator=None, choices=None, unique=False):
269
Property.__init__(self, verbose_name, name, default, required, validator, choices, unique)
271
def validate(self, value):
273
value = Property.validate(self, value)
274
min = -9223372036854775808
275
max = 9223372036854775807
277
raise ValueError, 'Maximum value is %d' % max
279
raise ValueError, 'Minimum value is %d' % min
282
def empty(self, value):
285
class BooleanProperty(Property):
288
type_name = 'Boolean'
290
def __init__(self, verbose_name=None, name=None, default=False, required=False,
291
validator=None, choices=None, unique=False):
292
Property.__init__(self, verbose_name, name, default, required, validator, choices, unique)
294
def empty(self, value):
297
class FloatProperty(Property):
302
def __init__(self, verbose_name=None, name=None, default=0.0, required=False,
303
validator=None, choices=None, unique=False):
304
Property.__init__(self, verbose_name, name, default, required, validator, choices, unique)
306
def validate(self, value):
308
value = Property.validate(self, value)
311
def empty(self, value):
314
class DateTimeProperty(Property):
316
data_type = datetime.datetime
317
type_name = 'DateTime'
319
def __init__(self, verbose_name=None, auto_now=False, auto_now_add=False, name=None,
320
default=None, required=False, validator=None, choices=None, unique=False):
321
Property.__init__(self, verbose_name, name, default, required, validator, choices, unique)
322
self.auto_now = auto_now
323
self.auto_now_add = auto_now_add
325
def default_value(self):
326
if self.auto_now or self.auto_now_add:
328
return Property.default_value(self)
330
def validate(self, value):
333
if not isinstance(value, self.data_type):
334
raise TypeError, 'Validation Error, expecting %s, got %s' % (self.data_type, type(value))
336
def get_value_for_datastore(self, model_instance):
338
setattr(model_instance, self.name, self.now())
339
return Property.get_value_for_datastore(self, model_instance)
342
return datetime.datetime.utcnow()
344
class ReferenceProperty(Property):
347
type_name = 'Reference'
349
def __init__(self, reference_class=None, collection_name=None,
350
verbose_name=None, name=None, default=None, required=False, validator=None, choices=None, unique=False):
351
Property.__init__(self, verbose_name, name, default, required, validator, choices, unique)
352
self.reference_class = reference_class
353
self.collection_name = collection_name
355
def __get__(self, obj, objtype):
357
value = getattr(obj, self.slot_name)
358
if value == self.default_value():
360
# If the value is still the UUID for the referenced object, we need to create
361
# the object now that is the attribute has actually been accessed. This lazy
362
# instantiation saves unnecessary roundtrips to SimpleDB
363
if isinstance(value, str) or isinstance(value, unicode):
364
value = self.reference_class(value)
365
setattr(obj, self.name, value)
368
def __property_config__(self, model_class, property_name):
369
Property.__property_config__(self, model_class, property_name)
370
if self.collection_name is None:
371
self.collection_name = '%s_%s_set' % (model_class.__name__.lower(), self.name)
372
if hasattr(self.reference_class, self.collection_name):
373
raise ValueError, 'duplicate property: %s' % self.collection_name
374
setattr(self.reference_class, self.collection_name,
375
_ReverseReferenceProperty(model_class, property_name, self.collection_name))
377
def check_uuid(self, value):
378
# This does a bit of hand waving to "type check" the string
383
def check_instance(self, value):
385
obj_lineage = value.get_lineage()
386
cls_lineage = self.reference_class.get_lineage()
387
if obj_lineage.startswith(cls_lineage):
389
raise TypeError, '%s not instance of %s' % (obj_lineage, cls_lineage)
391
raise ValueError, '%s is not a Model' % value
393
def validate(self, value):
394
if self.required and value==None:
395
raise ValueError, '%s is a required property' % self.name
396
if value == self.default_value():
398
if not isinstance(value, str) and not isinstance(value, unicode):
399
self.check_instance(value)
401
class _ReverseReferenceProperty(Property):
405
def __init__(self, model, prop, name):
407
self.__property = prop
408
self.collection_name = prop
410
self.item_type = model
412
def __get__(self, model_instance, model_class):
413
"""Fetches collection of model instances of this collection property."""
414
if model_instance is not None:
415
query = Query(self.__model)
416
if type(self.__property) == list:
418
for prop in self.__property:
419
props.append("%s =" % prop)
420
return query.filter(props, model_instance)
422
return query.filter(self.__property + ' =', model_instance)
426
def __set__(self, model_instance, value):
427
"""Not possible to set a new collection."""
428
raise ValueError, 'Virtual property is read-only'
431
class CalculatedProperty(Property):
433
def __init__(self, verbose_name=None, name=None, default=None,
434
required=False, validator=None, choices=None,
435
calculated_type=int, unique=False, use_method=False):
436
Property.__init__(self, verbose_name, name, default, required,
437
validator, choices, unique)
438
self.calculated_type = calculated_type
439
self.use_method = use_method
441
def __get__(self, obj, objtype):
442
value = self.default_value()
445
value = getattr(obj, self.slot_name)
448
except AttributeError:
452
def __set__(self, obj, value):
453
"""Not possible to set a new AutoID."""
456
def _set_direct(self, obj, value):
457
if not self.use_method:
458
setattr(obj, self.slot_name, value)
460
def get_value_for_datastore(self, model_instance):
461
if self.calculated_type in [str, int, bool]:
462
value = self.__get__(model_instance, model_instance.__class__)
467
class ListProperty(Property):
472
def __init__(self, item_type, verbose_name=None, name=None, default=None, **kwds):
475
self.item_type = item_type
476
Property.__init__(self, verbose_name, name, default=default, required=True, **kwds)
478
def validate(self, value):
479
if value is not None:
480
if not isinstance(value, list):
483
if self.item_type in (int, long):
484
item_type = (int, long)
485
elif self.item_type in (str, unicode):
486
item_type = (str, unicode)
488
item_type = self.item_type
491
if not isinstance(item, item_type):
492
if item_type == (int, long):
493
raise ValueError, 'Items in the %s list must all be integers.' % self.name
495
raise ValueError('Items in the %s list must all be %s instances' %
496
(self.name, self.item_type.__name__))
499
def empty(self, value):
502
def default_value(self):
503
return list(super(ListProperty, self).default_value())
505
def __set__(self, obj, value):
506
"""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"""
507
if self.item_type in (int, long):
508
item_type = (int, long)
509
elif self.item_type in (str, unicode):
510
item_type = (str, unicode)
512
item_type = self.item_type
513
if isinstance(value, item_type):
515
elif value == None: # Override to allow them to set this to "None" to remove everything
517
return super(ListProperty, self).__set__(obj,value)
520
class MapProperty(Property):
525
def __init__(self, item_type=str, verbose_name=None, name=None, default=None, **kwds):
528
self.item_type = item_type
529
Property.__init__(self, verbose_name, name, default=default, required=True, **kwds)
531
def validate(self, value):
532
if value is not None:
533
if not isinstance(value, dict):
534
raise ValueError, 'Value must of type dict'
536
if self.item_type in (int, long):
537
item_type = (int, long)
538
elif self.item_type in (str, unicode):
539
item_type = (str, unicode)
541
item_type = self.item_type
544
if not isinstance(value[key], item_type):
545
if item_type == (int, long):
546
raise ValueError, 'Values in the %s Map must all be integers.' % self.name
548
raise ValueError('Values in the %s Map must all be %s instances' %
549
(self.name, self.item_type.__name__))
552
def empty(self, value):
555
def default_value(self):