Skip to content

Commit 54916f7

Browse files
committed
Merge pull request #23 from jnothman/write_bytes
Allow packed data to be captured while executing skip(), etc.
2 parents 1c38913 + 87f292c commit 54916f7

File tree

2 files changed

+57
-12
lines changed

2 files changed

+57
-12
lines changed

msgpack/_msgpack.pyx

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -479,11 +479,16 @@ cdef class Unpacker(object):
479479
else:
480480
self.file_like = None
481481

482-
cdef object _unpack(self, execute_fn execute):
482+
cdef object _unpack(self, execute_fn execute, object write_bytes):
483483
cdef int ret
484484
cdef object obj
485+
cdef size_t prev_head
485486
while 1:
487+
prev_head = self.buf_head
486488
ret = execute(&self.ctx, self.buf, self.buf_tail, &self.buf_head)
489+
if write_bytes is not None:
490+
write_bytes(PyBytes_FromStringAndSize(self.buf + prev_head, self.buf_head - prev_head))
491+
487492
if ret == 1:
488493
obj = template_data(&self.ctx)
489494
template_init(&self.ctx)
@@ -496,27 +501,35 @@ cdef class Unpacker(object):
496501
else:
497502
raise ValueError("Unpack failed: error = %d" % (ret,))
498503

499-
def unpack(self):
500-
"""unpack one object"""
501-
return self._unpack(template_construct)
504+
def unpack(self, object write_bytes=None):
505+
"""
506+
unpack one object
507+
508+
If write_bytes is not None, it will be called with parts of the raw message as it is unpacked.
509+
"""
510+
return self._unpack(template_construct, write_bytes)
511+
512+
def skip(self, object write_bytes=None):
513+
"""
514+
read and ignore one object, returning None
502515
503-
def skip(self):
504-
"""read and ignore one object, returning None"""
505-
return self._unpack(template_skip)
516+
If write_bytes is not None, it will be called with parts of the raw message as it is unpacked.
517+
"""
518+
return self._unpack(template_skip, write_bytes)
506519

507-
def read_array_header(self):
520+
def read_array_header(self, object write_bytes=None):
508521
"""assuming the next object is an array, return its size n, such that the next n unpack() calls will iterate over its contents."""
509-
return self._unpack(read_array_header)
522+
return self._unpack(read_array_header, write_bytes)
510523

511-
def read_map_header(self):
524+
def read_map_header(self, object write_bytes=None):
512525
"""assuming the next object is a map, return its size n, such that the next n * 2 unpack() calls will iterate over its key-value pairs."""
513-
return self._unpack(read_map_header)
526+
return self._unpack(read_map_header, write_bytes)
514527

515528
def __iter__(self):
516529
return self
517530

518531
def __next__(self):
519-
return self._unpack(template_construct)
532+
return self._unpack(template_construct, None)
520533

521534
# for debug.
522535
#def _buf(self):

test/test_unpack_raw.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"""Tests for cases where the user seeks to obtain packed msgpack objects"""
2+
3+
from nose import main
4+
from nose.tools import *
5+
import six
6+
from msgpack import Unpacker, packb
7+
8+
def test_write_bytes():
9+
unpacker = Unpacker()
10+
unpacker.feed(b'abc')
11+
f = six.BytesIO()
12+
assert_equal(unpacker.unpack(f.write), ord('a'))
13+
assert_equal(f.getvalue(), b'a')
14+
f.truncate(0)
15+
assert_is_none(unpacker.skip(f.write))
16+
assert_equal(f.getvalue(), b'b')
17+
f.truncate(0)
18+
assert_is_none(unpacker.skip())
19+
assert_equal(f.getvalue(), b'')
20+
21+
def test_write_bytes_multi_buffer():
22+
long_val = (5) * 100
23+
expected = packb(long_val)
24+
unpacker = Unpacker(six.BytesIO(expected), read_size=3, max_buffer_size=3)
25+
26+
f = six.BytesIO()
27+
unpacked = unpacker.unpack(f.write)
28+
assert_equal(unpacked, long_val)
29+
assert_equal(f.getvalue(), expected)
30+
31+
if __name__ == '__main__':
32+
main()

0 commit comments

Comments
 (0)