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