Skip to content

Commit cdb7bd3

Browse files
authored
Correctly parse List[X] annotated objects (#170)
* Correctly parse List[X] annotated objects from dictionaries * Formatting * Add testcase for data list * Fix to_json of lists * Fix parsing List[x] from cbor * Raise useful exception when encountering wrong type
1 parent 985a2b6 commit cdb7bd3

File tree

3 files changed

+62
-5
lines changed

3 files changed

+62
-5
lines changed

pycardano/plutus.py

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -529,16 +529,14 @@ def _dfs(obj):
529529
return {"int": obj}
530530
elif isinstance(obj, bytes):
531531
return {"bytes": obj.hex()}
532-
elif isinstance(obj, list):
533-
return [_dfs(item) for item in obj]
534-
elif isinstance(obj, IndefiniteList):
532+
elif isinstance(obj, IndefiniteList) or isinstance(obj, list):
535533
return {"list": [_dfs(item) for item in obj]}
536534
elif isinstance(obj, dict):
537535
return {"map": [{"v": _dfs(v), "k": _dfs(k)} for k, v in obj.items()]}
538536
elif isinstance(obj, PlutusData):
539537
return {
540538
"constructor": obj.CONSTR_ID,
541-
"fields": _dfs([getattr(obj, f.name) for f in fields(obj)]),
539+
"fields": [_dfs(getattr(obj, f.name)) for f in fields(obj)],
542540
}
543541
else:
544542
raise TypeError(f"Unexpected type {type(obj)}")
@@ -589,6 +587,25 @@ def _dfs(obj):
589587
raise DeserializeException(
590588
f"Unexpected data structure: {f}."
591589
)
590+
elif (
591+
hasattr(f_info.type, "__origin__")
592+
and f_info.type.__origin__ is list
593+
):
594+
t_args = f_info.type.__args__
595+
if len(t_args) != 1:
596+
raise DeserializeException(
597+
f"List types need exactly one type argument, but got {t_args}"
598+
)
599+
if "list" not in f:
600+
raise DeserializeException(
601+
f'Expected type "list" for constructor List but got {f}'
602+
)
603+
t = t_args[0]
604+
if inspect.isclass(t) and issubclass(t, PlutusData):
605+
converted_fields.append(t.from_dict(f))
606+
else:
607+
converted_fields.append(_dfs(f))
608+
592609
elif (
593610
hasattr(f_info.type, "__origin__")
594611
and f_info.type.__origin__ is dict

pycardano/serialization.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,19 @@ def _restore_dataclass_field(
417417
return f.metadata["object_hook"](v)
418418
elif isclass(f.type) and issubclass(f.type, CBORSerializable):
419419
return f.type.from_primitive(v)
420+
elif hasattr(f.type, "__origin__") and (f.type.__origin__ is list):
421+
t_args = f.type.__args__
422+
if len(t_args) != 1:
423+
raise DeserializeException(
424+
f"List types need exactly one type argument, but got {t_args}"
425+
)
426+
t = t_args[0]
427+
if not isinstance(v, list):
428+
raise DeserializeException(f"Expected type list but got {type(v)}")
429+
if isclass(t) and issubclass(t, CBORSerializable):
430+
return IndefiniteList([t.from_primitive(w) for w in v])
431+
else:
432+
return IndefiniteList(v)
420433
elif isclass(f.type) and issubclass(f.type, IndefiniteList):
421434
return IndefiniteList(v)
422435
elif hasattr(f.type, "__origin__") and (f.type.__origin__ is dict):

test/pycardano/test_plutus.py

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

44
from test.pycardano.util import check_two_way_cbor
5-
from typing import Union, Dict
5+
from typing import Union, Dict, List
66

77
import pytest
88

@@ -48,6 +48,11 @@ class DictTest(PlutusData):
4848
a: Dict[int, LargestTest]
4949

5050

51+
@dataclass
52+
class ListTest(PlutusData):
53+
a: List[LargestTest]
54+
55+
5156
@dataclass
5257
class VestingParam(PlutusData):
5358
CONSTR_ID = 1
@@ -104,6 +109,28 @@ def test_plutus_data_json():
104109
assert my_vesting == VestingParam.from_json(encoded_json)
105110

106111

112+
def test_plutus_data_json_list():
113+
test = ListTest([LargestTest(), LargestTest()])
114+
encoded_json = test.to_json(separators=(",", ":"))
115+
116+
assert (
117+
'{"constructor":0,"fields":[{"list":[{"constructor":9,"fields":[]},{"constructor":9,"fields":[]}]}]}'
118+
== encoded_json
119+
)
120+
121+
assert test == ListTest.from_json(encoded_json)
122+
123+
124+
def test_plutus_data_cbor_list():
125+
test = ListTest([LargestTest(), LargestTest()])
126+
127+
encoded_cbor = test.to_cbor()
128+
129+
assert "d8799f82d9050280d9050280ff" == encoded_cbor
130+
131+
assert test == ListTest.from_cbor(encoded_cbor)
132+
133+
107134
def test_plutus_data_json_dict():
108135
test = DictTest({0: LargestTest(), 1: LargestTest()})
109136

0 commit comments

Comments
 (0)