Skip to content

Commit

Permalink
Merge pull request #11 from ajrgrubbs/container-access-support
Browse files Browse the repository at this point in the history
Support dict-style value access
  • Loading branch information
ajrgrubbs authored Jun 13, 2019
2 parents 34e87a6 + 8c90d67 commit 2ae3c3a
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 0 deletions.
39 changes: 39 additions & 0 deletions eip712_structs/struct.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,45 @@ def from_message(cls, message_dict: dict) -> 'StructTuple':

return result

@classmethod
def _assert_key_is_member(cls, key):
member_names = {tup[0] for tup in cls.get_members()}
if key not in member_names:
raise KeyError(f'"{key}" is not defined for this struct.')

@classmethod
def _assert_property_type(cls, key, value):
"""Eagerly check for a correct member type"""
members = dict(cls.get_members())
typ = members[key]

if isinstance(typ, type) and issubclass(typ, EIP712Struct):
# We expect an EIP712Struct instance. Assert that's true, and check the struct signature too.
if not isinstance(value, EIP712Struct) or value._encode_type(False) != typ._encode_type(False):
raise ValueError(f'Given value is of type {type(value)}, but we expected {typ}')
else:
# Since it isn't a nested struct, its an EIP712Type
try:
typ.encode_value(value)
except Exception as e:
raise ValueError(f'The python type {type(value)} does not appear '
f'to be supported for data type {typ}.') from e

def __getitem__(self, key):
"""Provide access directly to the underlying value dictionary"""
self._assert_key_is_member(key)
return self.values.__getitem__(key)

def __setitem__(self, key, value):
"""Provide access directly to the underlying value dictionary"""
self._assert_key_is_member(key)
self._assert_property_type(key, value)

return self.values.__setitem__(key, value)

def __delitem__(self, _):
raise TypeError('Deleting entries from an EIP712Struct is not allowed.')


class StructTuple(NamedTuple):
message: EIP712Struct
Expand Down
56 changes: 56 additions & 0 deletions tests/test_encode_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,3 +187,59 @@ def test_validation_errors():
bool_type.encode_value(0)
with pytest.raises(ValueError, match='Must be True or False.'):
bool_type.encode_value(1)


def test_value_access():
class Foo(EIP712Struct):
s = String()
b = Bytes(32)

test_str = 'hello world'
test_bytes = os.urandom(32)
foo = Foo(s=test_str, b=test_bytes)

assert foo['s'] == test_str
assert foo['b'] == test_bytes

test_bytes_2 = os.urandom(32)
foo['b'] = test_bytes_2

assert foo['b'] == test_bytes_2

with pytest.raises(KeyError):
foo['x'] = 'unacceptable'

# Check behavior when accessing a member that wasn't defined for the struct.
with pytest.raises(KeyError):
foo['x']
# Lets cheat a lil bit for robustness- add an invalid 'x' member to the value dict, and check the error still raises
foo.values['x'] = 'test'
with pytest.raises(KeyError):
foo['x']
foo.values.pop('x')

with pytest.raises(ValueError):
foo['s'] = b'unacceptable'
with pytest.raises(ValueError):
# Bytes do accept strings, but it has to be hex formatted.
foo['b'] = 'unacceptable'

# Test behavior when attempting to set nested structs as values
class Bar(EIP712Struct):
s = String()
f = Foo

class Baz(EIP712Struct):
s = String()
baz = Baz(s=test_str)

bar = Bar(s=test_str)
bar['f'] = foo
assert bar['f'] == foo

with pytest.raises(ValueError):
# Expects a Foo type, so should throw an error
bar['f'] = baz

with pytest.raises(TypeError):
del foo['s']

0 comments on commit 2ae3c3a

Please sign in to comment.