diff --git a/pycardano/serialization.py b/pycardano/serialization.py index 1b81a210..5ca96447 100644 --- a/pycardano/serialization.py +++ b/pycardano/serialization.py @@ -329,7 +329,7 @@ def _check_recursive(value, type_hint): _check_recursive(k, key_type) and _check_recursive(v, value_type) for k, v in value.items() ) - elif origin in (list, set, tuple): + elif origin in (list, set, tuple, frozenset): if value is None: return True args = type_hint.__args__ @@ -518,6 +518,7 @@ def _restore_typed_primitive( Returns: Union[:const:`Primitive`, CBORSerializable]: A CBOR primitive or a CBORSerializable. """ + if t is Any or (t in PRIMITIVE_TYPES and isinstance(v, t)): return v elif isclass(t) and issubclass(t, CBORSerializable): @@ -536,16 +537,14 @@ def _restore_typed_primitive( if not isinstance(v, bytes): raise DeserializeException(f"Expected type bytes but got {type(v)}") return ByteString(v) - elif isclass(t) and t.__name__ in ["PlutusV1Script", "PlutusV2Script"]: + elif isclass(t) and t.__name__ in [ + "PlutusV1Script", + "PlutusV2Script", + "PlutusV3Script", + ]: if not isinstance(v, bytes): raise DeserializeException(f"Expected type bytes but got {type(v)}") return t(v) - - elif isclass(t) and issubclass(t, IndefiniteList): - try: - return IndefiniteList(v) - except TypeError: - raise DeserializeException(f"Can not initialize IndefiniteList from {v}") elif hasattr(t, "__origin__") and (t.__origin__ is dict): t_args = t.__args__ if len(t_args) != 2: @@ -572,6 +571,11 @@ def _restore_typed_primitive( raise DeserializeException( f"Cannot deserialize object: \n{v}\n in any valid type from {t_args}." ) + elif isclass(t) and issubclass(t, IndefiniteList): + try: + return t(v) + except TypeError: + raise DeserializeException(f"Can not initialize IndefiniteList from {v}") raise DeserializeException(f"Cannot deserialize object: \n{v}\n to type {t}.") diff --git a/test/pycardano/test_serialization.py b/test/pycardano/test_serialization.py index 890eb16a..82d59fe6 100644 --- a/test/pycardano/test_serialization.py +++ b/test/pycardano/test_serialization.py @@ -1,24 +1,105 @@ +from collections import defaultdict, deque from dataclasses import dataclass, field from test.pycardano.util import check_two_way_cbor -from typing import Any, Dict, List, Optional, Set, Tuple, Union +from typing import Any, Deque, Dict, List, Optional, Set, Tuple, Union import cbor2 import pytest -import pycardano -from pycardano import Datum, RawPlutusData -from pycardano.exception import DeserializeException +from pycardano import Datum, MultiAsset, RawPlutusData, Transaction +from pycardano.exception import DeserializeException, SerializeException from pycardano.plutus import PlutusV1Script, PlutusV2Script from pycardano.serialization import ( ArrayCBORSerializable, + ByteString, CBORSerializable, DictCBORSerializable, IndefiniteList, MapCBORSerializable, + RawCBOR, + default_encoder, limit_primitive_type, ) +def test_hash(): + b1 = ByteString(b"hello") + b2 = ByteString(b"hello") + b3 = ByteString(b"world") + + assert hash(b1) == hash(b2) + assert hash(b1) != hash(b3) + + +def test_eq_with_bytestring(): + b1 = ByteString(b"hello") + b2 = ByteString(b"hello") + b3 = ByteString(b"world") + + assert b1 == b2 + assert b1 != b3 + + +def test_eq_with_bytes(): + b1 = ByteString(b"hello") + + assert b1 == b"hello" + assert b1 != b"world" + + +def test_eq_with_other_object(): + b1 = ByteString(b"hello") + + assert b1 != 123 + assert b1 != None + + +def test_default_encoder_cbor_serializable(): + cbor2.dumps(RawCBOR(b"1234"), default=default_encoder) + cbor2.dumps(ByteString(b"1234"), default=default_encoder) + + +def test_default_dict_serialization(): + @dataclass + class Test1(ArrayCBORSerializable): + a: defaultdict + + inner_dd = defaultdict(lambda: 0) + inner_dd[frozenset({1, 2, 3})] = 1 + + dd = Test1(a=inner_dd) + dd.to_primitive() + + +def test_default_dict_serialization_2(): + class MutableHashable(set): + def __init__(self, data): + self.data = data # Mutable attribute + + def __hash__(self): + # Provide a hash based on some property + return hash(self.data) + + def __eq__(self, other): + return isinstance(other, MutableHashable) and self.data == other.data + + @dataclass + class Test1(ArrayCBORSerializable): + a: MutableHashable + + dd = Test1(a=MutableHashable(1)) + dd.to_primitive() + + +def test_default_set_serialization(): + @dataclass + class Test1(ArrayCBORSerializable): + a: set + + dd = Test1(a=({"a", "b", "c"})) + dd.to_primitive() + + @pytest.mark.single def test_limit_primitive_type(): class MockClass(CBORSerializable): @@ -67,6 +148,17 @@ class Test1(ArrayCBORSerializable): assert hasattr(t, "unknown_field0") and t.unknown_field0 == "c" +def test_array_cbor_serializable_unknown_key(): + @dataclass + class Test1(ArrayCBORSerializable): + a: str + b: str + + values = {"a": "abc", "c": "cde"} + with pytest.raises(DeserializeException): + Test1.from_primitive(values) + + def test_array_cbor_serializable_optional_field(): @dataclass class Test1(ArrayCBORSerializable): @@ -100,6 +192,15 @@ class Test2(MapCBORSerializable): check_two_way_cbor(t) +def test_map_primitive_no_key(): + @dataclass + class Test1(MapCBORSerializable): + metadata: int = field(default=None, metadata={"key": 0}) + + with pytest.raises(DeserializeException): + Test1.from_primitive({1: 1}) + + def test_map_cbor_serializable_custom_keys(): @dataclass class Test1(MapCBORSerializable): @@ -117,6 +218,17 @@ class Test2(MapCBORSerializable): check_two_way_cbor(t) +def test_map_cbor_serializable_duplicate_keys(): + @dataclass + class Test3(MapCBORSerializable): + a: str = field(default="", metadata={"key": "0"}) + b: str = field(default="", metadata={"key": "0"}) + + obj = Test3(a="a", b="1") + with pytest.raises(SerializeException): + obj.to_shallow_primitive() + + def test_dict_cbor_serializable(): class MyTestDict(DictCBORSerializable): KEY_TYPE = bytes @@ -138,6 +250,15 @@ class MyTestDict(DictCBORSerializable): # Make sure the cbor of a and b are exactly the same even when their items are inserted in different orders. assert a.to_cbor_hex() == b.to_cbor_hex() + assert a.to_cbor_hex() != b + + del a[b"100"] + assert a.data == {b"110": 1, b"1": 3} + + a1 = a.__copy__() + assert a1.data == a.data + assert a1 is not a + def test_dict_complex_key_cbor_serializable(): @dataclass(unsafe_hash=True) @@ -210,6 +331,18 @@ class Test1(MapCBORSerializable): check_two_way_cbor(t) +def test_unexpected_validate_type(): + @dataclass + class Test1(CBORSerializable): + a: Optional[int] + b: Deque + + dq = deque((1, 2, 3)) + + obj = Test1(a=None, b=dq) + obj.validate() + + def test_wrong_primitive_type(): @dataclass class Test1(MapCBORSerializable): @@ -308,10 +441,94 @@ def test_deserialize_empty_value(): """ This should not crash even though there is an empty value in the transaction """ - transaction = pycardano.Transaction.from_cbor( + transaction = Transaction.from_cbor( "84a300818258202c9d507f93b5e46e9c807aff51e7bf0d015430fe1df0d2c6866df5c21febaf23020183a400581d707959ca93792ac3025823f706e9b12e8201d841bb96aed97a62db62a801821a009abef6a0028201d81843d8798003d818590859820259085459085101000032323232323232323222232325333008323232323232323232325333012002100114a0a666022a66602266e1c00d200113371e00e6eb8c024c03cc008c03c038528099b8f005375c6004601e6004601e01c2940c94ccc044cdc3a400000226464a66602c6032004266e3c004dd718059808980598088080b1bae30170013758602c602e602e602e602e602e602e602e602e601e6012601e018264646464646464646464646464a66603c66e24dd69807980e1806180e180b180e00d9991191919299981199b8748008004520001375a60506042004604200264a66604466e1d200200114c103d87a8000132323300100100222533302800114c103d87a800013232323253330293371e014004266e9520003302d375000297ae0133006006003375a60540066eb8c0a0008c0b0008c0a8004dd598139810001181000099198008008051129998128008a6103d87a800013232323253330263371e010004266e9520003302a374c00297ae01330060060033756604e0066eb8c094008c0a4008c09c004dd7180b180e180b180e1806180e180b180e00d9bae300f301c3016301c300c301c3016301c01b1323300100100422533302300114a226464a666044646600200200c44a66604e00229404c8c94ccc098cdc78010030a51133004004001302b002375c60520022660080080022940c09c008dd718128008a503322323300100100322533302400110031330253026001330020023027001330213015301b3015301b300b301b3015301b01a4bd70180180099299980e99b87480052000100113233333009002014001222325333022300e00114c103d87a800013374a9000198131ba60014bd701999980580080aa400244464a66604a66e1c005200014c103d87a800013374a9000198149ba80014bd7019b8000100200e00b3232002323300100100222533302300114984c94ccc09000452613232323232323253330283370e9000000899805005198160030028b1813000998098010009bae3026003375c604a0066052006604e004604c004604c0026604266ec0dd4808a6010120004bd6f7b6301bab300d301a300d301a3232325333022302500210011630230013300c3758602a603600246464a66604066e1d200000114a02944c078004c058c070c058c070c03cc070004c050c06805cc0040108c008004c004004894ccc07400452f5c026603c6036603e002660040046040002664464646466600200200497adef6c60222533302100210011333003003302400232323330010010030022225333025002100113233300400430290033333300e002375c60480026eacc094004888c94ccc09cc04c0045300103d87a800013374a9000198159ba60014bd70191998008008018011112999816001080089919980200218180019999980a0011bae302b001375a605800244464a66605c66e1c005200014c0103d87a800013374a9000198191ba80014bd7019b80002001017302e002010302700237566046004646600200200444a66603e002297ae0133020374c6eacc034c068c084004cc008008c088004cc020dd61803980b800919baf30123018301230180013374a90001980f1ba90034bd701bae300f3015300f3015014300f3015012222223233001001006225333020001133021337606ea4018dd4002a5eb7bdb1804c8c8c8c94ccc084cdd799803805001260103d8798000133025337606ea4028dd40048028a99981099b8f00a0021323253330233370e900000089981399bb037520186050604200400a200a604200266601001401200226604a66ec0dd48011ba800133006006003375a60440066eb8c080008c090008c08800488888c8cc004004018894ccc07c0044cc080cdd81ba9006374c00a97adef6c6013232323253330203375e6600e01400498103d8798000133024337606ea4028dd30048028a99981019b8f00a0021323253330223370e900000089981319bb03752018604e604000400a200a604000266601001401200226604866ec0dd48011ba600133006006003375660420066eb8c07c008c08c008c08400494ccc0600045288a50225333015337200040022980103d8798000153330153371e0040022980103d87a800014c103d87b8000230183019301900122323300100100322533301800114bd7009919299980b980280109980d80119802002000899802002000980e001180d00098078061180a980b0009bad30130013013002375c602200260220046eb8c03c004c8c8c94ccc03cc048008400458dd618080009919198008008011129998080008a5eb804c8ccc888c8cc00400400c894ccc058004400c4c8cc060dd39980c1ba90063301837526eb8c054004cc060dd41bad30160014bd7019801801980d001180c0009bae300f00137566020002660060066028004602400264646600200200444a666020002297adef6c6013232323253330113371e9101000021003133015337606ea4008dd3000998030030019bab3012003375c6020004602800460240026eacc03cc040c040c040c040c020004c004c01c0108c038004526136563253330083370e90000008a99980598030020a4c2c2a66601066e1d20020011533300b300600414985858c01800cc8c94ccc020cdc3a4000002264646464a66601e60240042646493180380119299980699b87480000044c8c8c8c8c8c94ccc058c0640084c9263253330143370e9000000899191919299980d980f00109924c60240062c6eb4c070004c070008c068004c04800858c04800458c05c004c05c008dd7180a800980a8011bae3013001300b00416300b0031630100013010002300e001300600516300600423253330083370e9000000899191919299980798090010a4c2c6eb8c040004c040008dd7180700098030010b1803000918029baa001230033754002ae6955ceaab9e5573eae815d0aba21a400581d707959ca93792ac3025823f706e9b12e8201d841bb96aed97a62db62a801821a005788a2a0028201d81843d8798003d81859045b820259045659045301000033232323232323232322322253330073232323232323232533300f00114a22646464a66602a60300042646464646464646464a666036a666036a666036002200a2940400852808018a50533301a533301a3370e01a9001099b8f011375c6026603000a29404cdc78079bae3003301800514a066e212000375a6004602e6014602e00c66e1cc8c8c8c94ccc070cdc3a40040022900009bad3021301a002301a00132533301b3370e90010008a6103d87a8000132323300100100222533302100114c103d87a800013232323253330223371e02e004266e95200033026375000297ae0133006006003375a60460066eb8c084008c094008c08c004dd59810180c801180c800991980080080111299980f0008a6103d87a8000132323232533301f3371e02c004266e95200033023374c00297ae0133006006003375660400066eb8c078008c088008c080004dd59800980b0038059180e980f0009991191980080080191299980e8008a5013232533301c3371e00400a29444cc010010004c084008dd7180f8009bac301b301c301c301c301c301c301c301c301c3014300f3014010375c601e602800660340026034004603000260206644a66602866e1d20043013001132325333019301c0021320023253330173370e9000000899191919299980f18108010991924c601400464a66603866e1d2000001132323232323253330253028002132498c94ccc08ccdc3a4000002264646464a666054605a00426493180a8018b1bad302b001302b002302900130210021630210011630260013026002375c604800260480046eb8c088004c06801058c06800c58c07c004c07c008c074004c05400858c05400458c068004c048004588c94ccc050cdc3a4000002264646464a666036603c0042930b1bae301c001301c002375c603400260240042c6024002600660200022c602c00264646600200200444a66602c002297ae0132325333015323253330173370e9001000899b8f013375c6038602a0042940c054004c038c04cc038c04c0084cc064008cc0100100044cc010010004c068008c060004dd618009807180498070051180a980b180b00099b8700148004dd6980900098090011bae30100013010002375c601c002646464a66601c602200420022c6eb0c03c004c8c8cc004004008894ccc03c00452f5c0264666444646600200200644a66602a00220062646602e6e9ccc05cdd48031980b9ba9375c60280026602e6ea0dd6980a800a5eb80cc00c00cc064008c05c004dd718070009bab300f001330030033013002301100132323300100100222533300f00114bd6f7b630099191919299980819b8f489000021003133014337606ea4008dd3000998030030019bab3011003375c601e004602600460220026eacc038c03cc03cc03cc03cc01c004c004c0180088c03400452613656375c0024600a6ea80048c00cdd5000ab9a5573aaae7955cfaba05742ae8930011e581c44942234591eee784947f20ab51826428bac8589b372aa9086ee49ee0001825839002b630067f4997129c6fcaea188d4535b1e69fa5860382ba1d2889c3ff3c5cf4a64da3d8d3ac295c3bcbe125961078cdb83080c2a722ef03b1b000000024ab36c59021a0004c829a1008182582064e1867f2c8e8da2e7f5bc06c2224dfdb51b9a40d895d556045ba696483c69685840525f47c570dcfee996eb41a726d1a25e19f46730dccc8e3c05b7dbcf98ceede1ce7931e3b7e0eee9f06b15351042c7285412ac8e4e624ee71805d1a926c65f02f5f6" ) assert ( - transaction.transaction_body.outputs[0].amount.multi_asset - == pycardano.MultiAsset() + transaction.transaction_body.outputs[0].amount.multi_asset == MultiAsset() ), "Invalid deserialization of multi asset" + + +def test_restore_typed_primitive(): + @dataclass + class Test1(ArrayCBORSerializable): + a: ByteString + b: str + + t = Test1.from_primitive([b"123", "b"]) + + +def test_restore_typed_byte_primitive(): + @dataclass + class Test1(ArrayCBORSerializable): + a: ByteString + b: str + + with pytest.raises(DeserializeException): + t = Test1.from_primitive(["123", "b"]) + + +def test_restore_typed_list_primitive(): + @dataclass + class Test1(ArrayCBORSerializable): + a: List[str] + + with pytest.raises(DeserializeException): + t = Test1.from_primitive([[1]]) + + +def test_restore_typed_plutus_script_primitive(): + @dataclass + class Test1(ArrayCBORSerializable): + a: PlutusV1Script + b: str + + with pytest.raises(DeserializeException): + t = Test1.from_primitive(["123", "b"]) + + +def test_restore_typed_indefinitelist(): + class Test1(IndefiniteList): + pass + + @dataclass + class Test2(ArrayCBORSerializable): + a: Test1 + + t = Test2.from_primitive([[1, 2, 3]]) + assert t.a.data == [1, 2, 3] + assert isinstance(t.a, Test1) + + +def test_restore_typed_not_dict_primitive(): + @dataclass + class Test1(ArrayCBORSerializable): + a: Dict[str, int] + + with pytest.raises(DeserializeException): + t = Test1.from_primitive(["a"]) + + +def test_restore_typed_union_primitive(): + @dataclass + class Test1(ArrayCBORSerializable): + a: Union[int, list] + + with pytest.raises(DeserializeException): + t = Test1.from_primitive(["a"]) + + +def test_copy(): + @dataclass + class Test1(ArrayCBORSerializable): + a: int + b: str + + class Test2(DictCBORSerializable): + KEY_TYPE = str + VALUE_TYPE = Test1 + + t = Test2() + t["x"] = Test1(a=1, b="x") + t["y"] = Test1(a=2, b="y") + t.copy()