def test_compare(self): x = msgspec.Ext(1, b"two") x2 = msgspec.Ext(1, b"two") x3 = msgspec.Ext(1, b"three") x4 = msgspec.Ext(2, b"two") assert x == x2 assert not (x != x2) assert x != x3 assert not (x == x3) assert x != x4 assert not (x == x4)
def test_roundtrip_typed_decoder(self, size): dec = msgspec.Decoder(msgspec.Ext) ext = msgspec.Ext(5, b"x" * size) buf = msgspec.encode(ext) out = dec.decode(buf) assert out == ext
def test_roundtrip(self, size): data = b"x" * size code = 5 buf = msgspec.encode(msgspec.Ext(code, data)) out = msgspec.decode(buf) assert out.code == code assert out.data == data
def test_serialize_compatibility(self, size): msgpack = pytest.importorskip("msgpack") data = b"x" * size code = 5 msgspec_bytes = msgspec.encode(msgspec.Ext(code, data)) msgpack_bytes = msgpack.dumps(msgpack.ExtType(code, data)) assert msgspec_bytes == msgpack_bytes
def test_typed_decoder_skips_ext_hook(self): def ext_hook(code, data): assert False, "shouldn't ever get called" msg = [None, msgspec.Ext(1, b"test")] dec = msgspec.Decoder(List[Optional[msgspec.Ext]]) buf = msgspec.encode(msg) out = dec.decode(buf) assert out == msg
def test_decoder_ext_hook_raises(self): class CustomError(Exception): pass def ext_hook(code, buf): raise CustomError msg = msgspec.encode(range(5), default=lambda x: msgspec.Ext(1, b"test")) with pytest.raises(CustomError): msgspec.decode(msg, ext_hook=ext_hook)
def default(x): return msgspec.Ext(5, pickle.dumps(x))
def test_serialize_other_types(self, typ): buf = b"test" a = msgspec.encode(msgspec.Ext(1, buf)) b = msgspec.encode(msgspec.Ext(1, typ(buf))) assert a == b
class TestTypedDecoder: def check_unexpected_type(self, dec_type, val, msg): dec = msgspec.Decoder(dec_type) s = msgspec.Encoder().encode(val) with pytest.raises(msgspec.DecodingError, match=msg): dec.decode(s) def test_none(self): enc = msgspec.Encoder() dec = msgspec.Decoder(None) assert dec.decode(enc.encode(None)) is None with pytest.raises(msgspec.DecodingError, match="expected `None`"): assert dec.decode(enc.encode(1)) @pytest.mark.parametrize("x", [False, True]) def test_bool(self, x): enc = msgspec.Encoder() dec = msgspec.Decoder(bool) assert dec.decode(enc.encode(x)) is x def test_bool_unexpected_type(self): self.check_unexpected_type(bool, "a", "expected `bool`") @pytest.mark.parametrize("x", INTS) def test_int(self, x): enc = msgspec.Encoder() dec = msgspec.Decoder(int) assert dec.decode(enc.encode(x)) == x def test_int_unexpected_type(self): self.check_unexpected_type(int, "a", "expected `int`") @pytest.mark.parametrize("x", FLOATS + INTS) def test_float(self, x): enc = msgspec.Encoder() dec = msgspec.Decoder(float) res = dec.decode(enc.encode(x)) sol = float(x) if math.isnan(sol): assert math.isnan(res) else: assert res == sol def test_float_unexpected_type(self): self.check_unexpected_type(float, "a", "expected `float`") @pytest.mark.parametrize("size", SIZES) def test_str(self, size): enc = msgspec.Encoder() dec = msgspec.Decoder(str) x = "a" * size res = dec.decode(enc.encode(x)) assert res == x def test_str_unexpected_type(self): self.check_unexpected_type(str, 1, "expected `str`") @pytest.mark.parametrize("size", SIZES) def test_bytes(self, size): enc = msgspec.Encoder() dec = msgspec.Decoder(bytes) x = b"a" * size res = dec.decode(enc.encode(x)) assert isinstance(res, bytes) assert res == x def test_bytes_unexpected_type(self): self.check_unexpected_type(bytes, 1, "expected `bytes`") @pytest.mark.parametrize("size", SIZES) def test_bytearray(self, size): enc = msgspec.Encoder() dec = msgspec.Decoder(bytearray) x = bytearray(size) res = dec.decode(enc.encode(x)) assert isinstance(res, bytearray) assert res == x def test_bytearray_unexpected_type(self): self.check_unexpected_type(bytearray, 1, "expected `bytearray`") @pytest.mark.parametrize("size", SIZES) def test_list_lengths(self, size): enc = msgspec.Encoder() dec = msgspec.Decoder(list) x = list(range(size)) res = dec.decode(enc.encode(x)) assert res == x @pytest.mark.parametrize("typ", [list, List, List[Any]]) def test_list_any(self, typ): enc = msgspec.Encoder() dec = msgspec.Decoder(typ) x = [1, "two", b"three"] res = dec.decode(enc.encode(x)) assert res == x with pytest.raises(msgspec.DecodingError, match="expected `list`"): dec.decode(enc.encode(1)) def test_list_typed(self): enc = msgspec.Encoder() dec = msgspec.Decoder(List[int]) x = [1, 2, 3] res = dec.decode(enc.encode(x)) assert res == x with pytest.raises(msgspec.DecodingError, match="expected `int`"): dec.decode(enc.encode([1, 2, "three"])) @pytest.mark.parametrize("size", SIZES) def test_set_lengths(self, size): enc = msgspec.Encoder() dec = msgspec.Decoder(set) x = set(range(size)) res = dec.decode(enc.encode(x)) assert res == x @pytest.mark.parametrize("typ", [set, Set, Set[Any]]) def test_set_any(self, typ): enc = msgspec.Encoder() dec = msgspec.Decoder(typ) x = {1, "two", b"three"} res = dec.decode(enc.encode(x)) assert res == x with pytest.raises(msgspec.DecodingError, match="expected `set`"): dec.decode(enc.encode(1)) def test_set_typed(self): enc = msgspec.Encoder() dec = msgspec.Decoder(Set[int]) x = {1, 2, 3} res = dec.decode(enc.encode(x)) assert res == x with pytest.raises(msgspec.DecodingError, match="expected `int`"): dec.decode(enc.encode({1, 2, "three"})) @pytest.mark.parametrize("size", SIZES) def test_vartuple_lengths(self, size): enc = msgspec.Encoder() dec = msgspec.Decoder(tuple) x = tuple(range(size)) res = dec.decode(enc.encode(x)) assert res == x @pytest.mark.parametrize("typ", [tuple, Tuple, Tuple[Any, ...]]) def test_vartuple_any(self, typ): enc = msgspec.Encoder() dec = msgspec.Decoder(typ) x = (1, "two", b"three") res = dec.decode(enc.encode(x)) assert res == x with pytest.raises(msgspec.DecodingError, match="expected `tuple`"): dec.decode(enc.encode(1)) def test_vartuple_typed(self): enc = msgspec.Encoder() dec = msgspec.Decoder(Tuple[int, ...]) x = (1, 2, 3) res = dec.decode(enc.encode(x)) assert res == x with pytest.raises(msgspec.DecodingError, match="expected `int`"): dec.decode(enc.encode((1, 2, "three"))) def test_fixtuple_any(self): enc = msgspec.Encoder() dec = msgspec.Decoder(Tuple[Any, Any, Any]) x = (1, "two", b"three") res = dec.decode(enc.encode(x)) assert res == x with pytest.raises(msgspec.DecodingError, match="expected `tuple`"): dec.decode(enc.encode(1)) with pytest.raises( msgspec.DecodingError, match= r"Error decoding `Tuple\[Any, Any, Any\]`: expected tuple of length 3, got 2", ): dec.decode(enc.encode((1, 2))) def test_fixtuple_typed(self): enc = msgspec.Encoder() dec = msgspec.Decoder(Tuple[int, str, bytes]) x = (1, "two", b"three") res = dec.decode(enc.encode(x)) assert res == x with pytest.raises(msgspec.DecodingError, match="expected `bytes`"): dec.decode(enc.encode((1, "two", "three"))) with pytest.raises( msgspec.DecodingError, match= r"Error decoding `Tuple\[int, str, bytes\]`: expected tuple of length 3, got 2", ): dec.decode(enc.encode((1, 2))) @pytest.mark.parametrize("size", SIZES) def test_dict_lengths(self, size): enc = msgspec.Encoder() dec = msgspec.Decoder(dict) x = {i: i for i in range(size)} res = dec.decode(enc.encode(x)) assert res == x @pytest.mark.parametrize("typ", [dict, Dict, Dict[Any, Any]]) def test_dict_any_any(self, typ): enc = msgspec.Encoder() dec = msgspec.Decoder(typ) x = {1: "one", "two": 2, b"three": 3.0} res = dec.decode(enc.encode(x)) assert res == x with pytest.raises(msgspec.DecodingError, match="expected `dict`"): dec.decode(enc.encode(1)) def test_dict_any_val(self): enc = msgspec.Encoder() dec = msgspec.Decoder(Dict[str, Any]) x = {"a": 1, "b": "two", "c": b"three"} res = dec.decode(enc.encode(x)) assert res == x with pytest.raises(msgspec.DecodingError, match="expected `str`"): dec.decode(enc.encode({1: 2})) def test_dict_any_key(self): enc = msgspec.Encoder() dec = msgspec.Decoder(Dict[Any, str]) x = {1: "a", "two": "b", b"three": "c"} res = dec.decode(enc.encode(x)) assert res == x with pytest.raises(msgspec.DecodingError, match="expected `str`"): dec.decode(enc.encode({1: 2})) def test_dict_typed(self): enc = msgspec.Encoder() dec = msgspec.Decoder(Dict[str, int]) x = {"a": 1, "b": 2} res = dec.decode(enc.encode(x)) assert res == x with pytest.raises(msgspec.DecodingError, match="expected `str`"): dec.decode(enc.encode({1: 2})) with pytest.raises(msgspec.DecodingError, match="expected `int`"): dec.decode(enc.encode({"a": "two"})) def test_enum(self): enc = msgspec.Encoder() dec = msgspec.Decoder(FruitStr) a = enc.encode(FruitStr.APPLE) assert enc.encode("APPLE") == a assert dec.decode(a) == FruitStr.APPLE with pytest.raises(msgspec.DecodingError, match="truncated"): dec.decode(a[:-2]) with pytest.raises(msgspec.DecodingError, match="Error decoding enum `FruitStr`"): dec.decode(enc.encode("MISSING")) with pytest.raises(msgspec.DecodingError): dec.decode(enc.encode(1)) def test_int_enum(self): enc = msgspec.Encoder() dec = msgspec.Decoder(FruitInt) a = enc.encode(FruitInt.APPLE) assert enc.encode(1) == a assert dec.decode(a) == FruitInt.APPLE with pytest.raises(msgspec.DecodingError, match="truncated"): dec.decode(a[:-2]) with pytest.raises(msgspec.DecodingError, match="Error decoding enum `FruitInt`"): dec.decode(enc.encode(1000)) with pytest.raises(msgspec.DecodingError): dec.decode(enc.encode("INVALID")) def test_struct(self): enc = msgspec.Encoder() dec = msgspec.Decoder(Person) x = Person(first="harry", last="potter", age=13) a = enc.encode(x) assert (enc.encode({ "first": "harry", "last": "potter", "age": 13, "prefect": False }) == a) assert dec.decode(a) == x with pytest.raises(msgspec.DecodingError, match="truncated"): dec.decode(a[:-2]) with pytest.raises(msgspec.DecodingError, match="expected `struct`"): dec.decode(enc.encode(1)) with pytest.raises( msgspec.DecodingError, match= r"Error decoding `Person` field `first` \(`str`\): expected `str`, got `int`", ): dec.decode(enc.encode({1: "harry"})) def test_struct_field_wrong_type(self): enc = msgspec.Encoder() dec = msgspec.Decoder(Person) bad = enc.encode({ "first": "harry", "last": "potter", "age": "thirteen" }) with pytest.raises(msgspec.DecodingError, match="expected `int`"): dec.decode(bad) def test_struct_missing_fields(self): enc = msgspec.Encoder() dec = msgspec.Decoder(Person) bad = enc.encode({"first": "harry", "last": "potter"}) with pytest.raises(msgspec.DecodingError, match="missing required field `age`"): dec.decode(bad) bad = enc.encode({}) with pytest.raises(msgspec.DecodingError, match="missing required field `first`"): dec.decode(bad) @pytest.mark.parametrize( "extra", [ None, False, True, 1, 2.0, "three", b"four", [1, 2], { 3: 4 }, msgspec.Ext(1, b"12345"), msgspec.Ext(1, b""), ], ) def test_struct_ignore_extra_fields(self, extra): enc = msgspec.Encoder() dec = msgspec.Decoder(Person) a = enc.encode({ "extra1": extra, "first": "harry", "extra2": extra, "last": "potter", "age": 13, "extra3": extra, }) res = dec.decode(a) assert res == Person("harry", "potter", 13) def test_struct_defaults_missing_fields(self): enc = msgspec.Encoder() dec = msgspec.Decoder(Person) a = enc.encode({"first": "harry", "last": "potter", "age": 13}) res = dec.decode(a) assert res == Person("harry", "potter", 13) assert res.prefect is False def test_struct_gc_maybe_untracked_on_decode(self): class Test(msgspec.Struct): x: Any y: Any z: Tuple = () enc = msgspec.Encoder() dec = msgspec.Decoder(List[Test]) ts = [ Test(1, 2), Test(3, "hello"), Test([], []), Test({}, {}), Test(None, None, ()), ] a, b, c, d, e = dec.decode(enc.encode(ts)) assert not gc.is_tracked(a) assert not gc.is_tracked(b) assert gc.is_tracked(c) assert gc.is_tracked(d) assert not gc.is_tracked(e) def test_struct_recursive_definition(self): enc = msgspec.Encoder() dec = msgspec.Decoder(Node) x = Node(Node(Node(), Node(Node()))) s = enc.encode(x) res = dec.decode(s) assert res == x @pytest.mark.parametrize( "typ, value", [ (bool, False), (bool, True), (int, 1), (float, 2.5), (str, "a"), (bytes, b"a"), (bytearray, bytearray(b"a")), (FruitInt, FruitInt.APPLE), (FruitStr, FruitStr.APPLE), (Person, Person("harry", "potter", 13)), (list, [1]), (set, {1}), (tuple, (1, 2)), (Tuple[int, int], (1, 2)), (dict, { 1: 2 }), ], ) def test_optional(self, typ, value): enc = msgspec.Encoder() dec = msgspec.Decoder(Optional[typ]) s = enc.encode(value) s2 = enc.encode(None) assert dec.decode(s) == value assert dec.decode(s2) is None dec = msgspec.Decoder(typ) with pytest.raises(msgspec.DecodingError): dec.decode(s2) @pytest.mark.parametrize( "typ, value", [ (List[Optional[int]], [1, None]), (Tuple[Optional[int], int], (None, 1)), (Set[Optional[int]], {1, None}), (Dict[str, Optional[int]], { "a": 1, "b": None }), (Dict[Optional[str], int], { "a": 1, None: 2 }), ], ) def test_optional_nested(self, typ, value): enc = msgspec.Encoder() dec = msgspec.Decoder(typ) s = enc.encode(value) assert dec.decode(s) == value def test_decoding_error_no_struct_toplevel(self): b = msgspec.Encoder().encode([{"a": 1}]) dec = msgspec.Decoder(List[Dict[str, str]]) with pytest.raises( msgspec.DecodingError, match= r"Error decoding `List\[Dict\[str, str\]\]`: expected `str`, got `int`", ): dec.decode(b)
def test_immutable(self): x = msgspec.Ext(1, b"two") with pytest.raises(AttributeError): x.code = 2
def test_code_wrong_type(self): with pytest.raises(TypeError): msgspec.Ext(b"bad", b"bad")
def test_data_wrong_type(self): with pytest.raises(TypeError): msgspec.Ext(1, 2)
def test_code_out_of_range(self, code): with pytest.raises(ValueError): msgspec.Ext(code, b"bad")
def test_init(self, data): x = msgspec.Ext(1, data) assert x.code == 1 assert x.data == data
def test_decoder_ext_hook_bad_signature(self): msg = msgspec.encode(range(5), default=lambda x: msgspec.Ext(1, b"test")) with pytest.raises(TypeError): msgspec.decode(msg, ext_hook=lambda: None)
def test_pickleable(self): x = msgspec.Ext(1, b"two") x2 = pickle.loads(pickle.dumps(x)) assert x2.code == 1 assert x2.data == b"two"
def check_Ext() -> None: ext = msgspec.Ext(1, b"test") reveal_type(ext.code) # assert "int" in typ reveal_type(ext.data) # assert "bytes" in typ