From 74a25010f54ce4437dbcdac8108cc009965ac013 Mon Sep 17 00:00:00 2001 From: Daehan Kim Date: Sat, 12 Nov 2022 15:27:40 -0800 Subject: [PATCH 1/7] UPDATE. including nativescript.py module in mypy test --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 0c033860..06a17a79 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -75,7 +75,6 @@ exclude = [ '^pycardano/key.py$', '^pycardano/logging.py$', '^pycardano/metadata.py$', - '^pycardano/nativescript.py$', '^pycardano/plutus.py$', '^pycardano/transaction.py$', '^pycardano/txbuilder.py$', From a26aaeeaf1d91591ae6c342e1bec656f7e63928b Mon Sep 17 00:00:00 2001 From: Daehan Kim Date: Sat, 12 Nov 2022 15:37:10 -0800 Subject: [PATCH 2/7] UPDATE. ensure NativeScript.from_primitive() only handles list inputs UPDATE. improve to_dict() type hint UPDATE. improve to_dict() conditional readability --- pycardano/nativescript.py | 19 ++++++++++--------- test/pycardano/test_nativescript.py | 6 ++++++ 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/pycardano/nativescript.py b/pycardano/nativescript.py index c1694d73..d2b6e6b4 100644 --- a/pycardano/nativescript.py +++ b/pycardano/nativescript.py @@ -35,6 +35,11 @@ def from_primitive( ) -> Union[ ScriptPubkey, ScriptAll, ScriptAny, ScriptNofK, InvalidBefore, InvalidHereAfter ]: + if not isinstance(value, (list, tuple,)): + raise DeserializeException( + f"A list or a tuple is required for deserialization: {str(value)}" + ) + script_type = value[0] for t in [ ScriptPubkey, @@ -118,22 +123,18 @@ def _script_jsons_to_primitive( native_script = [cls._script_json_to_primitive(i) for i in script_jsons] return native_script - def to_dict(self) -> dict: + def to_dict(self) -> JsonDict: """Export to standard native script dictionary (potentially to dump to a JSON file).""" - - script = {} - + script: JsonDict = {} for value in self.__dict__.values(): script["type"] = self.json_tag if isinstance(value, list): script["scripts"] = [i.to_dict() for i in value] - + elif isinstance(value, int): + script[self.json_field] = value else: - if isinstance(value, int): - script[self.json_field] = value - else: - script[self.json_field] = str(value) + script[self.json_field] = str(value) return script diff --git a/test/pycardano/test_nativescript.py b/test/pycardano/test_nativescript.py index 53c9dd16..8546eec1 100644 --- a/test/pycardano/test_nativescript.py +++ b/test/pycardano/test_nativescript.py @@ -14,6 +14,7 @@ ScriptPubkey, ) from pycardano.transaction import Transaction +from pycardano.exception import DeserializeException """The following ground truths of script hashes (policy ID) are generated from cardano-cli.""" @@ -163,6 +164,11 @@ def test_to_dict(): assert NativeScript.from_dict(script_dict) == script_nofk +def test_from_primitive_invalid_primitive_input(): + with pytest.raises(DeserializeException): + NativeScript.from_primitive(1) + + def test_from_dict(): vk1 = VerificationKey.from_cbor( From 3c4e8e51f38f5040343990d73e1951fe3a878e32 Mon Sep 17 00:00:00 2001 From: Daehan Kim Date: Sat, 12 Nov 2022 15:38:34 -0800 Subject: [PATCH 3/7] UPDATE. InvalidBefore and InvalidHereAfter should not have optional before and after fields --- pycardano/nativescript.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pycardano/nativescript.py b/pycardano/nativescript.py index d2b6e6b4..dbcf41b6 100644 --- a/pycardano/nativescript.py +++ b/pycardano/nativescript.py @@ -210,7 +210,7 @@ class InvalidBefore(NativeScript): json_field: ClassVar[str] = "slot" _TYPE: int = field(default=4, init=False) - before: int = None + before: int @dataclass @@ -219,4 +219,4 @@ class InvalidHereAfter(NativeScript): json_field: ClassVar[str] = "slot" _TYPE: int = field(default=5, init=False) - after: int = None + after: int From d5a48529f126ad1a140e77dcad6652ee6be9725c Mon Sep 17 00:00:00 2001 From: Daehan Kim Date: Sat, 12 Nov 2022 15:50:05 -0800 Subject: [PATCH 4/7] UPDATE. letting mypy know to_cbor() value is bytes --- pycardano/nativescript.py | 15 ++++++++++----- test/pycardano/test_nativescript.py | 3 +-- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/pycardano/nativescript.py b/pycardano/nativescript.py index dbcf41b6..de987174 100644 --- a/pycardano/nativescript.py +++ b/pycardano/nativescript.py @@ -3,7 +3,7 @@ from __future__ import annotations from dataclasses import dataclass, field -from typing import ClassVar, List, Type, Union +from typing import ClassVar, List, Type, Union, cast from nacl.encoding import RawEncoder from nacl.hash import blake2b @@ -35,7 +35,13 @@ def from_primitive( ) -> Union[ ScriptPubkey, ScriptAll, ScriptAny, ScriptNofK, InvalidBefore, InvalidHereAfter ]: - if not isinstance(value, (list, tuple,)): + if not isinstance( + value, + ( + list, + tuple, + ), + ): raise DeserializeException( f"A list or a tuple is required for deserialization: {str(value)}" ) @@ -55,10 +61,9 @@ def from_primitive( raise DeserializeException(f"Unknown script type indicator: {script_type}") def hash(self) -> ScriptHash: + cbor_bytes = cast(bytes, self.to_cbor("bytes")) return ScriptHash( - blake2b( - bytes(1) + self.to_cbor("bytes"), SCRIPT_HASH_SIZE, encoder=RawEncoder - ) + blake2b(bytes(1) + cbor_bytes, SCRIPT_HASH_SIZE, encoder=RawEncoder) ) @classmethod diff --git a/test/pycardano/test_nativescript.py b/test/pycardano/test_nativescript.py index 8546eec1..83b2ecdd 100644 --- a/test/pycardano/test_nativescript.py +++ b/test/pycardano/test_nativescript.py @@ -2,7 +2,7 @@ import pytest -from pycardano.exception import InvalidArgumentException +from pycardano.exception import DeserializeException, InvalidArgumentException from pycardano.key import VerificationKey from pycardano.nativescript import ( InvalidBefore, @@ -14,7 +14,6 @@ ScriptPubkey, ) from pycardano.transaction import Transaction -from pycardano.exception import DeserializeException """The following ground truths of script hashes (policy ID) are generated from cardano-cli.""" From 47a6aac13ea931784752994784e93d2d4570bbf9 Mon Sep 17 00:00:00 2001 From: Daehan Kim Date: Sat, 12 Nov 2022 20:59:12 -0800 Subject: [PATCH 5/7] UPDATE. mypy cannot detect common attributes in a list of classes --- pycardano/nativescript.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pycardano/nativescript.py b/pycardano/nativescript.py index de987174..d2a56792 100644 --- a/pycardano/nativescript.py +++ b/pycardano/nativescript.py @@ -46,7 +46,7 @@ def from_primitive( f"A list or a tuple is required for deserialization: {str(value)}" ) - script_type = value[0] + script_type: int = value[0] for t in [ ScriptPubkey, ScriptAll, @@ -55,7 +55,7 @@ def from_primitive( InvalidBefore, InvalidHereAfter, ]: - if t._TYPE == script_type: + if t._TYPE == script_type: # type: ignore return super(NativeScript, t).from_primitive(value[1:]) else: raise DeserializeException(f"Unknown script type indicator: {script_type}") @@ -75,7 +75,7 @@ def from_dict( """Parse a standard native script dictionary (potentially parsed from a JSON file).""" types = { - p.json_tag: p + p.json_tag: p # type: ignore for p in [ ScriptPubkey, ScriptAll, @@ -97,7 +97,7 @@ def _script_json_to_primitive( """Serialize a standard JSON native script into a primitive array""" types = { - p.json_tag: p + p.json_tag: p # type: ignore for p in [ ScriptPubkey, ScriptAll, @@ -109,7 +109,7 @@ def _script_json_to_primitive( } script_type: str = script_json["type"] - native_script = [types[script_type]._TYPE] + native_script: List[Primitive] = [types[script_type]._TYPE] # type: ignore for key, value in script_json.items(): if key == "type": From 6919848b94d274b0314ba404d67a8ab93015f006 Mon Sep 17 00:00:00 2001 From: Daehan Kim Date: Sat, 12 Nov 2022 21:04:51 -0800 Subject: [PATCH 6/7] UPDATE. mypy cannot infer the second argument of super() is NativeScript class' subclass --- pycardano/nativescript.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pycardano/nativescript.py b/pycardano/nativescript.py index d2a56792..68ccb746 100644 --- a/pycardano/nativescript.py +++ b/pycardano/nativescript.py @@ -56,7 +56,7 @@ def from_primitive( InvalidHereAfter, ]: if t._TYPE == script_type: # type: ignore - return super(NativeScript, t).from_primitive(value[1:]) + return super(NativeScript, t).from_primitive(value[1:]) # type: ignore else: raise DeserializeException(f"Unknown script type indicator: {script_type}") @@ -88,7 +88,7 @@ def from_dict( script_type = script_json["type"] target_class = types[script_type] script_primitive = cls._script_json_to_primitive(script_json) - return super(NativeScript, target_class).from_primitive(script_primitive[1:]) + return super(NativeScript, target_class).from_primitive(script_primitive[1:]) # type: ignore @classmethod def _script_json_to_primitive( From 7faa94bfd5a6258038789c09fad9610d10738c26 Mon Sep 17 00:00:00 2001 From: Daehan Kim Date: Sun, 13 Nov 2022 00:56:37 -0800 Subject: [PATCH 7/7] UPDATE. explicitly assigning NativeScript subclass instantiation type by individual conditional check UPDATE. simplify from_dict() method UPDATE. create a JSON_TAG_TO_INT map --- pycardano/nativescript.py | 63 +++++++++++++++------------------------ 1 file changed, 24 insertions(+), 39 deletions(-) diff --git a/pycardano/nativescript.py b/pycardano/nativescript.py index 68ccb746..4ea29ace 100644 --- a/pycardano/nativescript.py +++ b/pycardano/nativescript.py @@ -47,16 +47,18 @@ def from_primitive( ) script_type: int = value[0] - for t in [ - ScriptPubkey, - ScriptAll, - ScriptAny, - ScriptNofK, - InvalidBefore, - InvalidHereAfter, - ]: - if t._TYPE == script_type: # type: ignore - return super(NativeScript, t).from_primitive(value[1:]) # type: ignore + if script_type == ScriptPubkey._TYPE: + return super(NativeScript, ScriptPubkey).from_primitive(value[1:]) + elif script_type == ScriptAll._TYPE: + return super(NativeScript, ScriptAll).from_primitive(value[1:]) + elif script_type == ScriptAny._TYPE: + return super(NativeScript, ScriptAny).from_primitive(value[1:]) + elif script_type == ScriptNofK._TYPE: + return super(NativeScript, ScriptNofK).from_primitive(value[1:]) + elif script_type == InvalidBefore._TYPE: + return super(NativeScript, InvalidBefore).from_primitive(value[1:]) + elif script_type == InvalidHereAfter._TYPE: + return super(NativeScript, InvalidHereAfter).from_primitive(value[1:]) else: raise DeserializeException(f"Unknown script type indicator: {script_type}") @@ -73,43 +75,16 @@ def from_dict( ScriptPubkey, ScriptAll, ScriptAny, ScriptNofK, InvalidBefore, InvalidHereAfter ]: """Parse a standard native script dictionary (potentially parsed from a JSON file).""" - - types = { - p.json_tag: p # type: ignore - for p in [ - ScriptPubkey, - ScriptAll, - ScriptAny, - ScriptNofK, - InvalidBefore, - InvalidHereAfter, - ] - } - script_type = script_json["type"] - target_class = types[script_type] script_primitive = cls._script_json_to_primitive(script_json) - return super(NativeScript, target_class).from_primitive(script_primitive[1:]) # type: ignore + return cls.from_primitive(script_primitive) @classmethod def _script_json_to_primitive( cls: Type[NativeScript], script_json: JsonDict ) -> List[Primitive]: """Serialize a standard JSON native script into a primitive array""" - - types = { - p.json_tag: p # type: ignore - for p in [ - ScriptPubkey, - ScriptAll, - ScriptAny, - ScriptNofK, - InvalidBefore, - InvalidHereAfter, - ] - } - script_type: str = script_json["type"] - native_script: List[Primitive] = [types[script_type]._TYPE] # type: ignore + native_script: List[Primitive] = [JSON_TAG_TO_INT[script_type]] for key, value in script_json.items(): if key == "type": @@ -225,3 +200,13 @@ class InvalidHereAfter(NativeScript): _TYPE: int = field(default=5, init=False) after: int + + +JSON_TAG_TO_INT = { + ScriptPubkey.json_tag: ScriptPubkey._TYPE, + ScriptAll.json_tag: ScriptAll._TYPE, + ScriptAny.json_tag: ScriptAny._TYPE, + ScriptNofK.json_tag: ScriptNofK._TYPE, + InvalidBefore.json_tag: InvalidBefore._TYPE, + InvalidHereAfter.json_tag: InvalidHereAfter._TYPE, +}