Skip to content

Commit

Permalink
Merge pull request #76 from davesque/callable-fixes
Browse files Browse the repository at this point in the history
Fix support for callable coders
  • Loading branch information
davesque authored Jun 5, 2018
2 parents 344be36 + d634eb1 commit cb50c08
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 8 deletions.
9 changes: 7 additions & 2 deletions eth_abi/abi.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,13 @@ def is_encodable(typ, arg):
encoder.validate_value(arg)
except EncodingError:
return False
else:
return True
except AttributeError:
try:
encoder(arg)
except EncodingError:
return False

return True


# Decodes a single base datum
Expand Down
2 changes: 1 addition & 1 deletion eth_abi/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,4 +144,4 @@ def from_type_str(cls, type_str, registry): # pragma: no cover
Used by ``ABIRegistry`` to get an appropriate encoder or decoder
instance for the given type string and type registry.
"""
raise NotImplementedError('Must implement `from_type_str`')
raise cls()
4 changes: 2 additions & 2 deletions eth_abi/decoding.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,11 +145,11 @@ def __init__(self, **kwargs):
super().__init__(**kwargs)

self.decoders = tuple(
HeadTailDecoder(tail_decoder=d) if d.is_dynamic else d
HeadTailDecoder(tail_decoder=d) if getattr(d, 'is_dynamic', False) else d
for d in self.decoders
)

self.is_dynamic = any(d.is_dynamic for d in self.decoders)
self.is_dynamic = any(getattr(d, 'is_dynamic', False) for d in self.decoders)

def validate(self):
super().validate()
Expand Down
9 changes: 6 additions & 3 deletions eth_abi/encoding.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ class TupleEncoder(BaseEncoder):
def __init__(self, **kwargs):
super().__init__(**kwargs)

self.is_dynamic = any(e.is_dynamic for e in self.encoders)
self.is_dynamic = any(getattr(e, 'is_dynamic', False) for e in self.encoders)

def validate(self):
super().validate()
Expand All @@ -95,15 +95,18 @@ def validate_value(self, value):
)

for item, encoder in zip(value, self.encoders):
encoder.validate_value(item)
try:
encoder.validate_value(item)
except AttributeError:
encoder(item)

def encode(self, values):
self.validate_value(values)

raw_head_chunks = []
tail_chunks = []
for value, encoder in zip(values, self.encoders):
if encoder.is_dynamic:
if getattr(encoder, 'is_dynamic', False):
raw_head_chunks.append(None)
tail_chunks.append(encoder(value))
else:
Expand Down
Empty file.
99 changes: 99 additions & 0 deletions tests/test_integration/test_custom_registrations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
from eth_abi.abi import (
decode_single,
encode_single,
)
from eth_abi.decoding import (
BaseDecoder,
)
from eth_abi.encoding import (
BaseEncoder,
)
from eth_abi.exceptions import (
DecodingError,
EncodingError,
)
from eth_abi.registry import (
registry,
)

NULL_ENCODING = b'\x00' * 32


def encode_null(x):
if x is not None:
raise EncodingError('Unsupported value')

return NULL_ENCODING


def decode_null(stream):
if stream.read(32) != NULL_ENCODING:
raise DecodingError('Not enough data or wrong data')

return None


class EncodeNull(BaseEncoder):
word_width = None

@classmethod
def from_type_str(cls, type_str, registry):
word_width = int(type_str[4:])
return cls(word_width=word_width)

def encode(self, value):
self.validate_value(value)
return NULL_ENCODING * self.word_width

def validate_value(self, value):
if value is not None:
raise EncodingError('Unsupported value')


class DecodeNull(BaseDecoder):
word_width = None

@classmethod
def from_type_str(cls, type_str, registry):
word_width = int(type_str[4:])
return cls(word_width=word_width)

def decode(self, stream):
byts = stream.read(32 * self.word_width)
if byts != NULL_ENCODING * self.word_width:
raise DecodingError('Not enough data or wrong data')

return None


def test_register_and_use_callables():
registry.register('null', encode_null, decode_null)

assert encode_single('null', None) == NULL_ENCODING
assert decode_single('null', NULL_ENCODING) is None

encoded_tuple = encode_single('(int,null)', (1, None))

assert encoded_tuple == b'\x00' * 31 + b'\x01' + NULL_ENCODING
assert decode_single('(int,null)', encoded_tuple) == (1, None)

registry.unregister('null')


def test_register_and_use_coder_classes():
registry.register(
lambda x: x.startswith('null'),
EncodeNull,
DecodeNull,
label='null',
)

assert encode_single('null2', None) == NULL_ENCODING * 2
assert decode_single('null2', NULL_ENCODING * 2) is None

encoded_tuple = encode_single('(int,null2)', (1, None))

assert encoded_tuple == b'\x00' * 31 + b'\x01' + NULL_ENCODING * 2
assert decode_single('(int,null2)', encoded_tuple) == (1, None)

registry.unregister('null')

0 comments on commit cb50c08

Please sign in to comment.