forked from rrenaud/dominionstats
-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathprimitive_util.py
131 lines (107 loc) · 4.9 KB
/
primitive_util.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
#!/usr/bin/python
""" This a mixin to a generically serialize objects to primitive types.
This is serializing the internal variables of classes, and hence is a
big abstraction leak. By mixing with this class, you can give yourself
headaches if you change the implementation of a class and want to work
with previously serialized versions of data.
"""
import collections
PRIMITIVES = [dict, str, int, list, float, unicode]
def to_primitive(val):
if hasattr(val, 'to_primitive_object'):
return val.to_primitive_object()
assert type(val) in PRIMITIVES, (val, type(val))
return val
class PrimitiveConversion(object):
""" An object that supports the PrimitiveConversion operation can be
serialized to and deserialized from a possibly nested collection of native
Python objects. This requires that all members are either primitives
or PrimitiveConversions.
This is useful because they are then easy to turn into mongo BSON objects
and JSON objects.
This default implementation uses self.__dict__ to encode into this object
into a python dict whose keys are the member names."""
def to_primitive_object(self):
ret = {}
for k, v in self.__dict__.iteritems():
ret[k] = to_primitive(v)
return ret
def from_primitive_object(self, obj):
# Get rid of _id because it's something that mongo injects into our
# objects, and it's not really natural to the objects themselves.
obj_keys_except_id = set(obj.keys()) - set(['_id'])
unicoded_keys = set(map(unicode, self.__dict__.keys()))
assert unicoded_keys == obj_keys_except_id, (
'%s != %s' % (str(unicoded_keys), str(obj_keys_except_id)))
for k in obj_keys_except_id:
if hasattr(self.__dict__[k], 'from_primitive_object'):
self.__dict__[k].from_primitive_object(obj[k])
else:
assert type(obj[k]) in PRIMITIVES, obj[k]
self.__dict__[k] = obj[k]
def _slot_members(inst):
for member_name in inst.__slots__:
yield getattr(inst, member_name)
def slot_index_count(obj):
if isinstance(obj, ListSlotPrimitiveConversion):
return sum(slot_index_count(member) for member in _slot_members(obj))
else:
return 1
class ListSlotPrimitiveConversion(PrimitiveConversion):
""" A more restrictive, but more compact when serialized version of
PrimitiveConversion. This serializes to/from flat lists. This
requires that every member is a Primitive or ListSlotPrimitiveConversion.
Further, every member must define __slots__. The list indicies
depend on the __slots__ ordering, and hence this has trouble
(de)serializing versions that contain different slot members, or even
slot members in different orders.
"""
def to_primitive_object(self):
ret = [None] * slot_index_count(self)
self.serialize_to_list(ret, 0)
return ret
def from_primitive_object(self, obj):
assert type(obj) == list, '%s is not a list' % str(obj)
assert len(obj) == slot_index_count(self), '%d != %d' % (
len(obj), slot_index_count(self))
self.deserialize_from_list(obj, 0)
def serialize_to_list(self, l, start):
for member in _slot_members(self):
if isinstance(member, ListSlotPrimitiveConversion):
member.serialize_to_list(l, start)
else:
assert type(member) in PRIMITIVES
l[start] = member
start += slot_index_count(member)
def deserialize_from_list(self, l, start):
for member_name in self.__slots__:
member_val = getattr(self, member_name)
if isinstance(member_val, ListSlotPrimitiveConversion):
member_val.deserialize_from_list(l, start)
else:
assert type(member_val) in PRIMITIVES
setattr(self, member_name, l[start])
start += slot_index_count(member_val)
class ConvertibleDefaultDict(collections.defaultdict, PrimitiveConversion):
def __init__(self, value_type, key_type = str):
collections.defaultdict.__init__(self, value_type)
self.value_type = value_type
self.key_type = key_type
def to_primitive_object(self):
ret = {}
for key, val in self.iteritems():
if type(key) == unicode:
key = key.encode('utf-8')
else:
key = str(key)
ret[key] = to_primitive(val)
return ret
def from_primitive_object(self, obj):
for k, v in obj.iteritems():
if k == '_id': continue
val = self.value_type()
if hasattr(val, 'from_primitive_object'):
val.from_primitive_object(v)
else:
val = v
self[self.key_type(k)] = val