-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add basic configurable Method class with attr lookup
- Demonstrates limited feature set using the Version module
- Loading branch information
Showing
6 changed files
with
287 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import pytest | ||
|
||
from web3.module import Module | ||
|
||
|
||
class DummyWeb3: | ||
def __init__(self): | ||
Module.attach(self, 'module') | ||
|
||
|
||
@pytest.fixture(scope='module') | ||
def dw3(): | ||
w3 = DummyWeb3() | ||
return w3 | ||
|
||
|
||
def test_module_lookup_method(dw3): | ||
with pytest.raises(AttributeError): | ||
assert dw3.module.blahblahblah | ||
dw3.module.lookup_method_fn = lambda *_: True | ||
assert dw3.module.blahblahblah is True |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import pytest | ||
|
||
from web3 import Web3, EthereumTesterProvider | ||
from web3.providers.eth_tester.main import AsyncEthereumTesterProvider | ||
from web3.version import BlockingVersion, AsyncVersion, Version | ||
|
||
|
||
@pytest.fixture | ||
def blocking_w3(): | ||
return Web3( | ||
EthereumTesterProvider(), | ||
modules={ | ||
'blocking_version': BlockingVersion, | ||
'legacy_version': Version | ||
}) | ||
|
||
|
||
@pytest.fixture | ||
def async_w3(): | ||
return Web3( | ||
AsyncEthereumTesterProvider(), | ||
middlewares=[], | ||
modules={ | ||
'async_version': AsyncVersion, | ||
}) | ||
|
||
|
||
def test_blocking_version(blocking_w3): | ||
assert blocking_w3.blocking_version.api == blocking_w3.legacy_version.api | ||
assert blocking_w3.blocking_version.node == blocking_w3.legacy_version.node | ||
assert blocking_w3.blocking_version.network == blocking_w3.legacy_version.network | ||
assert blocking_w3.blocking_version.ethereum == blocking_w3.legacy_version.ethereum | ||
|
||
|
||
@pytest.mark.asyncio | ||
async def test_async_blocking_version(async_w3, blocking_w3): | ||
# This seems a little awkward. How do we know if something is an awaitable | ||
# or a static method? | ||
assert async_w3.async_version.api == blocking_w3.legacy_version.api | ||
|
||
assert await async_w3.async_version.node == blocking_w3.legacy_version.node | ||
with pytest.raises( | ||
ValueError, | ||
message="RPC Endpoint has not been implemented: net_version" | ||
): | ||
# net_version is provided through a middleware | ||
assert await async_w3.async_version.network == blocking_w3.legacy_version.network | ||
with pytest.raises( | ||
ValueError, | ||
message="RPC Endpoint has not been implemented: eth_protocolVersion" | ||
): | ||
assert await async_w3.async_version.ethereum == blocking_w3.legacy_version.ethereum |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
from eth_utils import ( | ||
to_tuple, | ||
) | ||
from eth_utils.toolz import ( | ||
identity, | ||
pipe, | ||
) | ||
|
||
from web3.exceptions import ( | ||
UndefinedMethodError, | ||
) | ||
|
||
TEST_METHOD_CONFIG = { | ||
'name': "signInBlood", | ||
'mungers': [], | ||
'json_rpc_method': "eth_signTransactionInBlood", | ||
} | ||
|
||
|
||
def lookup_method(module, module_config, method_class, attr_name): | ||
try: | ||
method = module_config[attr_name] | ||
except KeyError: | ||
raise UndefinedMethodError("No method named {0}".format(attr_name)) | ||
return method_class(module.web3, method) | ||
|
||
|
||
class DummyRequestManager: | ||
def request_blocking(method, params): | ||
return (method, params) | ||
|
||
|
||
class DummyWeb3: | ||
manager = DummyRequestManager | ||
|
||
|
||
def test_sync_method_config_loading(): | ||
signInBlood = BlockingMethod(DummyWeb3(), TEST_METHOD_CONFIG) | ||
signInBlood.input_munger = lambda *_: {} | ||
assert signInBlood.method_selector | ||
assert ('eth_signTransactionInBlood', {}) == signInBlood() | ||
|
||
|
||
class BaseMethod: | ||
"""BaseMethod for web3 module methods | ||
Calls to the Method go through these steps: | ||
1. input munging ;) - includes normalization, parameter checking, formatters. | ||
Any processing on the input parameters that need to happen before json_rpc | ||
method string selection occurs. | ||
2. method selection - function that selects the correct rpc_method. accepts a | ||
function or an string. | ||
3. constructing formatter middlewares - takes the rpc_method and looks up the | ||
corresponding input/output formatters. these are the middlewares migrated here. | ||
4. making the request through the middleware (pipeline)? wrapped request | ||
function. | ||
""" | ||
def __init__(self, web3, method_config): | ||
self.__name__ = method_config.get('name', 'anonymous') | ||
self.__doc__ = method_config.get('doc', '') | ||
self.is_property = method_config.get('is_property', False) | ||
self.web3 = web3 | ||
self.input_munger = self._construct_input_pipe( | ||
method_config.get('mungers')) or identity | ||
self.method_selector = self._method_selector( | ||
method_config.get('json_rpc_method')) | ||
# TODO: Write formatter lookup. | ||
self.lookup_formatter = None | ||
|
||
def _construct_input_pipe(self, formatters): | ||
formatters = formatters or [identity] | ||
|
||
def _method_selector(self, selector): | ||
"""Method selector can just be the method string. | ||
""" | ||
if isinstance(selector, (str,)): | ||
return lambda _: selector | ||
else: | ||
return selector | ||
|
||
def get_formatters(self, method_string): | ||
"""Lookup the request formatters for the rpc_method""" | ||
if not self.lookup_formatter: | ||
return ([identity], [identity],) | ||
else: | ||
raise NotImplementedError() | ||
|
||
def prep_for_call(self, *args, **kwargs): | ||
# takes in input params, steps 1-3 | ||
params, method, (req_formatters, ret_formatters) = pipe_appends( | ||
[self.input_munger, self.method_selector, self.get_formatters], | ||
(args, kwargs,)) | ||
return pipe((method, params,), *req_formatters), ret_formatters | ||
|
||
def __call__(self): | ||
raise NotImplementedError() | ||
|
||
|
||
@to_tuple | ||
def pipe_appends(fns, val): | ||
"""pipes val through a list of fns while appending the result to the | ||
tuple output | ||
e.g.: | ||
>>> pipe_appends([lambda x: x**2, lambda x: x*10], 5) | ||
(25, 250) | ||
""" | ||
for fn in fns: | ||
val = fn(val) | ||
yield val | ||
|
||
|
||
class BlockingMethod(BaseMethod): | ||
def __call__(self, *args, **kwargs): | ||
(method, params), output_formatters = self.prep_for_call(*args, **kwargs) | ||
return pipe( | ||
self.web3.manager.request_blocking(method, params), | ||
*output_formatters) | ||
|
||
def __get__(self, obj, objType): | ||
# allow methods to be configured for property access | ||
if self.is_property is True: | ||
return self.__call__() | ||
else: | ||
return self | ||
|
||
|
||
class AsyncMethod(BaseMethod): | ||
async def __call__(self, *args, **kwargs): | ||
(method, params), output_formatters = self.prep_for_call(*args, **kwargs) | ||
raw_result = await self.web3.manager.request_async(method, params) | ||
return pipe(raw_result, *output_formatters) | ||
|
||
async def __get__(self, obj, objType): | ||
if self.is_property is True: | ||
return await self.__call__() | ||
else: | ||
return self |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters