1
2
3
4
5
6 """ properties used by Document object """
7
8 import decimal
9 import datetime
10 import re
11 import time
12
13 try:
14 from collections import MutableSet, Iterable
17 return isinstance(c, Iterable)
18
19 support_setproperty = True
20 except ImportError:
21 support_setproperty = False
22
23 from couchdbkit.exceptions import BadValueError
24
25 __all__ = ['ALLOWED_PROPERTY_TYPES', 'Property', 'StringProperty',
26 'IntegerProperty', 'DecimalProperty', 'BooleanProperty',
27 'FloatProperty', 'DateTimeProperty', 'DateProperty',
28 'TimeProperty', 'DictProperty', 'ListProperty',
29 'StringListProperty',
30 'dict_to_json', 'list_to_json',
31 'value_to_json', 'MAP_TYPES_PROPERTIES', 'value_to_python',
32 'dict_to_python', 'list_to_python', 'convert_property',
33 'value_to_property', 'LazyDict', 'LazyList']
34
35 if support_setproperty:
36 __all__ += ['SetProperty', 'LasySet']
37
38 ALLOWED_PROPERTY_TYPES = set([
39 basestring,
40 str,
41 unicode,
42 bool,
43 int,
44 long,
45 float,
46 datetime.datetime,
47 datetime.date,
48 datetime.time,
49 decimal.Decimal,
50 dict,
51 list,
52 set,
53 type(None)
54 ])
55
56 re_date = re.compile('^(\d{4})\D?(0[1-9]|1[0-2])\D?([12]\d|0[1-9]|3[01])$')
57 re_time = re.compile('^([01]\d|2[0-3])\D?([0-5]\d)\D?([0-5]\d)?\D?(\d{3})?$')
58 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)?)?)?$')
59 re_decimal = re.compile('^(\d+)\.(\d+)$')
62 """ Property base which all other properties
63 inherit."""
64 creation_counter = 0
65
66 - def __init__(self, verbose_name=None, name=None,
67 default=None, required=False, validators=None,
68 choices=None):
69 """ Default constructor for a property.
70
71 :param verbose_name: str, verbose name of field, could
72 be use for description
73 :param name: str, name of field
74 :param default: default value
75 :param required: True if field is required, default is False
76 :param validators: list of callable or callable, field validators
77 function that are executed when document is saved.
78 """
79 self.verbose_name = verbose_name
80 self.name = name
81 self.default = default
82 self.required = required
83 self.validators = validators
84 self.choices = choices
85 self.creation_counter = Property.creation_counter
86 Property.creation_counter += 1
87
89 self.document_class = document_class
90 if self.name is None:
91 self.name = property_name
92
94 """ method used to set value of the property when
95 we create the document. Don't check required. """
96 if value is not None:
97 value = self.to_json(self.validate(value, required=False))
98 document_instance._doc[self.name] = value
99
100 - def __get__(self, document_instance, document_class):
101 if document_instance is None:
102 return self
103
104 value = document_instance._doc.get(self.name)
105 if value is not None:
106 value = self._to_python(value)
107
108 return value
109
110 - def __set__(self, document_instance, value):
111 value = self.validate(value, required=False)
112 document_instance._doc[self.name] = self._to_json(value)
113
116
118 """ return default value """
119
120 default = self.default
121 if callable(default):
122 default = default()
123 return default
124
125 - def validate(self, value, required=True):
126 """ validate value """
127 if required and self.empty(value):
128 if self.required:
129 raise BadValueError("Property %s is required." % self.name)
130 else:
131 if self.choices and value is not None:
132 if isinstance(self.choices, list): choice_list = self.choices
133 if isinstance(self.choices, dict): choice_list = self.choices.keys()
134 if isinstance(self.choices, tuple): choice_list = [key for (key, name) in self.choices]
135
136 if value not in choice_list:
137 raise BadValueError('Property %s is %r; must be one of %r' % (
138 self.name, value, choice_list))
139 if self.validators:
140 if isinstance(self.validators, (list, tuple,)):
141 for validator in self.validators:
142 if callable(validator):
143 validator(value)
144 elif callable(self.validators):
145 self.validators(value)
146 return value
147
149 """ test if value is empty """
150 return not value or value is None
151
153 if value == None:
154 return value
155 return self.to_python(value)
156
158 if value == None:
159 return value
160 return self.to_json(value)
161
163 """ convert to python type """
164 return unicode(value)
165
167 """ convert to json, Converted value is saved in couchdb. """
168 return self.to_python(value)
169
170 data_type = None
171
173 """ string property str or unicode property
174
175 *Value type*: unicode
176 """
177
178 to_python = unicode
179
180 - def validate(self, value, required=True):
181 value = super(StringProperty, self).validate(value,
182 required=required)
183
184 if value is None:
185 return value
186
187 if not isinstance(value, basestring):
188 raise BadValueError(
189 'Property %s must be unicode or str instance, not a %s' % (self.name, type(value).__name__))
190 return value
191
192 data_type = unicode
193
195 """ Integer property. map to int
196
197 *Value type*: int
198 """
199 to_python = int
200
203
204 - def validate(self, value, required=True):
205 value = super(IntegerProperty, self).validate(value,
206 required=required)
207
208 if value is None:
209 return value
210
211 if value is not None and not isinstance(value, (int, long,)):
212 raise BadValueError(
213 'Property %s must be %s or long instance, not a %s'
214 % (self.name, type(self.data_type).__name__,
215 type(value).__name__))
216
217 return value
218
219 data_type = int
220 LongProperty = IntegerProperty
223 """ Float property, map to python float
224
225 *Value type*: float
226 """
227 to_python = float
228 data_type = float
229
230 - def validate(self, value, required=True):
231 value = super(FloatProperty, self).validate(value,
232 required=required)
233
234 if value is None:
235 return value
236
237 if not isinstance(value, float):
238 raise BadValueError(
239 'Property %s must be float instance, not a %s'
240 % (self.name, type(value).__name__))
241
242 return value
243 Number = FloatProperty
246 """ Boolean property, map to python bool
247
248 *ValueType*: bool
249 """
250 to_python = bool
251 data_type = bool
252
253 - def validate(self, value, required=True):
254 value = super(BooleanProperty, self).validate(value,
255 required=required)
256
257 if value is None:
258 return value
259
260 if value is not None and not isinstance(value, bool):
261 raise BadValueError(
262 'Property %s must be bool instance, not a %s'
263 % (self.name, type(value).__name__))
264
265 return value
266
268 """test if boolean is empty"""
269 return value is None
270
272 """ Decimal property, map to Decimal python object
273
274 *ValueType*: decimal.Decimal
275 """
276 data_type = decimal.Decimal
277
280
283
285 """DateTime property. It convert iso3339 string
286 to python and vice-versa. Map to datetime.datetime
287 object.
288
289 *ValueType*: datetime.datetime
290 """
291
292 - def __init__(self, verbose_name=None, auto_now=False, auto_now_add=False,
293 **kwds):
294 super(DateTimeProperty, self).__init__(verbose_name, **kwds)
295 self.auto_now = auto_now
296 self.auto_now_add = auto_now_add
297
298 - def validate(self, value, required=True):
299 value = super(DateTimeProperty, self).validate(value, required=required)
300
301 if value is None:
302 return value
303
304 if value and not isinstance(value, self.data_type):
305 raise BadValueError('Property %s must be a %s, current is %s' %
306 (self.name, self.data_type.__name__, type(value).__name__))
307 return value
308
313
315 if isinstance(value, basestring):
316 try:
317 value = value.split('.', 1)[0]
318 value = value[0:19]
319 value = datetime.datetime.strptime(value, '%Y-%m-%dT%H:%M:%S')
320 except ValueError, e:
321 raise ValueError('Invalid ISO date/time %r [%s]' %
322 (value, str(e)))
323 return value
324
326 if self.auto_now:
327 value = self.now()
328
329 if value is None:
330 return value
331 return value.replace(microsecond=0).isoformat() + 'Z'
332
333 data_type = datetime.datetime
334
335 @staticmethod
338
340 """ Date property, like DateTime property but only
341 for Date. Map to datetime.date object
342
343 *ValueType*: datetime.date
344 """
345 data_type = datetime.date
346
347 @staticmethod
350
352 if isinstance(value, basestring):
353 try:
354 value = datetime.date(*time.strptime(value, '%Y-%m-%d')[:3])
355 except ValueError, e:
356 raise ValueError('Invalid ISO date %r [%s]' % (value,
357 str(e)))
358 return value
359
361 if value is None:
362 return value
363 return value.isoformat()
364
366 """ Date property, like DateTime property but only
367 for time. Map to datetime.time object
368
369 *ValueType*: datetime.time
370 """
371
372 data_type = datetime.time
373
374 @staticmethod
377
379 if isinstance(value, basestring):
380 try:
381 value = value.split('.', 1)[0]
382 value = datetime.time(*time.strptime(value, '%H:%M:%S')[3:6])
383 except ValueError, e:
384 raise ValueError('Invalid ISO time %r [%s]' % (value,
385 str(e)))
386 return value
387
389 if value is None:
390 return value
391 return value.replace(microsecond=0).isoformat()
392
395 """ A property that stores a dict of things"""
396
397 - def __init__(self, verbose_name=None, default=None,
398 required=False, **kwds):
399 """
400 :args verbose_name: Optional verbose name.
401 :args default: Optional default value; if omitted, an empty list is used.
402 :args**kwds: Optional additional keyword arguments, passed to base class.
403
404 Note that the only permissible value for 'required' is True.
405 """
406
407 if default is None:
408 default = {}
409
410 Property.__init__(self, verbose_name, default=default,
411 required=required, **kwds)
412
413 data_type = dict
414
415 - def validate(self, value, required=True):
422
423 - def validate_dict_contents(self, value):
424 try:
425 value = validate_dict_content(value)
426 except BadValueError:
427 raise BadValueError(
428 'Items of %s dict must all be in %s' %
429 (self.name, ALLOWED_PROPERTY_TYPES))
430 return value
431
433 """Default value for list.
434
435 Because the property supplied to 'default' is a static value,
436 that value must be shallow copied to prevent all fields with
437 default values from sharing the same instance.
438
439 Returns:
440 Copy of the default value.
441 """
442 value = super(DictProperty, self).default_value()
443 if value is None:
444 value = {}
445 return dict(value)
446
449
452
456 """A property that stores a list of things.
457
458 """
459 - def __init__(self, verbose_name=None, default=None,
460 required=False, item_type=None, **kwds):
461 """Construct ListProperty.
462
463
464 :args verbose_name: Optional verbose name.
465 :args default: Optional default value; if omitted, an empty list is used.
466 :args**kwds: Optional additional keyword arguments, passed to base class.
467
468
469 """
470 if default is None:
471 default = []
472
473 if item_type is not None and item_type not in ALLOWED_PROPERTY_TYPES:
474 raise ValueError('item_type %s not in %s' % (item_type, ALLOWED_PROPERTY_TYPES))
475 self.item_type = item_type
476
477 Property.__init__(self, verbose_name, default=default,
478 required=required, **kwds)
479
480 data_type = list
481
482 - def validate(self, value, required=True):
489
490 - def validate_list_contents(self, value):
491 value = validate_list_content(value, item_type=self.item_type)
492 try:
493 value = validate_list_content(value, item_type=self.item_type)
494 except BadValueError:
495 raise BadValueError(
496 'Items of %s list must all be in %s' %
497 (self.name, ALLOWED_PROPERTY_TYPES))
498 return value
499
501 """Default value for list.
502
503 Because the property supplied to 'default' is a static value,
504 that value must be shallow copied to prevent all fields with
505 default values from sharing the same instance.
506
507 Returns:
508 Copy of the default value.
509 """
510 value = super(ListProperty, self).default_value()
511 if value is None:
512 value = []
513 return list(value)
514
516 return LazyList(value, item_type=self.item_type)
517
520
523 """ shorthand for list that should containe only unicode"""
524
525 - def __init__(self, verbose_name=None, default=None,
526 required=False, **kwds):
527 super(StringListProperty, self).__init__(verbose_name=verbose_name,
528 default=default, required=required, item_type=basestring, **kwds)
529
530
531
532
533
534
535
536 -class LazyDict(dict):
537 """ object to make sure we keep updated of dict
538 in _doc. We just override a dict and maintain change in
539 doc reference (doc[keyt] obviously).
540
541 if init_vals is specified, doc is overwritten
542 with the dict given. Otherwise, the values already in
543 doc are used.
544 """
545
546 - def __init__(self, doc, item_type=None, init_vals=None):
547 dict.__init__(self)
548 self.item_type = item_type
549
550 self.doc = doc
551 if init_vals is None:
552 self._wrap()
553 else:
554 for key, value in init_vals.items():
555 self[key] = value
556
558 for key, json_value in self.doc.items():
559 if isinstance(json_value, dict):
560 value = LazyDict(json_value, item_type=self.item_type)
561 elif isinstance(json_value, list):
562 value = LazyList(json_value, item_type=self.item_type)
563 else:
564 value = value_to_python(json_value, self.item_type)
565 dict.__setitem__(self, key, value)
566
568 if isinstance(value, dict):
569 self.doc[key] = {}
570 value = LazyDict(self.doc[key], item_type=self.item_type, init_vals=value)
571 elif isinstance(value, list):
572 self.doc[key] = []
573 value = LazyList(self.doc[key], item_type=self.item_type, init_vals=value)
574 else:
575 self.doc.update({key: value_to_json(value, item_type=self.item_type) })
576 super(LazyDict, self).__setitem__(key, value)
577
581
582 - def pop(self, key, *args):
583 default = len(args) == 1
584 if default:
585 self.doc.pop(key, args[-1])
586 return super(LazyDict, self).pop(key, args[-1])
587 self.doc.pop(key)
588 return super(LazyDict, self).pop(key)
589
596
598 for k, v in value.items():
599 self[k] = v
600
605
609
612 """ object to make sure we keep update of list
613 in _doc. We just override a list and maintain change in
614 doc reference (doc[index] obviously).
615
616 if init_vals is specified, doc is overwritten
617 with the list given. Otherwise, the values already in
618 doc are used.
619 """
620
621 - def __init__(self, doc, item_type=None, init_vals=None):
622 list.__init__(self)
623
624 self.item_type = item_type
625 self.doc = doc
626 if init_vals is None:
627
628 self._wrap()
629 else:
630
631
632 del self.doc[:]
633 for item in init_vals:
634 self.append(item)
635
637 for json_value in self.doc:
638 if isinstance(json_value, dict):
639 value = LazyDict(json_value, item_type=self.item_type)
640 elif isinstance(json_value, list):
641 value = LazyList(json_value, item_type=self.item_type)
642 else:
643 value = value_to_python(json_value, self.item_type)
644 list.append(self, value)
645
649
660
661
665
668
672
674 jvalue = value_to_json(value)
675 for m in self.doc:
676 if m == jvalue: return True
677 return False
678
679 - def append(self, *args, **kwargs):
680 if args:
681 assert len(args) == 1
682 value = args[0]
683 else:
684 value = kwargs
685
686 index = len(self)
687 if isinstance(value, dict):
688 self.doc.append({})
689 value = LazyDict(self.doc[index], item_type=self.item_type, init_vals=value)
690 elif isinstance(value, list):
691 self.doc.append([])
692 value = LazyList(self.doc[index], item_type=self.item_type, init_vals=value)
693 else:
694 self.doc.append(value_to_json(value, item_type=self.item_type))
695 super(LazyList, self).append(value)
696
701
702 - def index(self, x, *args):
705
708
709 - def pop(self, i=-1):
713
715 del self[self.index(x)]
716
717 - def sort(self, cmp=None, key=None, reverse=False):
720
724
725 if support_setproperty:
727 """A property that stores a Python set as a list of unique
728 elements.
729
730 Note that Python set operations like union that return a set
731 object do not alter list that will be stored with the next save,
732 while operations like update that change a set object in-place do
733 keep the list in sync.
734 """
735 - def __init__(self, verbose_name=None, default=None, required=None,
736 item_type=None, **kwds):
737 """Construct SetProperty.
738
739 :args verbose_name: Optional verbose name.
740
741 :args default: Optional default value; if omitted, an empty
742 set is used.
743
744 :args required: True if field is required, default is False.
745
746 :args item_type: Optional data type of items that set
747 contains. Used to assist with JSON
748 serialization/deserialization when data is
749 stored/retireved.
750
751 :args **kwds: Optional additional keyword arguments, passed to
752 base class.
753 """
754 if default is None:
755 default = set()
756 if item_type is not None and item_type not in ALLOWED_PROPERTY_TYPES:
757 raise ValueError('item_type %s not in %s'
758 % (item_type, ALLOWED_PROPERTY_TYPES))
759 self.item_type = item_type
760 super(SetProperty, self).__init__(
761 verbose_name=verbose_name, default=default, required=required,
762 **kwds)
763
764 data_type = set
765
766 - def validate(self, value, required=True):
767 value = super(SetProperty, self).validate(value, required=required)
768 if value and value is not None:
769 if not isinstance(value, MutableSet):
770 raise BadValueError('Property %s must be a set' % self.name)
771 value = self.validate_set_contents(value)
772 return value
773
774 - def validate_set_contents(self, value):
775 try:
776 value = validate_set_content(value, item_type=self.item_type)
777 except BadValueError:
778 raise BadValueError(
779 'Items of %s set must all be in %s' %
780 (self.name, ALLOWED_PROPERTY_TYPES))
781 return value
782
784 """Return default value for set.
785
786 Because the property supplied to 'default' is a static value,
787 that value must be shallow copied to prevent all fields with
788 default values from sharing the same instance.
789
790 Returns:
791 Copy of the default value.
792 """
793 value = super(SetProperty, self).default_value()
794 if value is None:
795 return set()
796 return value.copy()
797
799 return LazySet(value, item_type=self.item_type)
800
803
806 """Object to make sure that we keep set and _doc synchronized.
807
808 We sub-class MutableSet and maintain changes in doc.
809
810 Note that methods like union that return a set object do not
811 alter _doc, while methods like update that change a set object
812 in-place do keep _doc in sync.
813 """
815 fn = getattr(MutableSet, opname)
816 if hasattr(fn, 'im_func'):
817 fn = fn.im_func
818 def method(self, other, fn=fn):
819 if not isinstance(other, MutableSet):
820 other = self._from_iterable(other)
821 return fn(self, other)
822 return method
823
824 issubset = _map_named_operation('__le__')
825 issuperset = _map_named_operation('__ge__')
826 symmetric_difference = _map_named_operation('__xor__')
827
828 - def __init__(self, doc, item_type=None):
829 self.item_type = item_type
830 self.doc = doc
831 self.elements = set(value_to_python(value, self.item_type)
832 for value in self.doc)
833
835 return '%s(%r)' % (type(self).__name__, list(self))
836
837 @classmethod
840
842 for value in (self.elements - iterator):
843 self.elements.discard(value)
844 return self
845
847 return iter(element for element in self.elements)
848
850 return len(self.elements)
851
853 return item in self.elements
854
856 if not isinstance(other, MutableSet):
857 if not is_iterable(other):
858 return NotImplemented
859 other = self._from_iterable(other)
860 return (self.elements - other) | (other - self.elements)
861
863 if not isinstance(other, MutableSet):
864 return NotImplemented
865 return other < self.elements
866
868 if not isinstance(other, MutableSet):
869 return NotImplemented
870 return other <= self.elements
871
873 return not (self.elements == other)
874
875 - def add(self, value):
879
881 return self.elements.copy()
882
885
891
893 self.elements.discard(value)
894 try:
895 self.doc.remove(value)
896 except ValueError:
897 pass
898
901
903 if not isinstance(other, MutableSet):
904 other = set(other)
905 for value in self.elements - other:
906 self.discard(value)
907 for arg in args:
908 self.intersection_update(arg)
909
911 if not isinstance(other, MutableSet):
912 other = set(other)
913 for value in other:
914 if value in self.elements:
915 self.discard(value)
916 else:
917 self.add(value)
918
919 - def union(self, other, *args):
920 return self.elements.union(other, *args)
921
922 - def update(self, other, *args):
923 self.elements.update(other, *args)
924 for element in self.elements:
925 if element not in self.doc:
926 self.doc.append(
927 value_to_json(element, item_type=self.item_type))
928
929
930
931 MAP_TYPES_PROPERTIES = {
932 decimal.Decimal: DecimalProperty,
933 datetime.datetime: DateTimeProperty,
934 datetime.date: DateProperty,
935 datetime.time: TimeProperty,
936 str: StringProperty,
937 unicode: StringProperty,
938 bool: BooleanProperty,
939 int: IntegerProperty,
940 long: LongProperty,
941 float: FloatProperty,
942 list: ListProperty,
943 dict: DictProperty
944 }
945
946 if support_setproperty:
947 MAP_TYPES_PROPERTIES[set] = SetProperty
955
964
965
966
967 -def validate_list_content(value, item_type=None):
968 """ validate type of values in a list """
969 return [validate_content(item, item_type=item_type) for item in value]
970
971 -def validate_dict_content(value, item_type=None):
972 """ validate type of values in a dict """
973 return dict([(k, validate_content(v,
974 item_type=item_type)) for k, v in value.iteritems()])
975
976 -def validate_set_content(value, item_type=None):
977 """ validate type of values in a set """
978 return set(validate_content(item, item_type=item_type) for item in value)
979
980 -def validate_content(value, item_type=None):
981 """ validate a value. test if value is in supported types """
982 if isinstance(value, list):
983 value = validate_list_content(value, item_type=item_type)
984 elif isinstance(value, dict):
985 value = validate_dict_content(value, item_type=item_type)
986 elif item_type is not None and not isinstance(value, item_type):
987 raise BadValueError(
988 'Items must all be in %s' % item_type)
989 elif type(value) not in ALLOWED_PROPERTY_TYPES:
990 raise BadValueError(
991 'Items must all be in %s' %
992 (ALLOWED_PROPERTY_TYPES))
993 return value
994
996 """ convert a dict to json """
997 return dict([(k, value_to_json(v, item_type=item_type)) for k, v in value.iteritems()])
998
1000 """ convert a list to json """
1001 return [value_to_json(item, item_type=item_type) for item in value]
1002
1004 """ convert a value to json using appropriate regexp.
1005 For Dates we use ISO 8601. Decimal are converted to string.
1006
1007 """
1008 if isinstance(value, datetime.datetime) and is_type_ok(item_type, datetime.datetime):
1009 value = value.replace(microsecond=0).isoformat() + 'Z'
1010 elif isinstance(value, datetime.date) and is_type_ok(item_type, datetime.date):
1011 value = value.isoformat()
1012 elif isinstance(value, datetime.time) and is_type_ok(item_type, datetime.time):
1013 value = value.replace(microsecond=0).isoformat()
1014 elif isinstance(value, decimal.Decimal) and is_type_ok(item_type, decimal.Decimal):
1015 value = unicode(value)
1016 elif isinstance(value, (list, MutableSet)):
1017 value = list_to_json(value, item_type)
1018 elif isinstance(value, dict):
1019 value = dict_to_json(value, item_type)
1020 return value
1021
1023 return item_type is None or item_type == value_type
1024
1027 """ convert a json value to python type using regexp. values converted
1028 have been put in json via `value_to_json` .
1029 """
1030 data_type = None
1031 if isinstance(value, basestring):
1032 if re_date.match(value) and is_type_ok(item_type, datetime.date):
1033 data_type = datetime.date
1034 elif re_time.match(value) and is_type_ok(item_type, datetime.time):
1035 data_type = datetime.time
1036 elif re_datetime.match(value) and is_type_ok(item_type, datetime.datetime):
1037 data_type = datetime.datetime
1038 elif re_decimal.match(value) and is_type_ok(item_type, decimal.Decimal):
1039 data_type = decimal.Decimal
1040 if data_type is not None:
1041 prop = MAP_TYPES_PROPERTIES[data_type]()
1042 try:
1043
1044 value = prop.to_python(value)
1045 except:
1046 pass
1047 elif isinstance(value, (list, MutableSet)):
1048 value = list_to_python(value, item_type=item_type)
1049 elif isinstance(value, dict):
1050 value = dict_to_python(value, item_type=item_type)
1051 return value
1052
1054 """ convert a list of json values to python list """
1055 return [value_to_python(item, item_type=item_type) for item in value]
1056
1058 """ convert a json object values to python dict """
1059 return dict([(k, value_to_python(v, item_type=item_type)) for k, v in value.iteritems()])
1060