Skip to content

Commit 87f292c

Browse files
committed
Allow packed data to be captured while executing skip(), etc.
1 parent d5f9995 commit 87f292c

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
@@ -467,11 +467,16 @@ cdef class Unpacker(object):
467467
else:
468468
self.file_like = None
469469

470-
cdef object _unpack(self, execute_fn execute):
470+
cdef object _unpack(self, execute_fn execute, object write_bytes):
471471
cdef int ret
472472
cdef object obj
473+
cdef size_t prev_head
473474
while 1:
475+
prev_head = self.buf_head
474476
ret = execute(&self.ctx, self.buf, self.buf_tail, &self.buf_head)
477+
if write_bytes is not None:
478+
write_bytes(PyBytes_FromStringAndSize(self.buf + prev_head, self.buf_head - prev_head))
479+
475480
if ret == 1:
476481
obj = template_data(&self.ctx)
477482
template_init(&self.ctx)
@@ -484,27 +489,35 @@ cdef class Unpacker(object):
484489
else:
485490
raise ValueError("Unpack failed: error = %d" % (ret,))
486491

487-
def unpack(self):
488-
"""unpack one object"""
489-
return self._unpack(template_construct)
492+
def unpack(self, object write_bytes=None):
493+
"""
494+
unpack one object
495+
496+
If write_bytes is not None, it will be called with parts of the raw message as it is unpacked.
497+
"""
498+
return self._unpack(template_construct, write_bytes)
499+
500+
def skip(self, object write_bytes=None):
501+
"""
502+
read and ignore one object, returning None
490503
491-
def skip(self):
492-
"""read and ignore one object, returning None"""
493-
return self._unpack(template_skip)
504+
If write_bytes is not None, it will be called with parts of the raw message as it is unpacked.
505+
"""
506+
return self._unpack(template_skip, write_bytes)
494507

495-
def read_array_header(self):
508+
def read_array_header(self, object write_bytes=None):
496509
"""assuming the next object is an array, return its size n, such that the next n unpack() calls will iterate over its contents."""
497-
return self._unpack(read_array_header)
510+
return self._unpack(read_array_header, write_bytes)
498511

499-
def read_map_header(self):
512+
def read_map_header(self, object write_bytes=None):
500513
"""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."""
501-
return self._unpack(read_map_header)
514+
return self._unpack(read_map_header, write_bytes)
502515

503516
def __iter__(self):
504517
return self
505518

506519
def __next__(self):
507-
return self._unpack(template_construct)
520+
return self._unpack(template_construct, None)
508521

509522
# for debug.
510523
#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)