Fast stream based implementation of msgpack in pure Python
Project description
msgpack-streams
Fast stream based implementation of msgpack in pure Python.
Installation
pip install msgpack-streams
Benchmarks
Average of 50 iterations each on a 3.77 MB payload, pure Python 3.14.3 (with
MSGPACK_PUREPYTHON=1).
| Implementation | Operation | Speedup vs msgpack |
|---|---|---|
msgpack-streams unpack |
decode | 2.83x |
msgpack-streams unpack_stream |
decode | 2.70x |
msgpack-streams pack |
encode | 1.84x |
msgpack-streams pack_stream |
encode | 1.69x |
For PyPy 3.11.15, the pure Python performance is comparable to the msgpack C
extension.
| Implementation | Operation | Speedup vs msgpack (C) |
|---|---|---|
msgpack-streams unpack |
decode | 0.95x |
msgpack-streams pack |
encode | 1.96x |
Usage
from msgpack_streams import pack, unpack
data = {"key": "value", "number": 42, "list": [1, 2, 3]}
packed = pack(data)
unpacked, excess_data = unpack(packed)
assert data == unpacked
assert not excess_data
The stream based API is also available:
from msgpack_streams import pack_stream, unpack_stream
import io
data = {"key": "value", "number": 42, "list": [1, 2, 3]}
with io.BytesIO() as stream:
pack_stream(stream, data)
# reset stream position for reading
stream.seek(0)
unpacked = unpack_stream(stream)
assert data == unpacked
Extensions
Datetime
Timezone-aware datetime objects are natively supported and automatically
encoded using the
msgpack Timestamp extension
(type code -1). The timestamp format (32-, 64-, or 96-bit) is chosen
automatically based on the value's range and precision. Decoded timestamps are
always returned as UTC datetime objects.
from datetime import datetime, timezone
from msgpack_streams import pack_stream, unpack_stream
import io
dt = datetime(2025, 3, 25, 12, 0, 0, tzinfo=timezone.utc)
with io.BytesIO() as stream:
pack_stream(stream, dt)
stream.seek(0)
unpacked = unpack_stream(stream)
assert unpacked == dt
Naive datetime objects (without tzinfo) will raise a ValueError.
ExtType
Arbitrary msgpack extension types are supported via the ExtType dataclass:
from msgpack_streams import ExtType, pack_stream, unpack_stream
import io
obj = ExtType(code=42, data=b"hello")
with io.BytesIO() as stream:
pack_stream(stream, obj)
stream.seek(0)
unpacked = unpack_stream(stream)
assert unpacked == obj
Use ext_hook to pack custom types as extensions, and ext_hook to decode them
back:
from dataclasses import dataclass
from msgpack_streams import ExtType, pack, unpack
from fmtspec import decode, encode, types # https://pypi.org/project/fmtspec/
@dataclass
class Point:
EXT_CODE = 10
__fmt__ = {
"x": types.u32,
"y": types.u32,
}
x: int
y: int
def unknown_type_hook(obj):
if isinstance(obj, Point):
return ExtType(Point.EXT_CODE, encode(obj))
return None # unsupported type -> TypeError
def ext_hook(ext):
if ext.code == Point.EXT_CODE:
return decode(ext.data, shape=Point)
return None # unknown -> keep as ExtType
pt = Point(1, 2)
packed = pack(pt, ext_hook=unknown_type_hook)
result, _ = unpack(packed, ext_hook=ext_hook)
assert pt == result
Depth limits
Use max_depth to reject excessively nested payloads during packing or
unpacking. If the nesting limit is exceeded, a RecursionError is raised.
max_depth counts the root object as one level, so scalar roots work with
max_depth=1, while nested containers require a higher value. The default value
is -1, which disables the depth limit.
Even with max_depth disabled, extremely deep payloads can hit Python's
built-in recursion limit. This limit can be temporarily raised:
import sys
from contextlib import contextmanager
from msgpack_streams import pack, unpack
@contextmanager
def recursion_limit(limit: int):
previous_limit = sys.getrecursionlimit()
sys.setrecursionlimit(limit)
try:
yield
finally:
sys.setrecursionlimit(previous_limit)
data = [[[{"key": "value"}]]]
with recursion_limit(10_000):
packed = pack(data, max_depth=9_000)
unpacked, excess_data = unpack(packed, max_depth=9_000)
assert unpacked == data
assert not excess_data
Use this carefully. Raising Python's recursion limit too far can still fail or destabilize the process.
API reference
def pack(
obj: object,
*,
float32: bool = False,
ext_hook: Callable[[object], ExtType | None] | None = None,
max_depth: int = -1,
) -> bytes:
...
Serialize obj to a bytes object. Pass float32=True to encode float
values as 32-bit instead of the default 64-bit.
Pass ext_hook to handle types that are not natively supported. The callback
receives the unsupported object and should return an ExtType to pack in its
place. If it returns None a TypeError is raised as normal.
Pass max_depth to limit container nesting during encoding. If the limit is
exceeded, a RecursionError is raised. The default -1 disables the limit.
def unpack(
data: bytes,
*,
ext_hook: Callable[[ExtType], object | None] | None = None,
max_depth: int = -1,
) -> tuple[object, bytes]:
...
Deserialize the first msgpack object from data. Returns (obj, excess) where
excess is any unconsumed bytes that followed the object.
Pass ext_hook to convert ExtType values during decoding. The callback
receives each ExtType and should return the decoded object, or None to leave
it as an ExtType.
Pass max_depth to limit container nesting during decoding. If the limit is
exceeded, a RecursionError is raised. The default -1 disables the limit.
def pack_stream(
stream: BinaryIO,
obj: object,
*,
float32: bool = False,
ext_hook: Callable[[object], ExtType | None] | None = None,
max_depth: int = -1,
) -> None:
...
Serialize obj directly into a binary stream. Pass float32=True to encode
float values as 32-bit instead of the default 64-bit.
Pass ext_hook to handle types that are not natively supported. The callback
receives the unsupported object and should return an ExtType to pack in its
place. If it returns None a TypeError is raised as normal.
Pass max_depth to limit container nesting during encoding. If the limit is
exceeded, a RecursionError is raised. The default -1 disables the limit.
def unpack_stream(
stream: BinaryIO,
*,
ext_hook: Callable[[ExtType], object] | None = None,
max_depth: int = -1,
) -> object:
...
Deserialize a single msgpack object from a binary stream, advancing the stream position past the consumed bytes.
Pass ext_hook to convert ExtType values during decoding. The callback
receives each ExtType and should return the decoded object, or None to leave
it as an ExtType.
Pass max_depth to limit container nesting during decoding. If the limit is
exceeded, a RecursionError is raised. The default -1 disables the limit.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file msgpack_streams-1.1.0.tar.gz.
File metadata
- Download URL: msgpack_streams-1.1.0.tar.gz
- Upload date:
- Size: 6.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.3 {"installer":{"name":"uv","version":"0.11.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f3d19f77a87f15f0f4f4a0fcc9c5aff6128ffc6113d5c00644a4faf70fc4a4ef
|
|
| MD5 |
0fea2c6273bf82d28723ca5ff0adcccb
|
|
| BLAKE2b-256 |
36352c1f8cc1858240b4be3edee99089446a68df4376644eb34668818a1b5c6e
|
File details
Details for the file msgpack_streams-1.1.0-py3-none-any.whl.
File metadata
- Download URL: msgpack_streams-1.1.0-py3-none-any.whl
- Upload date:
- Size: 8.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.3 {"installer":{"name":"uv","version":"0.11.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
df60338686d9bd6da0da9a32cb865780794c7bc16adb8ee947df15f072f4a895
|
|
| MD5 |
32d7a22dfd86ca1a5a0fac52323d627d
|
|
| BLAKE2b-256 |
fa0a80cbf574571402b6eabc0989a562fbb3100cd64e2f094160c64c2d596e33
|