Package couchdbkit :: Package schema :: Module properties
[hide private]
[frames] | no frames]

Source Code for Module couchdbkit.schema.properties

  1  # -*- coding: utf-8 - 
  2  # 
  3  # This file is part of couchdbkit released under the MIT license.  
  4  # See the NOTICE for more information. 
  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+)$') 
46 47 -class Property(object):
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
74 - def __property_config__(self, document_class, property_name):
75 self.document_class = document_class 76 if self.name is None: 77 self.name = property_name
78
79 - def __property_init__(self, document_instance, value):
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
100 - def __delete__(self, document_instance):
101 pass
102
103 - def default_value(self):
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
135 - def empty(self, value):
136 """ test if value is empty """ 137 return not value or value is None
138
139 - def _to_python(self, value):
140 if value == None: 141 return value 142 return self.to_python(value)
143
144 - def _to_json(self, value):
145 if value == None: 146 return value 147 return self.to_json(value)
148
149 - def to_python(self, value):
150 """ convert to python type """ 151 return unicode(value)
152
153 - def to_json(self, value):
154 """ convert to json, Converted value is saved in couchdb. """ 155 return self.to_python(value)
156 157 data_type = None
158
159 -class StringProperty(Property):
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
181 -class IntegerProperty(Property):
182 """ Integer property. map to int 183 184 *Value type*: int 185 """ 186 to_python = int 187
188 - def empty(self, value):
189 return value is None
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
208 209 -class FloatProperty(Property):
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
231 232 -class BooleanProperty(Property):
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
254 -class DecimalProperty(Property):
255 """ Decimal property, map to Decimal python object 256 257 *ValueType*: decimal.Decimal 258 """ 259 data_type = decimal.Decimal 260
261 - def to_python(self, value):
262 return decimal.Decimal(value)
263
264 - def to_json(self, value):
265 return unicode(value)
266
267 -class DateTimeProperty(Property):
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
292 - def default_value(self):
293 if self.auto_now or self.auto_now_add: 294 return self.now() 295 return Property.default_value(self)
296
297 - def to_python(self, value):
298 if isinstance(value, basestring): 299 try: 300 value = value.split('.', 1)[0] # strip out microseconds 301 value = value.rstrip('Z') # remove timezone separator 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
308 - def to_json(self, value):
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
319 - def now():
320 return datetime.datetime.utcnow()
321
322 -class DateProperty(DateTimeProperty):
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
331 - def now():
332 return datetime.datetime.now().date()
333
334 - def to_python(self, value):
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
342 - def to_json(self, value):
343 if value is None: 344 return value 345 return value.isoformat()
346
347 -class TimeProperty(DateTimeProperty):
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
357 - def now(self):
358 return datetime.datetime.now().time()
359
360 - def to_python(self, value):
361 if isinstance(value, basestring): 362 try: 363 value = value.split('.', 1)[0] # strip out microseconds 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
369 - def to_json(self, value):
370 if value is None: 371 return value 372 return value.replace(microsecond=0).isoformat()
373
374 375 -class DictProperty(Property):
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):
397 value = super(DictProperty, self).validate(value, required=required) 398 if value and value is not None: 399 if not isinstance(value, dict): 400 raise BadValueError('Property %s must be a dict' % self.name) 401 value = self.validate_dict_contents(value) 402 return value
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
413 - def default_value(self):
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
428 - def to_python(self, value):
429 return LazyDict(value)
430
431 - def to_json(self, value):
432 return value_to_json(value)
433
434 435 436 -class ListProperty(Property):
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):
464 value = super(ListProperty, self).validate(value, required=required) 465 if value and value is not None: 466 if not isinstance(value, list): 467 raise BadValueError('Property %s must be a list' % self.name) 468 value = self.validate_list_contents(value) 469 return value
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
481 - def default_value(self):
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
496 - def to_python(self, value):
497 return LazyList(value, item_type=self.item_type)
498
499 - def to_json(self, value):
500 return value_to_json(value, item_type=self.item_type)
501
502 503 -class StringListProperty(ListProperty):
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
511 # structures proxy 512 513 -class LazyDict(dict):
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
534 - def _wrap(self):
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
544 - def __setitem__(self, key, value):
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
555 - def __delitem__(self, key):
556 del self.doc[key] 557 super(LazyDict, self).__delitem__(key)
558
559 - def pop(self, key, default=None):
560 del self.doc[key] 561 return super(LazyDict, self).pop(key, default=default)
562
563 - def setdefault(self, key, default):
564 if key in self: 565 return self[key] 566 self.doc.setdefault(key, value_to_json(default, item_type=self.item_type)) 567 super(LazyDict, self).setdefault(key, default) 568 return default
569
570 - def update(self, value):
571 for k, v in value.items(): 572 self[k] = v
573
574 - def popitem(self, value):
575 new_value = super(LazyDict, self).popitem(value) 576 self.doc.popitem(value_to_json(value, item_type=self.item_type)) 577 return new_value
578
579 - def clear(self):
580 self.doc.clear() 581 super(LazyDict, self).clear()
582
583 -class LazyList(list):
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 # just wrap the current values 600 self._wrap() 601 else: 602 # initialize this list and the underlying list 603 # with the values given. 604 del self.doc[:] 605 for item in init_vals: 606 self.append(item)
607
608 - def _wrap(self):
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
618 - def __delitem__(self, index):
619 del self.doc[index] 620 list.__delitem__(self, index)
621
622 - def __setitem__(self, index, value):
623 if isinstance(value, dict): 624 self.doc[index] = {} 625 value = LazyDict(self.doc[index], item_type=self.item_type, init_vals=value) 626 elif isinstance(value, list): 627 self.doc[index] = [] 628 value = LazyList(self.doc[index], item_type=self.item_type, init_vals=value) 629 else: 630 self.doc[index] = value_to_json(value, item_type=self.item_type) 631 list.__setitem__(self, index, value)
632 633
634 - def __delslice__(self, i, j):
635 del self.doc[i:j] 636 list.__delslice__(self, i, j)
637
638 - def __getslice__(self, i, j):
639 return LazyList(self.doc[i:j], self.item_type)
640
641 - def __setslice__(self, i, j, seq):
642 self.doc[i:j] = (value_to_json(v, item_type=self.item_type) for v in seq) 643 list.__setslice__(self, i, j, seq)
644
645 - def __contains__(self, value):
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):
670 x = value_to_json(x, item_type=self.item_type) 671 return self.doc.index(x)
672
673 - def remove(self, x):
674 del self[self.index(x)]
675
676 - def sort(self, cmp=None, key=None, reverse=False):
677 self.doc.sort(cmp, key, reverse) 678 list.sort(self, cmp, key, reverse)
679
680 - def reverse(self):
681 self.doc.reverse() 682 list.reverse(self)
683 684 685 # some mapping 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 }
701 702 -def convert_property(value):
703 """ convert a value to json from Property._to_json """ 704 if type(value) in MAP_TYPES_PROPERTIES: 705 prop = MAP_TYPES_PROPERTIES[type(value)]() 706 value = prop.to_json(value) 707 return value
708
709 710 -def value_to_property(value):
711 """ Convert value in a Property object """ 712 if type(value) in MAP_TYPES_PROPERTIES: 713 prop = MAP_TYPES_PROPERTIES[type(value)]() 714 return prop 715 else: 716 return value
717
718 # utilities functions 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
744 -def dict_to_json(value, item_type=None):
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
748 -def list_to_json(value, item_type=None):
749 """ convert a list to json """ 750 return [value_to_json(item, item_type=item_type) for item in value]
751
752 -def value_to_json(value, item_type=None):
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
771 -def is_type_ok(item_type, value_type):
772 return item_type is None or item_type == value_type
773
774 775 -def value_to_python(value, item_type=None):
776 """ convert a json value to python type using regexp. values converted 777 have been put in json via `value_to_json` . 778 """ 779 data_type = None 780 if isinstance(value, basestring): 781 if re_date.match(value) and is_type_ok(item_type, datetime.datetime): 782 data_type = datetime.date 783 elif re_time.match(value) and is_type_ok(item_type, datetime.date): 784 data_type = datetime.time 785 elif re_datetime.match(value) and is_type_ok(item_type, datetime.time): 786 data_type = datetime.datetime 787 elif re_decimal.match(value) and is_type_ok(item_type, decimal.Decimal): 788 data_type = decimal.Decimal 789 if data_type is not None: 790 prop = MAP_TYPES_PROPERTIES[data_type]() 791 try: 792 #sometimes regex fail so return value 793 value = prop.to_python(value) 794 except: 795 pass 796 elif isinstance(value, list): 797 value = list_to_python(value, item_type=item_type) 798 elif isinstance(value, dict): 799 value = dict_to_python(value, item_type=item_type) 800 return value
801
802 -def list_to_python(value, item_type=None):
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
806 -def dict_to_python(value, item_type=None):
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