Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 33 additions & 33 deletions src/msgspec/msgpack.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -12,66 +12,66 @@ from typing import (

from typing_extensions import Buffer

T = TypeVar("T")
_T = TypeVar("_T")

enc_hook_sig: TypeAlias = Callable[[Any], Any] | None
ext_hook_sig: TypeAlias = Callable[[int, memoryview], Any] | None
dec_hook_sig: TypeAlias = Callable[[type, Any], Any] | None
_EncHookSig: TypeAlias = Callable[[Any], Any] | None
_ExtHookSig: TypeAlias = Callable[[int, memoryview], Any] | None
_DecHookSig: TypeAlias = Callable[[type, Any], Any] | None

@final
class Ext:
code: int
data: bytes | bytearray | memoryview
def __init__(self, code: int, data: bytes | bytearray | memoryview) -> None: ...
data: Buffer
def __init__(self, code: int, data: Buffer) -> None: ...

@final
class Decoder(Generic[T]):
type: Type[T] # needed for mypy, because of the same name
class Decoder(Generic[_T]):
type: Type[_T] # needed for mypy, because of the same name
strict: bool
dec_hook: dec_hook_sig
ext_hook: ext_hook_sig
dec_hook: _DecHookSig
ext_hook: _ExtHookSig
@overload
def __init__(
self: Decoder[Any],
*,
strict: bool = True,
dec_hook: dec_hook_sig = None,
ext_hook: ext_hook_sig = None,
dec_hook: _DecHookSig = None,
ext_hook: _ExtHookSig = None,
) -> None: ...
@overload
def __init__(
self: Decoder[T],
type: Type[T] = ...,
self: Decoder[_T],
type: Type[_T] = ...,
*,
strict: bool = True,
dec_hook: dec_hook_sig = None,
ext_hook: ext_hook_sig = None,
dec_hook: _DecHookSig = None,
ext_hook: _ExtHookSig = None,
) -> None: ...
@overload
def __init__(
self: Decoder[Any],
type: Any = ...,
*,
strict: bool = True,
dec_hook: dec_hook_sig = None,
ext_hook: ext_hook_sig = None,
dec_hook: _DecHookSig = None,
ext_hook: _ExtHookSig = None,
) -> None: ...
def decode(self, buf: Buffer, /) -> T: ...
def decode(self, buf: Buffer, /) -> _T: ...

@final
class Encoder:
enc_hook: enc_hook_sig
enc_hook: _EncHookSig
decimal_format: Literal["string", "number"]
uuid_format: Literal["canonical", "hex", "bytes"]
order: Literal[None, "deterministic", "sorted"]
order: Literal["deterministic", "sorted"] | None
def __init__(
self,
*,
enc_hook: enc_hook_sig = None,
enc_hook: _EncHookSig = None,
decimal_format: Literal["string", "number"] = "string",
uuid_format: Literal["canonical", "hex", "bytes"] = "canonical",
order: Literal[None, "deterministic", "sorted"] = None,
): ...
order: Literal["deterministic", "sorted"] | None = None,
) -> None: ...
def encode(self, obj: Any, /) -> bytes: ...
def encode_into(
self, obj: Any, buffer: bytearray, offset: int | None = 0, /
Expand All @@ -83,33 +83,33 @@ def decode(
/,
*,
strict: bool = True,
dec_hook: dec_hook_sig = None,
ext_hook: ext_hook_sig = None,
dec_hook: _DecHookSig = None,
ext_hook: _ExtHookSig = None,
) -> Any: ...
@overload
def decode(
buf: Buffer,
/,
*,
type: type[T] = ...,
type: type[_T] = ...,
strict: bool = True,
dec_hook: dec_hook_sig = None,
ext_hook: ext_hook_sig = None,
) -> T: ...
dec_hook: _DecHookSig = None,
ext_hook: _ExtHookSig = None,
) -> _T: ...
@overload
def decode(
buf: Buffer,
/,
*,
type: Any = ...,
strict: bool = True,
dec_hook: dec_hook_sig = None,
ext_hook: ext_hook_sig = None,
dec_hook: _DecHookSig = None,
ext_hook: _ExtHookSig = None,
) -> Any: ...
def encode(
obj: Any,
/,
*,
enc_hook: enc_hook_sig = None,
enc_hook: _EncHookSig = None,
order: Literal[None, "deterministic", "sorted"] = None,
) -> bytes: ...
8 changes: 7 additions & 1 deletion tests/typing/basic_typing_examples.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# fmt: off
from __future__ import annotations
import array
import decimal
import pickle
from typing import Annotated, Any, Final, Literal
Expand Down Expand Up @@ -736,7 +737,12 @@ def check_msgpack_decode_strict() -> None:
def check_msgpack_Ext() -> None:
ext = msgspec.msgpack.Ext(1, b"test")
reveal_type(ext.code) # assert "int" in typ
reveal_type(ext.data) # assert "bytes" in typ
reveal_type(ext.data) # assert "Buffer" in typ

# TODO: test that non buffers can't be used:
msgspec.msgpack.Ext(1, bytearray())
msgspec.msgpack.Ext(1, memoryview(b''))
msgspec.msgpack.Ext(1, array.array('i', [1, 2, 3]))


##########################################################
Expand Down
19 changes: 18 additions & 1 deletion tests/unit/test_msgpack.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import array
import datetime
import enum
import gc
Expand Down Expand Up @@ -1296,9 +1297,25 @@ def test_serialize_compatibility(self, size):
def test_serialize_other_types(self, typ):
buf = b"test"
a = msgspec.msgpack.encode(msgspec.msgpack.Ext(1, buf))
b = msgspec.msgpack.encode(msgspec.msgpack.Ext(1, typ(buf)))

ext = msgspec.msgpack.Ext(1, typ(buf))
assert isinstance(ext.data, typ)
b = msgspec.msgpack.encode(ext)
assert a == b

decoded = msgspec.msgpack.decode(b)
assert isinstance(decoded.data, bytes)
assert decoded.data == buf

def test_other_buffers(self):
buf = array.array("i", [1, 2, 3, 4])
out = msgspec.msgpack.decode(
msgspec.msgpack.encode(msgspec.msgpack.Ext(1, buf)),
)

assert isinstance(out.data, bytes)
assert array.array("i", out.data) == buf

@pytest.mark.parametrize("size", sorted({0, 1, 2, 4, 8, 16, *SIZES}))
def test_roundtrip(self, size):
data = b"x" * size
Expand Down
Loading