mirror of
https://github.com/ethereum/consensus-specs.git
synced 2026-02-02 14:45:04 -05:00
typing improvements, type testing
This commit is contained in:
@@ -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__
|
||||
|
||||
131
test_libs/pyspec/eth2spec/utils/ssz/test_ssz_typing.py
Normal file
131
test_libs/pyspec/eth2spec/utils/ssz/test_ssz_typing.py
Normal 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)
|
||||
Reference in New Issue
Block a user