typing improvements, type testing

This commit is contained in:
protolambda
2019-06-16 03:21:58 +02:00
parent 97025c51ac
commit 08e6f32f38
2 changed files with 147 additions and 5 deletions

View File

@@ -36,6 +36,11 @@ class BasicValue(int, SSZValue, metaclass=BasicType):
class Bit(BasicValue): # can't subclass bool.
def __new__(cls, value, *args, **kwargs):
if value < 0 or value > 1:
raise ValueError(f"value {value} out of bounds for bit")
return super().__new__(cls, value)
@classmethod
def default(cls):
return cls(False)
@@ -49,7 +54,7 @@ class uint(BasicValue, metaclass=BasicType):
def __new__(cls, value, *args, **kwargs):
if value < 0:
raise ValueError("unsigned types must not be negative")
if cls.byte_len and (value.bit_length() >> 3) > cls.byte_len:
if cls.byte_len and value.bit_length() > (cls.byte_len << 3):
raise ValueError("value out of bounds for uint{}".format(cls.byte_len))
return super().__new__(cls, value)
@@ -142,7 +147,7 @@ class Container(Series, metaclass=SSZType):
@classmethod
def get_fields(cls) -> Tuple[Tuple[str, SSZType], ...]:
return tuple((f, SSZType(t)) for f, t in dict(cls.__annotations__).items())
return tuple((f, t) for f, t in cls.__annotations__.items())
def get_typed_values(self):
return tuple(zip(self.get_field_values(), self.get_field_types()))
@@ -190,6 +195,12 @@ class ParamsMeta(SSZType):
o._bare = False
return o
def __str__(self):
return f"{self.__name__}~{self.__class__.__name__}"
def __repr__(self):
return self, self.__class__
def attr_from_params(self, p):
# single key params are valid too. Wrap them in a tuple.
params = p if isinstance(p, tuple) else (p,)
@@ -215,7 +226,7 @@ class ParamsMeta(SSZType):
def __instancecheck__(self, obj):
if obj.__class__.__name__ != self.__name__:
return False
for name, typ in self.__annotations__:
for name, typ in self.__annotations__.items():
if hasattr(self, name) and hasattr(obj.__class__, name) \
and getattr(obj.__class__, name) != getattr(self, name):
return False
@@ -233,7 +244,7 @@ class ElementsBase(ParamsBase, metaclass=Elements):
items = self.extract_args(*args)
if not self.value_check(items):
raise ValueCheckError("Bad input for class {}: {}".format(self.__class__, items))
raise ValueCheckError(f"Bad input for class {self.__class__}: {items}")
self.items = items
@classmethod
@@ -245,7 +256,7 @@ class ElementsBase(ParamsBase, metaclass=Elements):
x = list(args)
if len(x) == 1 and isinstance(x[0], GeneratorType):
x = list(x[0])
return x if len(x) > 0 else cls.default()
return x
def __str__(self):
cls = self.__class__

View File

@@ -0,0 +1,131 @@
from .ssz_typing import (
SSZValue, SSZType, BasicValue, BasicType, Series, Elements, Bit, Container, List, Vector, Bytes, BytesN,
uint, uint8, uint16, uint32, uint64, uint128, uint256
)
def test_subclasses():
for u in [uint, uint8, uint16, uint32, uint64, uint128, uint256]:
assert issubclass(u, uint)
assert issubclass(u, int)
assert issubclass(u, BasicValue)
assert issubclass(u, SSZValue)
assert isinstance(u, SSZType)
assert isinstance(u, BasicType)
assert issubclass(Bit, BasicValue)
assert isinstance(Bit, BasicType)
for c in [Container, List, Vector, Bytes, BytesN]:
assert issubclass(c, Series)
assert issubclass(c, SSZValue)
assert isinstance(c, SSZType)
assert not issubclass(c, BasicValue)
assert not isinstance(c, BasicType)
for c in [List, Vector, Bytes, BytesN]:
assert isinstance(c, Elements)
def test_basic_instances():
for u in [uint, uint8, uint16, uint32, uint64, uint128, uint256]:
v = u(123)
assert isinstance(v, uint)
assert isinstance(v, int)
assert isinstance(v, BasicValue)
assert isinstance(v, SSZValue)
assert isinstance(Bit(True), BasicValue)
assert isinstance(Bit(False), BasicValue)
def test_basic_value_bounds():
max = {
Bit: 2**1,
uint8: 2**(8 * 1),
uint16: 2**(8 * 2),
uint32: 2**(8 * 4),
uint64: 2**(8 * 8),
uint128: 2**(8 * 16),
uint256: 2**(8 * 32),
}
for k, v in max.items():
# this should work
assert k(v - 1) == v - 1
# but we do not allow overflows
try:
k(v)
assert False
except ValueError:
pass
for k, _ in max.items():
# this should work
assert k(0) == 0
# but we do not allow underflows
try:
k(-1)
assert False
except ValueError:
pass
def test_container():
class Foo(Container):
a: uint8
b: uint32
assert issubclass(Foo, Container)
assert issubclass(Foo, SSZValue)
assert issubclass(Foo, Series)
assert Foo.is_fixed_size()
x = Foo(a=uint8(123), b=uint32(45))
assert x.a == 123
assert x.b == 45
assert isinstance(x.a, uint8)
assert isinstance(x.b, uint32)
assert x.type().is_fixed_size()
class Bar(Container):
a: uint8
b: List[uint8, 1024]
assert not Bar.is_fixed_size()
y = Bar(a=uint8(123), b=List[uint8, 1024](uint8(1), uint8(2)))
assert y.a == 123
assert len(y.b) == 2
assert isinstance(y.a, uint8)
assert isinstance(y.b, List[uint8, 1024])
assert not y.type().is_fixed_size()
assert y.b[0] == 1
v: List = y.b
assert v.type().elem_type == uint8
assert v.type().length == 1024
def test_list():
typ = List[uint64, 128]
assert issubclass(typ, List)
assert issubclass(typ, SSZValue)
assert issubclass(typ, Series)
assert isinstance(typ, Elements)
assert not typ.is_fixed_size()
assert len(typ()) == 0 # empty
assert len(typ(uint64(0))) == 1 # single arg
assert len(typ(uint64(i) for i in range(10))) == 10 # generator
assert len(typ(uint64(0), uint64(1), uint64(2))) == 3 # args
v = typ(uint64(0))
v[0] = uint64(123)
assert v[0] == 123
assert isinstance(v[0], uint64)
assert isinstance(v, List)
assert isinstance(v, List[uint64, 128])
assert isinstance(v, typ)
assert isinstance(v, SSZValue)
assert isinstance(v, Series)
assert isinstance(v.type(), Elements)