Skip to content

Attempt to improve liskov substitution principle error #128

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Nov 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 79 additions & 72 deletions poetry.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pycardano/backend/blockfrost.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class BlockFrostChainContext(ChainContext):
_protocol_param: Optional[ProtocolParameters] = None

def __init__(
self, project_id: str, network: Network = Network.TESTNET, base_url: str = None
self, project_id: str, network: Network = Network.TESTNET, base_url: str = ""
):
self._network = network
self._project_id = project_id
Expand Down
9 changes: 7 additions & 2 deletions pycardano/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
from enum import Enum
from typing import Type

from pycardano.serialization import CBORSerializable
from pycardano.exception import DeserializeException
from pycardano.serialization import CBORSerializable, Primitive

__all__ = ["Network"]

Expand All @@ -22,5 +23,9 @@ def to_primitive(self) -> int:
return self.value

@classmethod
def from_primitive(cls: Type[Network], value: int) -> Network:
def from_primitive(cls: Type[Network], value: Primitive) -> Network:
if not isinstance(value, int):
raise DeserializeException(
f"An integer value is required for deserialization: {str(value)}"
)
return cls(value)
57 changes: 41 additions & 16 deletions pycardano/serialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from datetime import datetime
from decimal import Decimal
from inspect import isclass
from typing import Any, Callable, ClassVar, List, Type, TypeVar, Union, get_type_hints
from typing import Any, Callable, List, Type, TypeVar, Union, get_type_hints

from cbor2 import CBOREncoder, CBORSimpleValue, CBORTag, dumps, loads, undefined
from pprintpp import pformat
Expand Down Expand Up @@ -53,8 +53,31 @@ class RawCBOR:
cbor: bytes


Primitive = TypeVar(
"Primitive",
Primitive = Union[
bytes,
bytearray,
str,
int,
float,
Decimal,
bool,
None,
tuple,
list,
IndefiniteList,
dict,
defaultdict,
OrderedDict,
undefined.__class__,
datetime,
re.Pattern,
CBORSimpleValue,
CBORTag,
set,
frozenset,
]

PRIMITIVE_TYPES = (
bytes,
bytearray,
str,
Expand Down Expand Up @@ -381,10 +404,10 @@ def _restore_dataclass_field(
return t.from_primitive(v)
except DeserializeException:
pass
elif t in Primitive.__constraints__ and isinstance(v, t):
elif t in PRIMITIVE_TYPES and isinstance(v, t):
return v
raise DeserializeException(
f"Cannot deserialize object: \n{v}\n in any valid type from {t_args}."
f"Cannot deserialize object: \n{str(v)}\n in any valid type from {t_args}."
)
return v

Expand Down Expand Up @@ -453,8 +476,6 @@ class ArrayCBORSerializable(CBORSerializable):
Test2(c='c', test1=Test1(a='a', b=None))
"""

field_sorter: ClassVar[Callable[[List], List]] = lambda x: x

def to_shallow_primitive(self) -> List[Primitive]:
"""
Returns:
Expand All @@ -465,15 +486,15 @@ def to_shallow_primitive(self) -> List[Primitive]:
types.
"""
primitives = []
for f in self.__class__.field_sorter(fields(self)):
for f in fields(self):
val = getattr(self, f.name)
if val is None and f.metadata.get("optional"):
continue
primitives.append(val)
return primitives

@classmethod
def from_primitive(cls: Type[ArrayBase], values: List[Primitive]) -> ArrayBase:
def from_primitive(cls: Type[ArrayBase], values: Primitive) -> ArrayBase:
"""Restore a primitive value to its original class type.

Args:
Expand Down Expand Up @@ -660,7 +681,7 @@ def __init__(self, *args, **kwargs):
def __getattr__(self, item):
return getattr(self.data, item)

def __setitem__(self, key: KEY_TYPE, value: VALUE_TYPE):
def __setitem__(self, key: Any, value: Any):
check_type("key", key, self.KEY_TYPE)
check_type("value", value, self.VALUE_TYPE)
self.data[key] = value
Expand Down Expand Up @@ -704,7 +725,7 @@ def _get_sortable_val(key):
return dict(sorted(self.data.items(), key=lambda x: _get_sortable_val(x[0])))

@classmethod
def from_primitive(cls: Type[DictBase], value: dict) -> DictBase:
def from_primitive(cls: Type[DictBase], value: Primitive) -> DictBase:
"""Restore a primitive value to its original class type.

Args:
Expand All @@ -718,13 +739,17 @@ def from_primitive(cls: Type[DictBase], value: dict) -> DictBase:
DeserializeException: When the object could not be restored from primitives.
"""
if not value:
raise DeserializeException(f"Cannot accept empty value {value}.")
raise DeserializeException(f"Cannot accept empty value {str(value)}.")
if not isinstance(value, dict):
raise DeserializeException(
f"A dictionary value is required for deserialization: {str(value)}"
)

restored = cls()
for k, v in value.items():
k = (
cls.KEY_TYPE.from_primitive(k)
if isclass(cls.VALUE_TYPE)
and issubclass(cls.KEY_TYPE, CBORSerializable)
if isclass(cls.KEY_TYPE) and issubclass(cls.KEY_TYPE, CBORSerializable)
else k
)
v = (
Expand All @@ -736,13 +761,13 @@ def from_primitive(cls: Type[DictBase], value: dict) -> DictBase:
restored[k] = v
return restored

def copy(self) -> DictBase:
def copy(self) -> DictCBORSerializable:
return self.__class__(self)


@typechecked
def list_hook(
cls: Type[CBORSerializable],
cls: Type[CBORBase],
) -> Callable[[List[Primitive]], List[CBORBase]]:
"""A factory that generates a Callable which turns a list of Primitive to a list of CBORSerializables.

Expand Down
4 changes: 1 addition & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ sphinx-copybutton = "^0.5.0"
retry = "^0.9.2"
Flask = "^2.0.3"
pytest-xdist = "^3.0.2"
mypy = "^0.982"
mypy = "^0.990"

[build-system]
requires = ["poetry-core>=1.0.0"]
Expand Down Expand Up @@ -76,9 +76,7 @@ exclude = [
'^pycardano/logging.py$',
'^pycardano/metadata.py$',
'^pycardano/nativescript.py$',
'^pycardano/network.py$',
'^pycardano/plutus.py$',
'^pycardano/serialization.py$',
'^pycardano/transaction.py$',
'^pycardano/txbuilder.py$',
'^pycardano/utils.py$',
Expand Down
32 changes: 32 additions & 0 deletions test/pycardano/test_network.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import pytest

from pycardano.exception import DeserializeException
from pycardano.network import Network


def test_from_primitive_invalid_primitive_input():
value = "a string value"
with pytest.raises(DeserializeException):
Network.from_primitive(value)


def test_from_primitive_testnet():
testnet_value = 0
network = Network.from_primitive(testnet_value)
assert network.value == testnet_value


def test_from_primitive_mainnet():
mainnet_value = 1
network = Network.from_primitive(mainnet_value)
assert network.value == mainnet_value


def test_to_primitive_testnet():
network = Network(0)
assert network.to_primitive() == 0


def test_to_primitive_mainnet():
network = Network(1)
assert network.to_primitive() == 1