1"""*Brine* is a simple, fast and secure object serializer for **immutable** objects.
2
3The following types are supported: ``int``, ``bool``, ``str``, ``float``,
4``unicode``, ``bytes``, ``slice``, ``complex``, ``tuple`` (of simple types),
5``frozenset`` (of simple types) as well as the following singletons: ``None``,
6``NotImplemented``, and ``Ellipsis``.
7
8Example::
9 >>> x = ("he", 7, u"llo", 8, (), 900, None, True, Ellipsis, 18.2, 18.2j + 13,
10 ... slice(1,2,3), frozenset([5,6,7]), NotImplemented)
11 >>> dumpable(x)
12 True
13 >>> y = dump(x)
14 >>> y.encode("hex")
15 '140e0b686557080c6c6c6f580216033930300003061840323333333333331b402a000000000000403233333333333319125152531a1255565705'
16 >>> z = load(y)
17 >>> x == z
18 True
19"""
20from rpyc.lib.compat import Struct, BytesIO, BYTES_LITERAL
21
22
23# singletons
24TAG_NONE = b"\x00"
25TAG_EMPTY_STR = b"\x01"
26TAG_EMPTY_TUPLE = b"\x02"
27TAG_TRUE = b"\x03"
28TAG_FALSE = b"\x04"
29TAG_NOT_IMPLEMENTED = b"\x05"
30TAG_ELLIPSIS = b"\x06"
31# types
32TAG_UNICODE = b"\x08"
33# deprecated w/ py2 support TAG_LONG = b"\x09"
34TAG_STR1 = b"\x0a"
35TAG_STR2 = b"\x0b"
36TAG_STR3 = b"\x0c"
37TAG_STR4 = b"\x0d"
38TAG_STR_L1 = b"\x0e"
39TAG_STR_L4 = b"\x0f"
40TAG_TUP1 = b"\x10"
41TAG_TUP2 = b"\x11"
42TAG_TUP3 = b"\x12"
43TAG_TUP4 = b"\x13"
44TAG_TUP_L1 = b"\x14"
45TAG_TUP_L4 = b"\x15"
46TAG_INT_L1 = b"\x16"
47TAG_INT_L4 = b"\x17"
48TAG_FLOAT = b"\x18"
49TAG_SLICE = b"\x19"
50TAG_FSET = b"\x1a"
51TAG_COMPLEX = b"\x1b"
52IMM_INTS = dict((i, bytes([i + 0x50])) for i in range(-0x30, 0xa0))
53
54I1 = Struct("!B")
55I4 = Struct("!L")
56F8 = Struct("!d")
57C16 = Struct("!dd")
58
59_dump_registry = {}
60_load_registry = {}
61IMM_INTS_LOADER = dict((v, k) for k, v in IMM_INTS.items())
62
63
64def register(coll, key):
65    def deco(func):
66        coll[key] = func
67        return func
68    return deco
69
70# ===============================================================================
71# dumping
72# ===============================================================================
73@register(_dump_registry, type(None))
74def _dump_none(obj, stream):
75    stream.append(TAG_NONE)
76
77
78@register(_dump_registry, type(NotImplemented))
79def _dump_notimplemeted(obj, stream):
80    stream.append(TAG_NOT_IMPLEMENTED)
81
82
83@register(_dump_registry, type(Ellipsis))
84def _dump_ellipsis(obj, stream):
85    stream.append(TAG_ELLIPSIS)
86
87
88@register(_dump_registry, bool)
89def _dump_bool(obj, stream):
90    if obj:
91        stream.append(TAG_TRUE)
92    else:
93        stream.append(TAG_FALSE)
94
95
96@register(_dump_registry, slice)
97def _dump_slice(obj, stream):
98    stream.append(TAG_SLICE)
99    _dump((obj.start, obj.stop, obj.step), stream)
100
101
102@register(_dump_registry, frozenset)
103def _dump_frozenset(obj, stream):
104    stream.append(TAG_FSET)
105    _dump(tuple(obj), stream)
106
107
108@register(_dump_registry, int)
109def _dump_int(obj, stream):
110    if obj in IMM_INTS:
111        stream.append(IMM_INTS[obj])
112    else:
113        obj = BYTES_LITERAL(str(obj))
114        lenobj = len(obj)
115        if lenobj < 256:
116            stream.append(TAG_INT_L1 + I1.pack(lenobj) + obj)
117        else:
118            stream.append(TAG_INT_L4 + I4.pack(lenobj) + obj)
119
120
121@register(_dump_registry, float)
122def _dump_float(obj, stream):
123    stream.append(TAG_FLOAT + F8.pack(obj))
124
125
126@register(_dump_registry, complex)
127def _dump_complex(obj, stream):
128    stream.append(TAG_COMPLEX + C16.pack(obj.real, obj.imag))
129
130
131@register(_dump_registry, bytes)
132def _dump_bytes(obj, stream):
133    lenobj = len(obj)
134    if lenobj == 0:
135        stream.append(TAG_EMPTY_STR)
136    elif lenobj == 1:
137        stream.append(TAG_STR1 + obj)
138    elif lenobj == 2:
139        stream.append(TAG_STR2 + obj)
140    elif lenobj == 3:
141        stream.append(TAG_STR3 + obj)
142    elif lenobj == 4:
143        stream.append(TAG_STR4 + obj)
144    elif lenobj < 256:
145        stream.append(TAG_STR_L1 + I1.pack(lenobj) + obj)
146    else:
147        stream.append(TAG_STR_L4 + I4.pack(lenobj) + obj)
148
149
150@register(_dump_registry, type(u""))
151def _dump_str(obj, stream):
152    stream.append(TAG_UNICODE)
153    _dump_bytes(obj.encode("utf8"), stream)
154
155
156@register(_dump_registry, tuple)
157def _dump_tuple(obj, stream):
158    lenobj = len(obj)
159    if lenobj == 0:
160        stream.append(TAG_EMPTY_TUPLE)
161    elif lenobj == 1:
162        stream.append(TAG_TUP1)
163    elif lenobj == 2:
164        stream.append(TAG_TUP2)
165    elif lenobj == 3:
166        stream.append(TAG_TUP3)
167    elif lenobj == 4:
168        stream.append(TAG_TUP4)
169    elif lenobj < 256:
170        stream.append(TAG_TUP_L1 + I1.pack(lenobj))
171    else:
172        stream.append(TAG_TUP_L4 + I4.pack(lenobj))
173    for item in obj:
174        _dump(item, stream)
175
176
177def _undumpable(obj, stream):
178    raise TypeError("cannot dump %r" % (obj,))
179
180
181def _dump(obj, stream):
182    _dump_registry.get(type(obj), _undumpable)(obj, stream)
183
184# ===============================================================================
185# loading
186# ===============================================================================
187@register(_load_registry, TAG_NONE)
188def _load_none(stream):
189    return None
190
191
192@register(_load_registry, TAG_NOT_IMPLEMENTED)
193def _load_nonimp(stream):
194    return NotImplemented
195
196
197@register(_load_registry, TAG_ELLIPSIS)
198def _load_elipsis(stream):
199    return Ellipsis
200
201
202@register(_load_registry, TAG_TRUE)
203def _load_true(stream):
204    return True
205
206
207@register(_load_registry, TAG_FALSE)
208def _load_false(stream):
209    return False
210
211
212@register(_load_registry, TAG_EMPTY_TUPLE)
213def _load_empty_tuple(stream):
214    return ()
215
216
217@register(_load_registry, TAG_EMPTY_STR)
218def _load_empty_str(stream):
219    return b""
220
221
222@register(_load_registry, TAG_FLOAT)
223def _load_float(stream):
224    return F8.unpack(stream.read(8))[0]
225
226
227@register(_load_registry, TAG_COMPLEX)
228def _load_complex(stream):
229    real, imag = C16.unpack(stream.read(16))
230    return complex(real, imag)
231
232
233@register(_load_registry, TAG_STR1)
234def _load_str1(stream):
235    return stream.read(1)
236
237
238@register(_load_registry, TAG_STR2)
239def _load_str2(stream):
240    return stream.read(2)
241
242
243@register(_load_registry, TAG_STR3)
244def _load_str3(stream):
245    return stream.read(3)
246
247
248@register(_load_registry, TAG_STR4)
249def _load_str4(stream):
250    return stream.read(4)
251
252
253@register(_load_registry, TAG_STR_L1)
254def _load_str_l1(stream):
255    l, = I1.unpack(stream.read(1))
256    return stream.read(l)
257
258
259@register(_load_registry, TAG_STR_L4)
260def _load_str_l4(stream):
261    l, = I4.unpack(stream.read(4))
262    return stream.read(l)
263
264
265@register(_load_registry, TAG_UNICODE)
266def _load_unicode(stream):
267    obj = _load(stream)
268    return obj.decode("utf-8")
269
270
271@register(_load_registry, TAG_TUP1)
272def _load_tup1(stream):
273    return (_load(stream),)
274
275
276@register(_load_registry, TAG_TUP2)
277def _load_tup2(stream):
278    return (_load(stream), _load(stream))
279
280
281@register(_load_registry, TAG_TUP3)
282def _load_tup3(stream):
283    return (_load(stream), _load(stream), _load(stream))
284
285
286@register(_load_registry, TAG_TUP4)
287def _load_tup4(stream):
288    return (_load(stream), _load(stream), _load(stream), _load(stream))
289
290
291@register(_load_registry, TAG_TUP_L1)
292def _load_tup_l1(stream):
293    l, = I1.unpack(stream.read(1))
294    return tuple(_load(stream) for i in range(l))
295
296
297@register(_load_registry, TAG_TUP_L4)
298def _load_tup_l4(stream):
299    l, = I4.unpack(stream.read(4))
300    return tuple(_load(stream) for i in range(l))
301
302
303@register(_load_registry, TAG_SLICE)
304def _load_slice(stream):
305    start, stop, step = _load(stream)
306    return slice(start, stop, step)
307
308
309@register(_load_registry, TAG_FSET)
310def _load_frozenset(stream):
311    return frozenset(_load(stream))
312
313
314@register(_load_registry, TAG_INT_L1)
315def _load_int_l1(stream):
316    l, = I1.unpack(stream.read(1))
317    return int(stream.read(l))
318
319
320@register(_load_registry, TAG_INT_L4)
321def _load_int_l4(stream):
322    l, = I4.unpack(stream.read(4))
323    return int(stream.read(l))
324
325
326def _load(stream):
327    tag = stream.read(1)
328    if tag in IMM_INTS_LOADER:
329        return IMM_INTS_LOADER[tag]
330    return _load_registry.get(tag)(stream)
331
332# ===============================================================================
333# API
334# ===============================================================================
335
336
337def dump(obj):
338    """Converts (dumps) the given object to a byte-string representation
339
340    :param obj: any :func:`dumpable` object
341
342    :returns: a byte-string representation of the object
343    """
344    stream = []
345    _dump(obj, stream)
346    return b"".join(stream)
347
348
349def load(data):
350    """Recreates (loads) an object from its byte-string representation
351
352    :param data: the byte-string representation of an object
353
354    :returns: the dumped object
355    """
356    stream = BytesIO(data)
357    return _load(stream)
358
359
360simple_types = frozenset([type(None), int, bool, float, bytes, str, complex, type(NotImplemented), type(Ellipsis)])
361
362
363def dumpable(obj):
364    """Indicates whether the given object is *dumpable* by brine
365
366    :returns: ``True`` if the object is dumpable (e.g., :func:`dump` would succeed),
367              ``False`` otherwise
368    """
369    if type(obj) in simple_types:
370        return True
371    if type(obj) in (tuple, frozenset):
372        return all(dumpable(item) for item in obj)
373    if type(obj) is slice:
374        return dumpable(obj.start) and dumpable(obj.stop) and dumpable(obj.step)
375    return False
376
377
378if __name__ == "__main__":
379    import doctest
380    doctest.testmod()
381