~justin-fathomdb/nova/justinsb-openstack-api-volumes

« back to all changes in this revision

Viewing changes to vendor/boto/boto/sdb/db/property.py

  • Committer: Jesse Andrews
  • Date: 2010-05-28 06:05:26 UTC
  • Revision ID: git-v1:bf6e6e718cdc7488e2da87b21e258ccc065fe499
initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (c) 2006,2007,2008 Mitch Garnaat http://garnaat.org/
 
2
#
 
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-
 
9
# lowing conditions:
 
10
#
 
11
# The above copyright notice and this permission notice shall be included
 
12
# in all copies or substantial portions of the Software.
 
13
#
 
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
 
20
# IN THE SOFTWARE.
 
21
 
 
22
import datetime
 
23
from key import Key
 
24
from boto.utils import Password
 
25
from boto.sdb.db.query import Query
 
26
import re
 
27
import boto
 
28
import boto.s3.key
 
29
from boto.sdb.db.blob import Blob
 
30
 
 
31
class Property(object):
 
32
 
 
33
    data_type = str
 
34
    type_name = ''
 
35
    name = ''
 
36
    verbose_name = ''
 
37
 
 
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
 
41
        self.name = name
 
42
        self.default = default
 
43
        self.required = required
 
44
        self.validator = validator
 
45
        self.choices = choices
 
46
        self.slot_name = '_'
 
47
        self.unique = unique
 
48
        
 
49
    def __get__(self, obj, objtype):
 
50
        if obj:
 
51
            obj.load()
 
52
            return getattr(obj, self.slot_name)
 
53
        else:
 
54
            return None
 
55
 
 
56
    def __set__(self, obj, value):
 
57
        self.validate(value)
 
58
 
 
59
        # Fire off any on_set functions
 
60
        try:
 
61
            if obj._loaded and hasattr(obj, "on_set_%s" % self.name):
 
62
                fnc = getattr(obj, "on_set_%s" % self.name)
 
63
                value = fnc(value)
 
64
        except Exception:
 
65
            boto.log.exception("Exception running on_set_%s" % self.name)
 
66
 
 
67
        setattr(obj, self.slot_name, value)
 
68
 
 
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
 
73
 
 
74
    def default_validator(self, value):
 
75
        if value == self.default_value():
 
76
            return
 
77
        if not isinstance(value, self.data_type):
 
78
            raise TypeError, 'Validation Error, expecting %s, got %s' % (self.data_type, type(value))
 
79
                                      
 
80
    def default_value(self):
 
81
        return self.default
 
82
 
 
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)
 
88
        if self.validator:
 
89
            self.validator(value)
 
90
        else:
 
91
            self.default_validator(value)
 
92
        return value
 
93
 
 
94
    def empty(self, value):
 
95
        return not value
 
96
 
 
97
    def get_value_for_datastore(self, model_instance):
 
98
        return getattr(model_instance, self.name)
 
99
 
 
100
    def make_value_from_datastore(self, value):
 
101
        return value
 
102
 
 
103
    def get_choices(self):
 
104
        if callable(self.choices):
 
105
            return self.choices()
 
106
        return self.choices
 
107
 
 
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'
 
112
    else:
 
113
        raise TypeError, 'Expecting String, got %s' % type(value)
 
114
 
 
115
class StringProperty(Property):
 
116
 
 
117
    type_name = 'String'
 
118
    
 
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)
 
122
 
 
123
class TextProperty(Property):
 
124
    
 
125
    type_name = 'Text'
 
126
    
 
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
 
131
 
 
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
 
137
 
 
138
class PasswordProperty(StringProperty):
 
139
    """
 
140
    Hashed property who's original value can not be
 
141
    retrieved, but still can be compaired.
 
142
    """
 
143
    data_type = Password
 
144
    type_name = 'Password'
 
145
 
 
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)
 
149
 
 
150
    def make_value_from_datastore(self, value):
 
151
        p = Password(value)
 
152
        return p
 
153
 
 
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):
 
157
            return str(value)
 
158
        else:
 
159
            return None
 
160
 
 
161
    def __set__(self, obj, value):
 
162
        if not isinstance(value, Password):
 
163
            p = Password()
 
164
            p.set(value)
 
165
            value = p
 
166
        Property.__set__(self, obj, value)
 
167
 
 
168
    def __get__(self, obj, objtype):
 
169
        return Password(StringProperty.__get__(self, obj, objtype))
 
170
 
 
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'
 
176
        else:
 
177
            raise TypeError, 'Expecting Password, got %s' % type(value)
 
178
 
 
179
class BlobProperty(Property):
 
180
    data_type = Blob
 
181
    type_name = "blob"
 
182
 
 
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))
 
187
                id = None
 
188
                if oldb:
 
189
                    id = oldb.id
 
190
                b = Blob(value=value, id=id)
 
191
                value = b
 
192
        Property.__set__(self, obj, value)
 
193
 
 
194
class S3KeyProperty(Property):
 
195
    
 
196
    data_type = boto.s3.key.Key
 
197
    type_name = 'S3Key'
 
198
    validate_regex = "^s3:\/\/([^\/]*)\/(.*)$"
 
199
 
 
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)
 
204
 
 
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):
 
209
            return
 
210
        match = re.match(self.validate_regex, value)
 
211
        if match:
 
212
            return
 
213
        raise TypeError, 'Validation Error, expecting %s, got %s' % (self.data_type, type(value))
 
214
 
 
215
    def __get__(self, obj, objtype):
 
216
        value = Property.__get__(self, obj, objtype)
 
217
        if value:
 
218
            if isinstance(value, self.data_type):
 
219
                return value
 
220
            match = re.match(self.validate_regex, value)
 
221
            if match:
 
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))
 
225
                if not k:
 
226
                    k = bucket.new_key(match.group(2))
 
227
                    k.set_contents_from_string("")
 
228
                return k
 
229
        else:
 
230
            return value
 
231
        
 
232
    def get_value_for_datastore(self, model_instance):
 
233
        value = Property.get_value_for_datastore(self, model_instance)
 
234
        if value:
 
235
            return "s3://%s/%s" % (value.bucket.name, value.name)
 
236
        else:
 
237
            return None
 
238
 
 
239
class IntegerProperty(Property):
 
240
 
 
241
    data_type = int
 
242
    type_name = 'Integer'
 
243
 
 
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)
 
247
        self.max = max
 
248
        self.min = min
 
249
 
 
250
    def validate(self, value):
 
251
        value = int(value)
 
252
        value = Property.validate(self, value)
 
253
        if value > self.max:
 
254
            raise ValueError, 'Maximum value is %d' % self.max
 
255
        if value < self.min:
 
256
            raise ValueError, 'Minimum value is %d' % self.min
 
257
        return value
 
258
    
 
259
    def empty(self, value):
 
260
        return value is None
 
261
 
 
262
class LongProperty(Property):
 
263
 
 
264
    data_type = long
 
265
    type_name = 'Long'
 
266
 
 
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)
 
270
 
 
271
    def validate(self, value):
 
272
        value = long(value)
 
273
        value = Property.validate(self, value)
 
274
        min = -9223372036854775808
 
275
        max = 9223372036854775807
 
276
        if value > max:
 
277
            raise ValueError, 'Maximum value is %d' % max
 
278
        if value < min:
 
279
            raise ValueError, 'Minimum value is %d' % min
 
280
        return value
 
281
        
 
282
    def empty(self, value):
 
283
        return value is None
 
284
 
 
285
class BooleanProperty(Property):
 
286
 
 
287
    data_type = bool
 
288
    type_name = 'Boolean'
 
289
 
 
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)
 
293
 
 
294
    def empty(self, value):
 
295
        return value is None
 
296
    
 
297
class FloatProperty(Property):
 
298
 
 
299
    data_type = float
 
300
    type_name = 'Float'
 
301
 
 
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)
 
305
 
 
306
    def validate(self, value):
 
307
        value = float(value)
 
308
        value = Property.validate(self, value)
 
309
        return value
 
310
    
 
311
    def empty(self, value):
 
312
        return value is None
 
313
 
 
314
class DateTimeProperty(Property):
 
315
 
 
316
    data_type = datetime.datetime
 
317
    type_name = 'DateTime'
 
318
 
 
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
 
324
 
 
325
    def default_value(self):
 
326
        if self.auto_now or self.auto_now_add:
 
327
            return self.now()
 
328
        return Property.default_value(self)
 
329
 
 
330
    def validate(self, value):
 
331
        if value == None:
 
332
            return
 
333
        if not isinstance(value, self.data_type):
 
334
            raise TypeError, 'Validation Error, expecting %s, got %s' % (self.data_type, type(value))
 
335
 
 
336
    def get_value_for_datastore(self, model_instance):
 
337
        if self.auto_now:
 
338
            setattr(model_instance, self.name, self.now())
 
339
        return Property.get_value_for_datastore(self, model_instance)
 
340
 
 
341
    def now(self):
 
342
        return datetime.datetime.utcnow()
 
343
 
 
344
class ReferenceProperty(Property):
 
345
 
 
346
    data_type = Key
 
347
    type_name = 'Reference'
 
348
 
 
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
 
354
        
 
355
    def __get__(self, obj, objtype):
 
356
        if obj:
 
357
            value = getattr(obj, self.slot_name)
 
358
            if value == self.default_value():
 
359
                return 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)
 
366
            return value
 
367
    
 
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))
 
376
 
 
377
    def check_uuid(self, value):
 
378
        # This does a bit of hand waving to "type check" the string
 
379
        t = value.split('-')
 
380
        if len(t) != 5:
 
381
            raise ValueError
 
382
 
 
383
    def check_instance(self, value):
 
384
        try:
 
385
            obj_lineage = value.get_lineage()
 
386
            cls_lineage = self.reference_class.get_lineage()
 
387
            if obj_lineage.startswith(cls_lineage):
 
388
                return
 
389
            raise TypeError, '%s not instance of %s' % (obj_lineage, cls_lineage)
 
390
        except:
 
391
            raise ValueError, '%s is not a Model' % value
 
392
            
 
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():
 
397
            return
 
398
        if not isinstance(value, str) and not isinstance(value, unicode):
 
399
            self.check_instance(value)
 
400
        
 
401
class _ReverseReferenceProperty(Property):
 
402
    data_type = Query
 
403
    type_name = 'query'
 
404
 
 
405
    def __init__(self, model, prop, name):
 
406
        self.__model = model
 
407
        self.__property = prop
 
408
        self.collection_name = prop
 
409
        self.name = name
 
410
        self.item_type = model
 
411
 
 
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:
 
417
                props = []
 
418
                for prop in self.__property:
 
419
                    props.append("%s =" % prop)
 
420
                return query.filter(props, model_instance)
 
421
            else:
 
422
                return query.filter(self.__property + ' =', model_instance)
 
423
        else:
 
424
            return self
 
425
 
 
426
    def __set__(self, model_instance, value):
 
427
        """Not possible to set a new collection."""
 
428
        raise ValueError, 'Virtual property is read-only'
 
429
 
 
430
        
 
431
class CalculatedProperty(Property):
 
432
 
 
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
 
440
        
 
441
    def __get__(self, obj, objtype):
 
442
        value = self.default_value()
 
443
        if obj:
 
444
            try:
 
445
                value = getattr(obj, self.slot_name)
 
446
                if self.use_method:
 
447
                    value = value()
 
448
            except AttributeError:
 
449
                pass
 
450
        return value
 
451
 
 
452
    def __set__(self, obj, value):
 
453
        """Not possible to set a new AutoID."""
 
454
        pass
 
455
 
 
456
    def _set_direct(self, obj, value):
 
457
        if not self.use_method:
 
458
            setattr(obj, self.slot_name, value)
 
459
 
 
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__)
 
463
            return value
 
464
        else:
 
465
            return None
 
466
 
 
467
class ListProperty(Property):
 
468
    
 
469
    data_type = list
 
470
    type_name = 'List'
 
471
 
 
472
    def __init__(self, item_type, verbose_name=None, name=None, default=None, **kwds):
 
473
        if default is None:
 
474
            default = []
 
475
        self.item_type = item_type
 
476
        Property.__init__(self, verbose_name, name, default=default, required=True, **kwds)
 
477
 
 
478
    def validate(self, value):
 
479
        if value is not None:
 
480
            if not isinstance(value, list):
 
481
                value = [value]
 
482
 
 
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)
 
487
        else:
 
488
            item_type = self.item_type
 
489
 
 
490
        for item in value:
 
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
 
494
                else:
 
495
                    raise ValueError('Items in the %s list must all be %s instances' %
 
496
                                     (self.name, self.item_type.__name__))
 
497
        return value
 
498
 
 
499
    def empty(self, value):
 
500
        return value is None
 
501
 
 
502
    def default_value(self):
 
503
        return list(super(ListProperty, self).default_value())
 
504
 
 
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)
 
511
        else:
 
512
            item_type = self.item_type
 
513
        if isinstance(value, item_type):
 
514
            value = [value]
 
515
        elif value == None: # Override to allow them to set this to "None" to remove everything
 
516
            value = []
 
517
        return super(ListProperty, self).__set__(obj,value)
 
518
 
 
519
 
 
520
class MapProperty(Property):
 
521
    
 
522
    data_type = dict
 
523
    type_name = 'Map'
 
524
 
 
525
    def __init__(self, item_type=str, verbose_name=None, name=None, default=None, **kwds):
 
526
        if default is None:
 
527
            default = {}
 
528
        self.item_type = item_type
 
529
        Property.__init__(self, verbose_name, name, default=default, required=True, **kwds)
 
530
 
 
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'
 
535
 
 
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)
 
540
        else:
 
541
            item_type = self.item_type
 
542
 
 
543
        for key in value:
 
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
 
547
                else:
 
548
                    raise ValueError('Values in the %s Map must all be %s instances' %
 
549
                                     (self.name, self.item_type.__name__))
 
550
        return value
 
551
 
 
552
    def empty(self, value):
 
553
        return value is None
 
554
 
 
555
    def default_value(self):
 
556
        return {}