|
| 1 | +from eth_utils import ( |
| 2 | + to_tuple, |
| 3 | +) |
| 4 | +from eth_utils.toolz import ( |
| 5 | + identity, |
| 6 | + pipe, |
| 7 | +) |
| 8 | + |
| 9 | +from web3.exceptions import ( |
| 10 | + UndefinedMethodError, |
| 11 | +) |
| 12 | + |
| 13 | +TEST_METHOD_CONFIG = { |
| 14 | + 'name': "signInBlood", |
| 15 | + 'mungers': [], |
| 16 | + 'json_rpc_method': "eth_signTransactionInBlood", |
| 17 | +} |
| 18 | + |
| 19 | + |
| 20 | +def lookup_method(module, module_config, method_class, attr_name): |
| 21 | + try: |
| 22 | + method = module_config[attr_name] |
| 23 | + except KeyError: |
| 24 | + raise UndefinedMethodError("No method named {0}".format(attr_name)) |
| 25 | + return method_class(module.web3, method) |
| 26 | + |
| 27 | + |
| 28 | +class DummyRequestManager: |
| 29 | + def request_blocking(method, params): |
| 30 | + return (method, params) |
| 31 | + |
| 32 | + |
| 33 | +class DummyWeb3: |
| 34 | + manager = DummyRequestManager |
| 35 | + |
| 36 | + |
| 37 | +def test_sync_method_config_loading(): |
| 38 | + signInBlood = BlockingMethod(DummyWeb3(), TEST_METHOD_CONFIG) |
| 39 | + signInBlood.input_munger = lambda *_: {} |
| 40 | + assert signInBlood.method_selector |
| 41 | + assert ('eth_signTransactionInBlood', {}) == signInBlood() |
| 42 | + |
| 43 | + |
| 44 | +class BaseMethod: |
| 45 | + """BaseMethod for web3 module methods |
| 46 | +
|
| 47 | + Calls to the Method go through these steps: |
| 48 | +
|
| 49 | + 1. input munging ;) - includes normalization, parameter checking, formatters. |
| 50 | + Any processing on the input parameters that need to happen before json_rpc |
| 51 | + method string selection occurs. |
| 52 | +
|
| 53 | + 2. method selection - function that selects the correct rpc_method. accepts a |
| 54 | + function or an string. |
| 55 | +
|
| 56 | + 3. constructing formatter middlewares - takes the rpc_method and looks up the |
| 57 | + corresponding input/output formatters. these are the middlewares migrated here. |
| 58 | +
|
| 59 | + 4. making the request through the middleware (pipeline)? wrapped request |
| 60 | + function. |
| 61 | + """ |
| 62 | + def __init__(self, web3, method_config): |
| 63 | + self.__name__ = method_config.get('name', 'anonymous') |
| 64 | + self.__doc__ = method_config.get('doc', '') |
| 65 | + self.is_property = method_config.get('is_property', False) |
| 66 | + self.web3 = web3 |
| 67 | + self.input_munger = self._construct_input_pipe( |
| 68 | + method_config.get('mungers')) or identity |
| 69 | + self.method_selector = self._method_selector( |
| 70 | + method_config.get('json_rpc_method')) |
| 71 | + # TODO: Write formatter lookup. |
| 72 | + self.lookup_formatter = None |
| 73 | + |
| 74 | + def _construct_input_pipe(self, formatters): |
| 75 | + formatters = formatters or [identity] |
| 76 | + |
| 77 | + def _method_selector(self, selector): |
| 78 | + """Method selector can just be the method string. |
| 79 | + """ |
| 80 | + if isinstance(selector, (str,)): |
| 81 | + return lambda _: selector |
| 82 | + else: |
| 83 | + return selector |
| 84 | + |
| 85 | + def get_formatters(self, method_string): |
| 86 | + """Lookup the request formatters for the rpc_method""" |
| 87 | + if not self.lookup_formatter: |
| 88 | + return ([identity], [identity],) |
| 89 | + else: |
| 90 | + raise NotImplementedError() |
| 91 | + |
| 92 | + def prep_for_call(self, *args, **kwargs): |
| 93 | + # takes in input params, steps 1-3 |
| 94 | + params, method, (req_formatters, ret_formatters) = pipe_appends( |
| 95 | + [self.input_munger, self.method_selector, self.get_formatters], |
| 96 | + (args, kwargs,)) |
| 97 | + return pipe((method, params,), *req_formatters), ret_formatters |
| 98 | + |
| 99 | + def __call__(self): |
| 100 | + raise NotImplementedError() |
| 101 | + |
| 102 | + |
| 103 | +@to_tuple |
| 104 | +def pipe_appends(fns, val): |
| 105 | + """pipes val through a list of fns while appending the result to the |
| 106 | + tuple output |
| 107 | +
|
| 108 | + e.g.: |
| 109 | +
|
| 110 | + >>> pipe_appends([lambda x: x**2, lambda x: x*10], 5) |
| 111 | + (25, 250) |
| 112 | +
|
| 113 | + """ |
| 114 | + for fn in fns: |
| 115 | + val = fn(val) |
| 116 | + yield val |
| 117 | + |
| 118 | + |
| 119 | +class BlockingMethod(BaseMethod): |
| 120 | + def __call__(self, *args, **kwargs): |
| 121 | + (method, params), output_formatters = self.prep_for_call(*args, **kwargs) |
| 122 | + return pipe( |
| 123 | + self.web3.manager.request_blocking(method, params), |
| 124 | + *output_formatters) |
| 125 | + |
| 126 | + def __get__(self, obj, objType): |
| 127 | + # allow methods to be configured for property access |
| 128 | + if self.is_property is True: |
| 129 | + return self.__call__() |
| 130 | + else: |
| 131 | + return self |
| 132 | + |
| 133 | + |
| 134 | +class AsyncMethod(BaseMethod): |
| 135 | + async def __call__(self, *args, **kwargs): |
| 136 | + (method, params), output_formatters = self.prep_for_call(*args, **kwargs) |
| 137 | + raw_result = await self.web3.manager.request_async(method, params) |
| 138 | + return pipe(raw_result, *output_formatters) |
| 139 | + |
| 140 | + async def __get__(self, obj, objType): |
| 141 | + if self.is_property is True: |
| 142 | + return await self.__call__() |
| 143 | + else: |
| 144 | + return self |
0 commit comments