Skip to content

Commit d610975

Browse files
committed
add support for extended types: you can now pack/unpack custom python objects by subclassing Packer and Unpacker
1 parent f45d7b4 commit d610975

File tree

2 files changed

+108
-47
lines changed

2 files changed

+108
-47
lines changed

msgpack/fallback.py

Lines changed: 84 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -58,54 +58,9 @@ def getvalue(self):
5858
TYPE_MAP = 2
5959
TYPE_RAW = 3
6060

61-
DEFAULT_RECURSE_LIMIT=511
62-
63-
def pack(o, stream, **kwargs):
64-
"""
65-
Pack object `o` and write it to `stream`
66-
67-
See :class:`Packer` for options.
68-
"""
69-
packer = Packer(**kwargs)
70-
stream.write(packer.pack(o))
71-
72-
def packb(o, **kwargs):
73-
"""
74-
Pack object `o` and return packed bytes
61+
EXTENDED_TYPE = 1000
7562

76-
See :class:`Packer` for options.
77-
"""
78-
return Packer(**kwargs).pack(o)
79-
80-
def unpack(stream, **kwargs):
81-
"""
82-
Unpack an object from `stream`.
83-
84-
Raises `ExtraData` when `packed` contains extra bytes.
85-
See :class:`Unpacker` for options.
86-
"""
87-
unpacker = Unpacker(stream, **kwargs)
88-
ret = unpacker._fb_unpack()
89-
if unpacker._fb_got_extradata():
90-
raise ExtraData(ret, unpacker._fb_get_extradata())
91-
return ret
92-
93-
def unpackb(packed, **kwargs):
94-
"""
95-
Unpack an object from `packed`.
96-
97-
Raises `ExtraData` when `packed` contains extra bytes.
98-
See :class:`Unpacker` for options.
99-
"""
100-
unpacker = Unpacker(None, **kwargs)
101-
unpacker.feed(packed)
102-
try:
103-
ret = unpacker._fb_unpack()
104-
except OutOfData:
105-
raise UnpackValueError("Data is not enough.")
106-
if unpacker._fb_got_extradata():
107-
raise ExtraData(ret, unpacker._fb_get_extradata())
108-
return ret
63+
DEFAULT_RECURSE_LIMIT=511
10964

11065
class Unpacker(object):
11166
"""
@@ -334,6 +289,9 @@ def _read_header(self, execute=EX_CONSTRUCT, write_bytes=None):
334289
elif b == 0xdf:
335290
n = struct.unpack(">I", self._fb_read(4, write_bytes))[0]
336291
typ = TYPE_MAP
292+
elif b == 0xc9:
293+
n, typ = struct.unpack(">Ib", self._fb_read(5, write_bytes))
294+
typ += EXTENDED_TYPE
337295
else:
338296
raise UnpackValueError("Unknown header: 0x%x" % b)
339297
return typ, n, obj
@@ -390,6 +348,10 @@ def _fb_unpack(self, execute=EX_CONSTRUCT, write_bytes=None):
390348
if self._encoding is not None:
391349
obj = obj.decode(self._encoding, self._unicode_errors)
392350
return obj
351+
if typ >= EXTENDED_TYPE:
352+
typ -= EXTENDED_TYPE
353+
data = self._fb_read(n, write_bytes)
354+
return self.handle_extended_type(typ, data)
393355
assert typ == TYPE_IMMEDIATE
394356
return obj
395357

@@ -411,6 +373,9 @@ def unpack(self, write_bytes=None):
411373
self._fb_consume()
412374
return ret
413375

376+
def handle_extended_type(self, typecode, data):
377+
raise NotImplementedError("Cannot decode extended type with typecode=%d" % typecode)
378+
414379
def read_array_header(self, write_bytes=None):
415380
ret = self._fb_unpack(EX_READ_ARRAY_HEADER, write_bytes)
416381
self._fb_consume()
@@ -521,10 +486,33 @@ def _pack(self, obj, nest_limit=DEFAULT_RECURSE_LIMIT, isinstance=isinstance):
521486
if isinstance(obj, dict):
522487
return self._fb_pack_map_pairs(len(obj), dict_iteritems(obj),
523488
nest_limit - 1)
489+
if self.pack_extended_type(obj):
490+
# it means that obj was succesfully handled by
491+
# handle_extended_type, so we are done
492+
return
524493
if self._default is not None:
525494
return self._pack(self._default(obj), nest_limit - 1)
526495
raise TypeError("Cannot serialize %r" % obj)
527496

497+
def pack_extended_type(self, obj):
498+
res = self.handle_extended_type(obj)
499+
if res is None:
500+
return False
501+
fmt, typecode, data = res
502+
# for now we support only this. We should add support for the other
503+
# fixext/ext formats
504+
assert fmt == "ext 32"
505+
assert 0 <= typecode <= 127
506+
N = len(data)
507+
self._buffer.write(struct.pack('>BIB', 0xc9, N, typecode))
508+
self._buffer.write(data)
509+
return True
510+
511+
def handle_extended_type(self, obj):
512+
# by default we don't support any extended type. This can be
513+
# overridden by subclasses
514+
return None
515+
528516
def pack(self, obj):
529517
self._pack(obj)
530518
ret = self._buffer.getvalue()
@@ -590,3 +578,52 @@ def bytes(self):
590578

591579
def reset(self):
592580
self._buffer = StringIO()
581+
582+
583+
def pack(o, stream, Packer=Packer, **kwargs):
584+
"""
585+
Pack object `o` and write it to `stream`
586+
587+
See :class:`Packer` for options.
588+
"""
589+
packer = Packer(**kwargs)
590+
stream.write(packer.pack(o))
591+
592+
def packb(o, Packer=Packer, **kwargs):
593+
"""
594+
Pack object `o` and return packed bytes
595+
596+
See :class:`Packer` for options.
597+
"""
598+
return Packer(**kwargs).pack(o)
599+
600+
def unpack(stream, Unpacker=Unpacker, **kwargs):
601+
"""
602+
Unpack an object from `stream`.
603+
604+
Raises `ExtraData` when `packed` contains extra bytes.
605+
See :class:`Unpacker` for options.
606+
"""
607+
unpacker = Unpacker(stream, **kwargs)
608+
ret = unpacker._fb_unpack()
609+
if unpacker._fb_got_extradata():
610+
raise ExtraData(ret, unpacker._fb_get_extradata())
611+
return ret
612+
613+
def unpackb(packed, Unpacker=Unpacker, **kwargs):
614+
"""
615+
Unpack an object from `packed`.
616+
617+
Raises `ExtraData` when `packed` contains extra bytes.
618+
See :class:`Unpacker` for options.
619+
"""
620+
unpacker = Unpacker(None, **kwargs)
621+
unpacker.feed(packed)
622+
try:
623+
ret = unpacker._fb_unpack()
624+
except OutOfData:
625+
raise UnpackValueError("Data is not enough.")
626+
if unpacker._fb_got_extradata():
627+
raise ExtraData(ret, unpacker._fb_get_extradata())
628+
return ret
629+

test/test_extension.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import array
2+
import msgpack
3+
4+
def test_extension_type():
5+
class MyPacker(msgpack.Packer):
6+
def handle_extended_type(self, obj):
7+
if isinstance(obj, array.array):
8+
fmt = "ext 32"
9+
typecode = 123 # application specific typecode
10+
data = obj.tostring()
11+
return fmt, typecode, data
12+
13+
class MyUnpacker(msgpack.Unpacker):
14+
def handle_extended_type(self, typecode, data):
15+
assert typecode == 123
16+
obj = array.array('d')
17+
obj.fromstring(data)
18+
return obj
19+
20+
obj = [42, 'hello', array.array('d', [1.1, 2.2, 3.3])]
21+
s = msgpack.packb(obj, MyPacker)
22+
obj2 = msgpack.unpackb(s, MyUnpacker)
23+
assert obj == obj2
24+

0 commit comments

Comments
 (0)