Skip to content

Commit 8b28c90

Browse files
committed
Add support for bypassing pre-packed data
1 parent 659ee98 commit 8b28c90

5 files changed

Lines changed: 78 additions & 3 deletions

File tree

msgpack/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import os
33

44
from .exceptions import * # noqa: F403
5-
from .ext import ExtType, Timestamp
5+
from .ext import Bypass, ExtType, Timestamp
66

77
version = (1, 2, 1)
88
__version__ = "1.2.1"

msgpack/_packer.pyx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ from cpython.datetime cimport (
88
cdef ExtType
99
cdef Timestamp
1010

11-
from .ext import ExtType, Timestamp
11+
from .ext import ExtType, Timestamp, Bypass
1212

1313

1414
cdef extern from "Python.h":
@@ -222,6 +222,8 @@ cdef class Packer:
222222
llval = o.seconds
223223
ulval = o.nanoseconds
224224
msgpack_pack_timestamp(&self.pk, llval, ulval)
225+
elif type(o) is Bypass:
226+
msgpack_pack_raw_body(&self.pk, o.data, len(o.data))
225227
elif PyList_CheckExact(o) if strict else (PyTuple_Check(o) or PyList_Check(o)):
226228
L = Py_SIZE(o)
227229
if L > ITEM_LIMIT:

msgpack/ext.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,19 @@ def __new__(cls, code, data):
1818
return super().__new__(cls, code, data)
1919

2020

21+
class Bypass:
22+
"""Bypass is a placeholder class to skip serialization and pass the bytes value as is."""
23+
24+
__slots__ = ["data"]
25+
26+
def __init__(self, data):
27+
if isinstance(data, bytes):
28+
self.data = data
29+
30+
else:
31+
self.data = memoryview(data).tobytes()
32+
33+
2134
class Timestamp:
2235
"""Timestamp represents the Timestamp extension type in msgpack.
2336

msgpack/fallback.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def newlist_hint(size):
3838

3939

4040
from .exceptions import BufferFull, ExtraData, FormatError, OutOfData, StackError
41-
from .ext import ExtType, Timestamp
41+
from .ext import Bypass, ExtType, Timestamp
4242

4343
EX_SKIP = 0
4444
EX_CONSTRUCT = 1
@@ -779,6 +779,9 @@ def _pack(
779779
self._buffer.write(struct.pack("b", code))
780780
self._buffer.write(data)
781781
return
782+
if check(obj, Bypass):
783+
self._buffer.write(obj.data)
784+
return
782785
if check(obj, list_types):
783786
n = len(obj)
784787
self._pack_array_header(n)

test/test_bypass.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
from msgpack import Bypass, packb, unpackb
2+
3+
4+
def test_roundtrip_bypassed_bytes():
5+
binary = b"\x00\x01\x02\x03\x04\x05"
6+
7+
binary_packed = packb(binary)
8+
binary_bypassed_packed = packb(Bypass(binary_packed))
9+
10+
assert binary_bypassed_packed == binary_packed
11+
assert unpackb(binary_packed) == binary
12+
13+
14+
def test_roundtrip_bypassed_object():
15+
obj = {"key0": {"key1": {"key2": {"key3": [1, 2, 3]}}}}
16+
17+
obj_packed = packb(obj)
18+
obj_bypassed_packed = packb(Bypass(obj_packed))
19+
20+
assert obj_bypassed_packed == obj_packed
21+
assert unpackb(obj_bypassed_packed) == obj
22+
23+
24+
def test_roundtrip_deep_bypassed_object():
25+
obj = {"key0": {"key1": {"key2": {"key3": [1, 2, 3]}}}}
26+
obj_bypassed = {"key0": {"key1": {"key2": {"key3": [1, 2, Bypass(packb(3))]}}}}
27+
28+
assert packb(obj) == packb(obj_bypassed)
29+
assert unpackb(packb(obj)) == obj
30+
31+
32+
def test_roundtrip_bypassed_object_with_cache():
33+
class SimpleCache:
34+
def __init__(self, obj):
35+
self.obj = obj
36+
self.packed = None
37+
38+
def get_packed(self):
39+
if self.packed is None:
40+
self.packed = packb(self.obj)
41+
42+
return Bypass(self.packed)
43+
44+
def default(obj):
45+
if isinstance(obj, SimpleCache):
46+
return obj.get_packed()
47+
return obj
48+
49+
obj = {"key0": {"key1": {"key2": {"key3": [1, 2, 3]}}}}
50+
cache = SimpleCache(obj)
51+
52+
obj_packed = packb(cache.obj, default=default)
53+
cache.packed = obj_packed
54+
obj_bypassed_packed = packb(Bypass(cache.packed))
55+
56+
assert obj_bypassed_packed == obj_packed
57+
assert unpackb(obj_bypassed_packed) == cache.obj

0 commit comments

Comments
 (0)