1
2
3
4
5
6 """ properties used by Document object """
7
8 from calendar import timegm
9 import decimal
10 import datetime
11 import re
12 import time
13
14 from couchdbkit.exceptions import *
15
16 __all__ = ['ALLOWED_PROPERTY_TYPES', 'Property', 'StringProperty',
17 'IntegerProperty','DecimalProperty', 'BooleanProperty',
18 'FloatProperty','DateTimeProperty', 'DateProperty',
19 'TimeProperty','DictProperty', 'ListProperty',
20 'StringListProperty', 'dict_to_json', 'list_to_json',
21 'value_to_json', 'MAP_TYPES_PROPERTIES', 'value_to_python',
22 'dict_to_python', 'list_to_python', 'convert_property',
23 'value_to_property', 'LazyDict', 'LazyList']
24
25 ALLOWED_PROPERTY_TYPES = set([
26 basestring,
27 str,
28 unicode,
29 bool,
30 int,
31 long,
32 float,
33 datetime.datetime,
34 datetime.date,
35 datetime.time,
36 decimal.Decimal,
37 dict,
38 list,
39 type(None)
40 ])
41
42 re_date = re.compile('^(\d{4})\D?(0[1-9]|1[0-2])\D?([12]\d|0[1-9]|3[01])$')
43 re_time = re.compile('^([01]\d|2[0-3])\D?([0-5]\d)\D?([0-5]\d)?\D?(\d{3})?$')
44 re_datetime = re.compile('^(\d{4})\D?(0[1-9]|1[0-2])\D?([12]\d|0[1-9]|3[01])(\D?([01]\d|2[0-3])\D?([0-5]\d)\D?([0-5]\d)?\D?(\d{3})?([zZ]|([\+-])([01]\d|2[0-3])\D?([0-5]\d)?)?)?$')
45 re_decimal = re.compile('^(\d+).(\d+)$')
48 """ Property base which all other properties
49 inherit."""
50 creation_counter = 0
51
52 - def __init__(self, verbose_name=None, name=None,
53 default=None, required=False, validators=None,
54 choices=None):
55 """ Default constructor for a property.
56
57 :param verbose_name: str, verbose name of field, could
58 be use for description
59 :param name: str, name of field
60 :param default: default value
61 :param required: True if field is required, default is False
62 :param validators: list of callable or callable, field validators
63 function that are executed when document is saved.
64 """
65 self.verbose_name = verbose_name
66 self.name = name
67 self.default = default
68 self.required = required
69 self.validators = validators
70 self.choices = choices
71 self.creation_counter = Property.creation_counter
72 Property.creation_counter += 1
73
75 self.document_class = document_class
76 if self.name is None:
77 self.name = property_name
78
80 """ method used to set value of the property when
81 we create the document. Don't check required. """
82 if value is not None:
83 value = self.to_json(self.validate(value, required=False))
84 document_instance._doc[self.name] = value
85
86 - def __get__(self, document_instance, document_class):
87 if document_instance is None:
88 return self
89
90 value = document_instance._doc.get(self.name)
91 if value is not None:
92 value = self._to_python(value)
93
94 return value
95
96 - def __set__(self, document_instance, value):
97 value = self.validate(value, required=False)
98 document_instance._doc[self.name] = self._to_json(value)
99
102
104 """ return default value """
105
106 default = self.default
107 if callable(default):
108 default = default()
109 return default
110
111 - def validate(self, value, required=True):
112 """ validate value """
113 if required and self.empty(value):
114 if self.required:
115 raise BadValueError("Property %s is required." % self.name)
116 else:
117 if self.choices:
118 match = False
119 for choice in self.choices:
120 if choice == value:
121 match = True
122 break
123 if not match:
124 raise BadValueError('Property %s is %r; must be one of %r' % (
125 self.name, value, self.choices))
126 if self.validators:
127 if isinstance(self.validators, (list, tuple,)):
128 for validator in self.validators:
129 if callable(validator):
130 validator(value)
131 elif callable(self.validators):
132 self.validators(value)
133 return value
134
136 """ test if value is empty """
137 return not value or value is None
138
140 if value == None:
141 return value
142 return self.to_python(value)
143
145 if value == None:
146 return value
147 return self.to_json(value)
148
150 """ convert to python type """
151 return unicode(value)
152
154 """ convert to json, Converted value is saved in couchdb. """
155 return self.to_python(value)
156
157 data_type = None
158
160 """ string property str or unicode property
161
162 *Value type*: unicode
163 """
164
165 to_python = unicode
166
167 - def validate(self, value, required=True):
168 value = super(StringProperty, self).validate(value,
169 required=required)
170
171 if value is None:
172 return value
173
174 if not isinstance(value, basestring):
175 raise BadValueError(
176 'Property %s must be unicode or str instance, not a %s' % (self.name, type(value).__name__))
177 return value
178
179 data_type = unicode
180
182 """ Integer property. map to int
183
184 *Value type*: int
185 """
186 to_python = int
187
190
191 - def validate(self, value, required=True):
192 value = super(IntegerProperty, self).validate(value,
193 required=required)
194
195 if value is None:
196 return value
197
198 if value is not None and not isinstance(value, (int, long,)):
199 raise BadValueError(
200 'Property %s must be %s or long instance, not a %s'
201 % (self.name, type(self.data_type).__name__,
202 type(value).__name__))
203
204 return value
205
206 data_type = int
207 LongProperty = IntegerProperty
210 """ Float property, map to python float
211
212 *Value type*: float
213 """
214 to_python = float
215 data_type = float
216
217 - def validate(self, value, required=True):
218 value = super(FloatProperty, self).validate(value,
219 required=required)
220
221 if value is None:
222 return value
223
224 if not isinstance(value, float):
225 raise BadValueError(
226 'Property %s must be float instance, not a %s'
227 % (self.name, type(value).__name__))
228
229 return value
230 Number = FloatProperty
233 """ Boolean property, map to python bool
234
235 *ValueType*: bool
236 """
237 to_python = bool
238 data_type = bool
239
240 - def validate(self, value, required=True):
241 value = super(BooleanProperty, self).validate(value,
242 required=required)
243
244 if value is None:
245 return value
246
247 if value is not None and not isinstance(value, bool):
248 raise BadValueError(
249 'Property %s must be bool instance, not a %s'
250 % (self.name, type(value).__name__))
251
252 return value
253
255 """ Decimal property, map to Decimal python object
256
257 *ValueType*: decimal.Decimal
258 """
259 data_type = decimal.Decimal
260
262 return decimal.Decimal(value)
263
266
268 """DateTime property. It convert iso3339 string
269 to python and vice-versa. Map to datetime.datetime
270 object.
271
272 *ValueType*: datetime.datetime
273 """
274
275 - def __init__(self, verbose_name=None, auto_now=False, auto_now_add=False,
276 **kwds):
277 super(DateTimeProperty, self).__init__(verbose_name, **kwds)
278 self.auto_now = auto_now
279 self.auto_now_add = auto_now_add
280
281 - def validate(self, value, required=True):
282 value = super(DateTimeProperty, self).validate(value, required=required)
283
284 if value is None:
285 return value
286
287 if value and not isinstance(value, self.data_type):
288 raise BadValueError('Property %s must be a %s, current is %s' %
289 (self.name, self.data_type.__name__, type(value).__name__))
290 return value
291
296
298 if isinstance(value, basestring):
299 try:
300 value = value.split('.', 1)[0]
301 value = value.rstrip('Z')
302 timestamp = timegm(time.strptime(value, '%Y-%m-%dT%H:%M:%S'))
303 value = datetime.datetime.utcfromtimestamp(timestamp)
304 except ValueError, e:
305 raise ValueError('Invalid ISO date/time %r' % value)
306 return value
307
309 if self.auto_now:
310 value = self.now()
311
312 if value is None:
313 return value
314 return value.replace(microsecond=0).isoformat() + 'Z'
315
316 data_type = datetime.datetime
317
318 @staticmethod
320 return datetime.datetime.utcnow()
321
323 """ Date property, like DateTime property but only
324 for Date. Map to datetime.date object
325
326 *ValueType*: datetime.date
327 """
328 data_type = datetime.date
329
330 @staticmethod
332 return datetime.datetime.now().date()
333
335 if isinstance(value, basestring):
336 try:
337 value = datetime.date(*time.strptime(value, '%Y-%m-%d')[:3])
338 except ValueError, e:
339 raise ValueError('Invalid ISO date %r' % value)
340 return value
341
343 if value is None:
344 return value
345 return value.isoformat()
346
348 """ Date property, like DateTime property but only
349 for time. Map to datetime.time object
350
351 *ValueType*: datetime.time
352 """
353
354 data_type = datetime.time
355
356 @staticmethod
358 return datetime.datetime.now().time()
359
361 if isinstance(value, basestring):
362 try:
363 value = value.split('.', 1)[0]
364 value = datetime.time(*time.strptime(value, '%H:%M:%S')[3:6])
365 except ValueError, e:
366 raise ValueError('Invalid ISO time %r' % value)
367 return value
368
370 if value is None:
371 return value
372 return value.replace(microsecond=0).isoformat()
373
376 """ A property that stores a dict of things"""
377
378 - def __init__(self, verbose_name=None, default=None,
379 required=False, **kwds):
380 """
381 :args verbose_name: Optional verbose name.
382 :args default: Optional default value; if omitted, an empty list is used.
383 :args**kwds: Optional additional keyword arguments, passed to base class.
384
385 Note that the only permissible value for 'required' is True.
386 """
387
388 if default is None:
389 default = {}
390
391 Property.__init__(self, verbose_name, default=default,
392 required=required, **kwds)
393
394 data_type = dict
395
396 - def validate(self, value, required=True):
403
404 - def validate_dict_contents(self, value):
405 try:
406 value = validate_dict_content(value)
407 except BadValueError:
408 raise BadValueError(
409 'Items of %s dict must all be in %s' %
410 (self.name, ALLOWED_PROPERTY_TYPES))
411 return value
412
414 """Default value for list.
415
416 Because the property supplied to 'default' is a static value,
417 that value must be shallow copied to prevent all fields with
418 default values from sharing the same instance.
419
420 Returns:
421 Copy of the default value.
422 """
423 value = super(DictProperty, self).default_value()
424 if value is None:
425 value = {}
426 return dict(value)
427
430
433
437 """A property that stores a list of things.
438
439 """
440 - def __init__(self, verbose_name=None, default=None,
441 required=False, item_type=None, **kwds):
442 """Construct ListProperty.
443
444
445 :args verbose_name: Optional verbose name.
446 :args default: Optional default value; if omitted, an empty list is used.
447 :args**kwds: Optional additional keyword arguments, passed to base class.
448
449
450 """
451 if default is None:
452 default = []
453
454 if item_type is not None and item_type not in ALLOWED_PROPERTY_TYPES:
455 raise ValueError('item_type %s not in %s' % (item_type, ALLOWED_PROPERTY_TYPES))
456 self.item_type = item_type
457
458 Property.__init__(self, verbose_name, default=default,
459 required=required, **kwds)
460
461 data_type = list
462
463 - def validate(self, value, required=True):
470
471 - def validate_list_contents(self, value):
472 value = validate_list_content(value, item_type=self.item_type)
473 try:
474 value = validate_list_content(value, item_type=self.item_type)
475 except BadValueError:
476 raise BadValueError(
477 'Items of %s list must all be in %s' %
478 (self.name, ALLOWED_PROPERTY_TYPES))
479 return value
480
482 """Default value for list.
483
484 Because the property supplied to 'default' is a static value,
485 that value must be shallow copied to prevent all fields with
486 default values from sharing the same instance.
487
488 Returns:
489 Copy of the default value.
490 """
491 value = super(ListProperty, self).default_value()
492 if value is None:
493 value = []
494 return list(value)
495
497 return LazyList(value, item_type=self.item_type)
498
501
504 """ shorthand for list that should containe only unicode"""
505
506 - def __init__(self, verbose_name=None, default=None,
507 required=False, **kwds):
508 super(StringListProperty, self).__init__(verbose_name=verbose_name,
509 default=default, required=required, item_type=basestring,**kwds)
510
514 """ object to make sure we keep updated of dict
515 in _doc. We just override a dict and maintain change in
516 doc reference (doc[keyt] obviously).
517
518 if init_vals is specified, doc is overwritten
519 with the dict given. Otherwise, the values already in
520 doc are used.
521 """
522
523 - def __init__(self, doc, item_type=None, init_vals=None):
524 dict.__init__(self)
525 self.item_type = item_type
526
527 self.doc = doc
528 if init_vals is None:
529 self._wrap()
530 else:
531 for key, value in init_vals.items():
532 self[key] = value
533
535 for key, json_value in self.doc.items():
536 if isinstance(json_value, dict):
537 value = LazyDict(json_value, item_type=self.item_type)
538 elif isinstance(json_value, list):
539 value = LazyList(json_value, item_type=self.item_type)
540 else:
541 value = value_to_python(json_value, self.item_type)
542 dict.__setitem__(self, key, value)
543
545 if isinstance(value, dict):
546 self.doc[key] = {}
547 value = LazyDict(self.doc[key], item_type=self.item_type, init_vals=value)
548 elif isinstance(value, list):
549 self.doc[key] = []
550 value = LazyList(self.doc[key], item_type=self.item_type, init_vals=value)
551 else:
552 self.doc.update({key: value_to_json(value, item_type=self.item_type) })
553 super(LazyDict, self).__setitem__(key, value)
554
558
559 - def pop(self, key, default=None):
560 del self.doc[key]
561 return super(LazyDict, self).pop(key, default=default)
562
569
571 for k, v in value.items():
572 self[k] = v
573
578
582
584 """ object to make sure we keep update of list
585 in _doc. We just override a list and maintain change in
586 doc reference (doc[index] obviously).
587
588 if init_vals is specified, doc is overwritten
589 with the list given. Otherwise, the values already in
590 doc are used.
591 """
592
593 - def __init__(self, doc, item_type=None, init_vals=None):
594 list.__init__(self)
595
596 self.item_type = item_type
597 self.doc = doc
598 if init_vals is None:
599
600 self._wrap()
601 else:
602
603
604 del self.doc[:]
605 for item in init_vals:
606 self.append(item)
607
609 for json_value in self.doc:
610 if isinstance(json_value, dict):
611 value = LazyDict(json_value, item_type=self.item_type)
612 elif isinstance(json_value, list):
613 value = LazyList(json_value, item_type=self.item_type)
614 else:
615 value = value_to_python(json_value, self.item_type)
616 list.append(self, value)
617
621
632
633
637
639 return LazyList(self.doc[i:j], self.item_type)
640
644
646 jvalue = value_to_json(value)
647 for m in self.doc:
648 if m == value: return True
649 return False
650
651 - def append(self, *args, **kwargs):
652 if args:
653 assert len(args) == 1
654 value = args[0]
655 else:
656 value = kwargs
657
658 index = len(self)
659 if isinstance(value, dict):
660 self.doc.append({})
661 value = LazyDict(self.doc[index], item_type=self.item_type, init_vals=value)
662 elif isinstance(value, list):
663 self.doc.append([])
664 value = LazyList(self.doc[index], item_type=self.item_type, init_vals=value)
665 else:
666 self.doc.append(value_to_json(value, item_type=self.item_type))
667 super(LazyList, self).append(value)
668
669 - def index(self, x, *args):
672
674 del self[self.index(x)]
675
676 - def sort(self, cmp=None, key=None, reverse=False):
679
683
684
685
686
687 MAP_TYPES_PROPERTIES = {
688 decimal.Decimal: DecimalProperty,
689 datetime.datetime: DateTimeProperty,
690 datetime.date: DateProperty,
691 datetime.time: TimeProperty,
692 str: StringProperty,
693 unicode: StringProperty,
694 bool: BooleanProperty,
695 int: IntegerProperty,
696 long: LongProperty,
697 float: FloatProperty,
698 list: ListProperty,
699 dict: DictProperty
700 }
708
717
718
719
720 -def validate_list_content(value, item_type=None):
721 """ validate type of values in a list """
722 return [validate_content(item, item_type=item_type) for item in value]
723
724 -def validate_dict_content(value, item_type=None):
725 """ validate type of values in a dict """
726 return dict([(k, validate_content(v,
727 item_type=item_type)) for k, v in value.iteritems()])
728
729 -def validate_content(value, item_type=None):
730 """ validate a value. test if value is in supported types """
731 if isinstance(value, list):
732 value = validate_list_content(value, item_type=item_type)
733 elif isinstance(value, dict):
734 value = validate_dict_content(value, item_type=item_type)
735 elif item_type is not None and not isinstance(value, item_type):
736 raise BadValueError(
737 'Items must all be in %s' % item_type)
738 elif type(value) not in ALLOWED_PROPERTY_TYPES:
739 raise BadValueError(
740 'Items must all be in %s' %
741 (ALLOWED_PROPERTY_TYPES))
742 return value
743
745 """ convert a dict to json """
746 return dict([(k, value_to_json(v, item_type=item_type)) for k, v in value.iteritems()])
747
749 """ convert a list to json """
750 return [value_to_json(item, item_type=item_type) for item in value]
751
753 """ convert a value to json using appropriate regexp.
754 For Dates we use ISO 8601. Decimal are converted to string.
755
756 """
757 if isinstance(value, datetime.datetime) and is_type_ok(item_type, datetime.datetime):
758 value = value.replace(microsecond=0).isoformat() + 'Z'
759 elif isinstance(value, datetime.date) and is_type_ok(item_type, datetime.date):
760 value = value.isoformat()
761 elif isinstance(value, datetime.time) and is_type_ok(item_type, datetime.time):
762 value = value.replace(microsecond=0).isoformat()
763 elif isinstance(value, decimal.Decimal) and is_type_ok(item_type, decimal.Decimal):
764 value = unicode(value)
765 elif isinstance(value, list):
766 value = list_to_json(value, item_type)
767 elif isinstance(value, dict):
768 value = dict_to_json(value, item_type)
769 return value
770
772 return item_type is None or item_type == value_type
773
801
803 """ convert a list of json values to python list """
804 return [value_to_python(item, item_type=item_type) for item in value]
805
807 """ convert a json object values to python dict """
808 return dict([(k, value_to_python(v, item_type=item_type)) for k, v in value.iteritems()])
809