-
Notifications
You must be signed in to change notification settings - Fork 269
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
Add support for providing custom registries #109
Conversation
Hey, Stefan. Thanks for your work on this. Some of our team members had a previous discussion about fixing this issue. In that discussion, we actually decided that the best path forward would be to convert |
@davesque okay, sounds good! Since that's a non-passive change, is the expectation that we would mark the |
The methods could still exist in the same location. It's just that, instead of being stand-alone functions, they would be references to bound methods on a class instance. For example, you could end up doing something like this: # eth_abi/abi.py
from .registry import registry
encode_single = registry.encode_single
encode_abi = registry.encode_abi
... However, I'm realizing now that this doesn't really solve the issue of how web3.py or any other external consumer of |
@davesque one thing @carver and I were discussing yesterday was potentially having a Codec class and passing that around as naming wise it makes more sense, you're not overloading the registry's purpose, and you're passing around the instance you're actually calling methods on (vs. this initial design I had in here where we were passing the registry to every method). See my comment here and his comment below: |
Why did you decide to implement just the encoder instead of the full |
Ugh, that's my bad. Typo. I moved the Line 99 in 90aba57
Line 119 in 90aba57
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Feel free to @ mention me again when ready for next review.
eth_abi/__init__.py
Outdated
# encode_single, | ||
# encode_abi, | ||
# is_encodable, | ||
# ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's still okay to expose these as convenience methods for at-console quicky encoding/decoding with the default registry.
Nevermind, but it should import codec
and the class (there are several docs to update, too). Something like:
from eth_abi.abi import (
ABICodec,
default_codec as codec,
)
This shortens to codec for super short clean examples in the docs, but default_codec
is a more complete/accurate name for internal usage, like the tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ended up defining the default_codec in the abi module as well as exposing the convenience methods per @davesque's comment here:
#109 (review)
eth_abi/abi.py
Outdated
encoding e.g. ``'uint256'``, ``'bytes[]'``, ``'(int,int)'``, etc. | ||
:param arg: The python value to be encoded. | ||
def __init__(self, registry: ABIRegistry = default_registry): | ||
self._registry = registry |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So here are some examples how a mutable registry creates problems:
-
If someone modifies the default registry, it modifies how all copies of
ABICodec()
work, even after they have been created, which is surprising. -
If someone does something a little naughty like
codec = ABICodec(); codec._registry.register_encoder(...)
then it will modify how the default registry works. A big surprise!
Since there's no way currently to lock down the registry from change and/or make a copy of a registry, here's an idea for resolving the problem:
- change the
registry
import instance to a method:make_default_registry()
- change the default value of the
registry
keyword arg toNone
- change this line to:
if registry is None:
self._registry = make_default_registry()
else:
self._registry = registry
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@carver wasn't exactly sure if you meant to make default_registry
in eth_abi.registry
into a method? This is how I ended up setting them up
- BaseABICodecEncoder - abceb18#diff-840cea94757e5fecdafdc6c8bb6ba8e2R32
- ABICodec - abceb18#diff-840cea94757e5fecdafdc6c8bb6ba8e2R100
- ABICodecPacked - abceb18#diff-df4a437f066f81aabfd6125511b5e745R18
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are a few things I'd like to see before merging this:
- It would be nice to have
ABICodec
defined in a separatecodec
submodule. The issue is that there are two different submodules,abi
andpacked
, that both include encoding/decoding functions which use the registry. It makes more sense to me to just importABICodec
into those two submodules and instantiate default instances for normal and packed coding. - The
abi
andpacked
submodules could expose adefault_codec
anddefault_packed_codec
respectively. I think they should also expose references to the bound methods on those codec objects for backwards compatibility. - The tests might need to be restructured a bit in light of the encapsulation of coding function in an
ABICodec
class. I tried to structure the tests in correspondence with the particular modules that are included in theeth-abi
package and also in correspondence with the particular resources offered by those modules. For example, there is atest_abi
testing module which corresponds to theabi
module and, within that, there aretest_encode_single
,test_encode_abi
, etc. modules which correspond to the particular resources provided by theabi
module. Those are unit testing modules which is the reason that structure was chosen. Now, I think we're going to need atest_codec
module and, under that, atest_abi_codec
module. You can probably just grab all of the tests fromtest_encode_single
,test_encode_abi
,test_decode_single
, etc. and all the other testing modules which were previously unit testing the functions which were moved into theABICodec
class.
eth_abi/codec_packed.py
Outdated
) | ||
|
||
|
||
class ABICodecPacked(ABICodec): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like the inheritance actually works the other way, with some methods only implemented on the non-packed codec.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, it's weird. I feel like the assumption is that any subclass of the ABIEncoder
would be expected to implement all five methods, but the packed implementation is experimental and isn't decodable. Not sure if it makes sense to have a completely different class for that one. Was trying to keep in line with the ABICodec. Thoughts? I could see both ways.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another alternative is to make a third base codec class that implements the encoding methods. The packed codec would be like:
class ABICodecPacked(BaseABICodecEncoding):
pass
The full codec would also subclass the Base codec and implement the decoding.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@carver I considered that as well, but would we want to make a BaseABICoderDecoding
as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's fine to just implement that in ABICodec
for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated here: abceb18
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added null check for the registry provided to the base encoder: f939865
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and a test for the null case ^ - d63273c
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ended up naming it BaseABICodecEncoder
. Let me know if you think that doesn't make sense since we have separate *Encoder
classes. If so, I can make it BaseABICodecEncoding
like you suggested. Just thought the wording of that was kind of odd. 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, seems fine to me. Could also be BaseABIEncoder
since it's only responsible for the encoding side of the codec. Whatever you like, since it's not intended to be part of the public API. :)
eth_abi/codec_packed.py
Outdated
ABICodec.__init__(self, registry) | ||
|
||
def is_encodable(self, typ: TypeStr, arg: Any) -> bool: | ||
raise NotImplementedError("'is_encodable' is not supported in packed mode") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't looked too deeply at the packed stuff. Why can't we test if a value is encodable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was just keeping with the methods that were previously implemented for packed encoding. I can dig into this some more and see. 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@carver I think the lack of an is_encodable_packed
function may have just been an oversight or for the sake of expedience. I just looked through the code and all of the Packed*
encoder classes seem to inherit from base classes which implement validate_value
. So they should all have what is needed to run an encodability check. Otherwise, I can't think of another reason that it wouldn't be possible. Now that we have the ABICodec
class, we should get this "for free." We just need delete the NotImplementedError
override and add something like is_encodable_packed = default_codec_packed.is_encodable
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why rename the method with the _packed
suffix? We don't rename any of the other methods like decode_abi_packed
.
I'd say just implement it once in the base codec class and get it for free with the same name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think he's suggesting that because we're trying to stay passive by exporting the methods in eth_abi/packed
. Like:
encode_abi_packed = default_codec_packed.encode_abi
encode_single_packed = default_codec_packed.encode_single
Since those were previously exported. But this method didn't exist before.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, thanks, I understand. That line was for the global module export 👍 sounds good
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@carver What @stefanmendoza said. If we had had an is_encodable
function for packed mode, it would have been named is_encodable_packed
. Incidentally, I'm now realizing that this is possibly the reason I didn't add one: such a function, that calls out to all relevant validate_value
methods, would not have actually ended up calling any different method than the regular is_encodable
function. The criteria for encodability do not differ between normal and packed mode.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added the method here:
abceb18#diff-f7c24c7b7a7b6405352611f3ee624887
Just an update, I think all that's left is adding tests for the Packed ABICodec's is_encodable method and updating the documentation to discuss using custom registries. |
@@ -32,7 +32,7 @@ v2.0.0-beta.1 | |||
the parsing API more consistent with the new parsimonious parser. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There were warnings showing up when running make build-docs
about the title underline being too short, so I fixed this up to clean up the warnings.
eth_abi/codec.py
Outdated
def __init__(self, registry: ABIRegistry=None): | ||
""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@davesque / @carver - I noticed that the __init__
methods aren't showing up in the docs; also, I haven't really seen constructors be documented in the project. I think it could be valuable to have this documented as the API isn't self-documenting in regards to what registry is being used or even that there is a default registry being used. Not sure if this is something we'd move up to the class doc or just not have it at all?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like we can get the __init__
docs added to the class docs by adding autoclass_content = 'both'
to the docs conf. Can you experiment with it to see if that works as expected, looks okay, etc? https://stackoverflow.com/a/9772922/8412986
If so, we will probably add it to our project template so that it eventually gets filters to all our other projects.
|
||
.. automodule:: eth_abi.codec | ||
:members: | ||
:undoc-members: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I think it's good to have it on. If we don't want something public, it should be _
-prefixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to go, after the docs. (and @davesque approves, of course) 👍
Yeah, I've got a few commits I'd like to add to this to tie a bow on things. Will hopefully have that all wrapped up before EOD. |
@stefanmendoza @carver Alright, I made the PR to your fork of To briefly summarize what I did:
|
Here's a link to that PR: https://github.com/stefanmendoza/eth-abi/pull/1 |
* Move some testing modules * Add "Codecs" article to docs Also, remove API docs for `codec_packed` module since it will be deleted. * Use common values for encodability tests * Import lint * Type annotations and docstring formatting * Remove explicit checking for `None` registries * Rearrange inheritance scheme in `codec` module This will save some code and hopefully add some utility to the classes provided by that module. * Add missing __init__.py file * Typo * Add note in releases article about codecs
@davesque merged the PR! Thanks for the help! |
Cool, I'm really happy with this direction! When you write out the docs for a new API, and the examples look straightforward and concise, it's a great confirmation that some good choices were made. |
I'm going to go ahead and merge this. Thanks for all your work, @stefanmendoza ! CC: @carver |
What was wrong?
ethereum/web3.py#829
Specifically this comment and this comment. This in the first step towards supporting tuple types in web3.py - allow for providing customer registries to allow for customer encoders and decoders.
How was it fixed?
Added support for providing custom registries to the
eth_abi.abi
methods. The methods assume theeth_abi.registry.registry
is the default.Cute Animal Picture