Skip to content

Commit 5340be1

Browse files
Enhance nativescript.py type hint (#129)
* UPDATE. including nativescript.py module in mypy test * UPDATE. ensure NativeScript.from_primitive() only handles list inputs UPDATE. improve to_dict() type hint UPDATE. improve to_dict() conditional readability * UPDATE. InvalidBefore and InvalidHereAfter should not have optional before and after fields * UPDATE. letting mypy know to_cbor() value is bytes * UPDATE. mypy cannot detect common attributes in a list of classes * UPDATE. mypy cannot infer the second argument of super() is NativeScript class' subclass * UPDATE. explicitly assigning NativeScript subclass instantiation type by individual conditional check UPDATE. simplify from_dict() method UPDATE. create a JSON_TAG_TO_INT map
1 parent c5ef3bc commit 5340be1

File tree

3 files changed

+52
-57
lines changed

3 files changed

+52
-57
lines changed

pycardano/nativescript.py

Lines changed: 46 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from __future__ import annotations
44

55
from dataclasses import dataclass, field
6-
from typing import ClassVar, List, Type, Union
6+
from typing import ClassVar, List, Type, Union, cast
77

88
from nacl.encoding import RawEncoder
99
from nacl.hash import blake2b
@@ -35,25 +35,37 @@ def from_primitive(
3535
) -> Union[
3636
ScriptPubkey, ScriptAll, ScriptAny, ScriptNofK, InvalidBefore, InvalidHereAfter
3737
]:
38-
script_type = value[0]
39-
for t in [
40-
ScriptPubkey,
41-
ScriptAll,
42-
ScriptAny,
43-
ScriptNofK,
44-
InvalidBefore,
45-
InvalidHereAfter,
46-
]:
47-
if t._TYPE == script_type:
48-
return super(NativeScript, t).from_primitive(value[1:])
38+
if not isinstance(
39+
value,
40+
(
41+
list,
42+
tuple,
43+
),
44+
):
45+
raise DeserializeException(
46+
f"A list or a tuple is required for deserialization: {str(value)}"
47+
)
48+
49+
script_type: int = value[0]
50+
if script_type == ScriptPubkey._TYPE:
51+
return super(NativeScript, ScriptPubkey).from_primitive(value[1:])
52+
elif script_type == ScriptAll._TYPE:
53+
return super(NativeScript, ScriptAll).from_primitive(value[1:])
54+
elif script_type == ScriptAny._TYPE:
55+
return super(NativeScript, ScriptAny).from_primitive(value[1:])
56+
elif script_type == ScriptNofK._TYPE:
57+
return super(NativeScript, ScriptNofK).from_primitive(value[1:])
58+
elif script_type == InvalidBefore._TYPE:
59+
return super(NativeScript, InvalidBefore).from_primitive(value[1:])
60+
elif script_type == InvalidHereAfter._TYPE:
61+
return super(NativeScript, InvalidHereAfter).from_primitive(value[1:])
4962
else:
5063
raise DeserializeException(f"Unknown script type indicator: {script_type}")
5164

5265
def hash(self) -> ScriptHash:
66+
cbor_bytes = cast(bytes, self.to_cbor("bytes"))
5367
return ScriptHash(
54-
blake2b(
55-
bytes(1) + self.to_cbor("bytes"), SCRIPT_HASH_SIZE, encoder=RawEncoder
56-
)
68+
blake2b(bytes(1) + cbor_bytes, SCRIPT_HASH_SIZE, encoder=RawEncoder)
5769
)
5870

5971
@classmethod
@@ -63,43 +75,16 @@ def from_dict(
6375
ScriptPubkey, ScriptAll, ScriptAny, ScriptNofK, InvalidBefore, InvalidHereAfter
6476
]:
6577
"""Parse a standard native script dictionary (potentially parsed from a JSON file)."""
66-
67-
types = {
68-
p.json_tag: p
69-
for p in [
70-
ScriptPubkey,
71-
ScriptAll,
72-
ScriptAny,
73-
ScriptNofK,
74-
InvalidBefore,
75-
InvalidHereAfter,
76-
]
77-
}
78-
script_type = script_json["type"]
79-
target_class = types[script_type]
8078
script_primitive = cls._script_json_to_primitive(script_json)
81-
return super(NativeScript, target_class).from_primitive(script_primitive[1:])
79+
return cls.from_primitive(script_primitive)
8280

8381
@classmethod
8482
def _script_json_to_primitive(
8583
cls: Type[NativeScript], script_json: JsonDict
8684
) -> List[Primitive]:
8785
"""Serialize a standard JSON native script into a primitive array"""
88-
89-
types = {
90-
p.json_tag: p
91-
for p in [
92-
ScriptPubkey,
93-
ScriptAll,
94-
ScriptAny,
95-
ScriptNofK,
96-
InvalidBefore,
97-
InvalidHereAfter,
98-
]
99-
}
100-
10186
script_type: str = script_json["type"]
102-
native_script = [types[script_type]._TYPE]
87+
native_script: List[Primitive] = [JSON_TAG_TO_INT[script_type]]
10388

10489
for key, value in script_json.items():
10590
if key == "type":
@@ -118,22 +103,18 @@ def _script_jsons_to_primitive(
118103
native_script = [cls._script_json_to_primitive(i) for i in script_jsons]
119104
return native_script
120105

121-
def to_dict(self) -> dict:
106+
def to_dict(self) -> JsonDict:
122107
"""Export to standard native script dictionary (potentially to dump to a JSON file)."""
123-
124-
script = {}
125-
108+
script: JsonDict = {}
126109
for value in self.__dict__.values():
127110
script["type"] = self.json_tag
128111

129112
if isinstance(value, list):
130113
script["scripts"] = [i.to_dict() for i in value]
131-
114+
elif isinstance(value, int):
115+
script[self.json_field] = value
132116
else:
133-
if isinstance(value, int):
134-
script[self.json_field] = value
135-
else:
136-
script[self.json_field] = str(value)
117+
script[self.json_field] = str(value)
137118

138119
return script
139120

@@ -209,7 +190,7 @@ class InvalidBefore(NativeScript):
209190
json_field: ClassVar[str] = "slot"
210191
_TYPE: int = field(default=4, init=False)
211192

212-
before: int = None
193+
before: int
213194

214195

215196
@dataclass
@@ -218,4 +199,14 @@ class InvalidHereAfter(NativeScript):
218199
json_field: ClassVar[str] = "slot"
219200
_TYPE: int = field(default=5, init=False)
220201

221-
after: int = None
202+
after: int
203+
204+
205+
JSON_TAG_TO_INT = {
206+
ScriptPubkey.json_tag: ScriptPubkey._TYPE,
207+
ScriptAll.json_tag: ScriptAll._TYPE,
208+
ScriptAny.json_tag: ScriptAny._TYPE,
209+
ScriptNofK.json_tag: ScriptNofK._TYPE,
210+
InvalidBefore.json_tag: InvalidBefore._TYPE,
211+
InvalidHereAfter.json_tag: InvalidHereAfter._TYPE,
212+
}

pyproject.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,6 @@ exclude = [
7575
'^pycardano/key.py$',
7676
'^pycardano/logging.py$',
7777
'^pycardano/metadata.py$',
78-
'^pycardano/nativescript.py$',
7978
'^pycardano/plutus.py$',
8079
'^pycardano/transaction.py$',
8180
'^pycardano/txbuilder.py$',

test/pycardano/test_nativescript.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import pytest
44

5-
from pycardano.exception import InvalidArgumentException
5+
from pycardano.exception import DeserializeException, InvalidArgumentException
66
from pycardano.key import VerificationKey
77
from pycardano.nativescript import (
88
InvalidBefore,
@@ -163,6 +163,11 @@ def test_to_dict():
163163
assert NativeScript.from_dict(script_dict) == script_nofk
164164

165165

166+
def test_from_primitive_invalid_primitive_input():
167+
with pytest.raises(DeserializeException):
168+
NativeScript.from_primitive(1)
169+
170+
166171
def test_from_dict():
167172

168173
vk1 = VerificationKey.from_cbor(

0 commit comments

Comments
 (0)