forked from ethereum/web3.py
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmethod.py
253 lines (212 loc) · 8.23 KB
/
method.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
import functools
from typing import (
TYPE_CHECKING,
Any,
Callable,
Dict,
Generic,
List,
Optional,
Sequence,
Tuple,
Type,
TypeVar,
Union,
)
import warnings
from eth_utils.curried import (
to_tuple,
)
from eth_utils.toolz import (
pipe,
)
from web3._utils.method_formatters import (
get_error_formatters,
get_null_result_formatters,
get_request_formatters,
get_result_formatters,
)
from web3._utils.rpc_abi import (
RPC,
)
from web3.exceptions import (
Web3ValidationError,
)
from web3.types import (
RPCEndpoint,
TReturn,
)
if TYPE_CHECKING:
from web3 import Web3 # noqa: F401
from web3.module import Module # noqa: F401
Munger = Callable[..., Any]
@to_tuple
def _apply_request_formatters(
params: Any, request_formatters: Dict[RPCEndpoint, Callable[..., TReturn]]
) -> Tuple[Any, ...]:
if request_formatters:
formatted_params = pipe(params, request_formatters)
return formatted_params
return params
def _munger_star_apply(fn: Callable[..., TReturn]) -> Callable[..., TReturn]:
@functools.wraps(fn)
def inner(args: Any) -> TReturn:
return fn(*args)
return inner
def _set_mungers(
mungers: Optional[Sequence[Munger]], is_property: bool
) -> Sequence[Any]:
if is_property and mungers:
raise Web3ValidationError("Mungers cannot be used with a property.")
return (
mungers
if mungers
else [default_munger]
if is_property
else [default_root_munger]
)
def default_munger(_module: "Module", *args: Any, **kwargs: Any) -> Tuple[()]:
if args or kwargs:
raise Web3ValidationError("Parameters cannot be passed to a property.")
return ()
def default_root_munger(_module: "Module", *args: Any) -> List[Any]:
return [*args]
TFunc = TypeVar("TFunc", bound=Callable[..., Any])
class Method(Generic[TFunc]):
"""Method object for web3 module methods
Calls to the Method go through these steps:
1. input munging - includes normalization, parameter checking, early parameter
formatting. Any processing on the input parameters that need to happen before
json_rpc method string selection occurs.
A note about mungers: The first (root) munger should reflect the desired
api function arguments. In other words, if the api function wants to
behave as: `get_balance(account, block_identifier=None)`, the root munger
should accept these same arguments, with the addition of the module as
the first argument e.g.:
```
def get_balance_root_munger(module, account, block_identifier=None):
if block_identifier is None:
block_identifier = DEFAULT_BLOCK
return module, [account, block_identifier]
```
all mungers should return an argument list.
if no munger is provided, a default munger expecting no method arguments
will be used.
2. method selection - The json_rpc_method argument can be method string or a
function that returns a method string. If a callable is provided the processed
method inputs are passed to the method selection function, and the returned
method string is used.
3. request and response formatters are set - formatters are retrieved
using the json rpc method string.
4. After the parameter processing from steps 1-3 the request is made using
the calling function returned by the module attribute ``retrieve_caller_fn``
and the response formatters are applied to the output.
"""
def __init__(
self,
json_rpc_method: Optional[RPCEndpoint] = None,
mungers: Optional[Sequence[Munger]] = None,
request_formatters: Optional[Callable[..., TReturn]] = None,
result_formatters: Optional[Callable[..., TReturn]] = None,
null_result_formatters: Optional[Callable[..., TReturn]] = None,
method_choice_depends_on_args: Optional[Callable[..., RPCEndpoint]] = None,
is_property: bool = False,
):
self.json_rpc_method = json_rpc_method
self.mungers = _set_mungers(mungers, is_property)
self.request_formatters = request_formatters or get_request_formatters
self.result_formatters = result_formatters or get_result_formatters
self.null_result_formatters = (
null_result_formatters or get_null_result_formatters
)
self.method_choice_depends_on_args = method_choice_depends_on_args
self.is_property = is_property
def __get__(
self, obj: Optional["Module"] = None, obj_type: Optional[Type["Module"]] = None
) -> TFunc:
if obj is None:
raise TypeError(
"Direct calls to methods are not supported. "
"Methods must be called from an module instance, "
"usually attached to a web3 instance."
)
return obj.retrieve_caller_fn(self)
@property
def method_selector_fn(
self,
) -> Callable[..., Union[RPCEndpoint, Callable[..., RPCEndpoint]]]:
"""Gets the method selector from the config."""
if callable(self.json_rpc_method):
return self.json_rpc_method
elif isinstance(self.json_rpc_method, (str,)):
return lambda *_: self.json_rpc_method
raise ValueError(
"``json_rpc_method`` config invalid. May be a string or function"
)
def input_munger(self, module: "Module", args: Any, kwargs: Any) -> List[Any]:
# This function takes the "root_munger" - (the first munger in
# the list of mungers) and then pipes the return value of the
# previous munger as an argument to the next munger to return
# an array of arguments that have been formatted.
# See the test_process_params test
# in tests/core/method-class/test_method.py for an example
# with multiple mungers.
# TODO: Create friendly error output.
mungers_iter = iter(self.mungers)
root_munger = next(mungers_iter)
munged_inputs = pipe(
root_munger(module, *args, **kwargs),
*map(
lambda m: _munger_star_apply(functools.partial(m, module)), mungers_iter
),
)
return munged_inputs
def process_params(
self, module: "Module", *args: Any, **kwargs: Any
) -> Tuple[
Tuple[Union[RPCEndpoint, Callable[..., RPCEndpoint]], Tuple[Any, ...]],
Tuple[
Union[TReturn, Dict[str, Callable[..., Any]]],
Callable[..., Any],
Union[TReturn, Callable[..., Any]],
],
]:
params = self.input_munger(module, args, kwargs)
if self.method_choice_depends_on_args:
# If the method choice depends on the args that get passed in,
# the first parameter determines which method needs to be called
self.json_rpc_method = self.method_choice_depends_on_args(value=params[0])
pending_or_latest_filter_methods = [
RPC.eth_newPendingTransactionFilter,
RPC.eth_newBlockFilter,
]
if self.json_rpc_method in pending_or_latest_filter_methods:
# For pending or latest filter methods, use params to determine
# which method to call, but don't pass them through with the request
params = []
method = self.method_selector_fn()
response_formatters = (
self.result_formatters(method, module),
get_error_formatters(method),
self.null_result_formatters(method),
)
request = (
method,
_apply_request_formatters(params, self.request_formatters(method)),
)
return request, response_formatters
class DeprecatedMethod:
def __init__(
self, method: Method[Callable[..., Any]], old_name: str, new_name: str
) -> None:
self.method = method
self.old_name = old_name
self.new_name = new_name
def __get__(
self, obj: Optional["Module"] = None, obj_type: Optional[Type["Module"]] = None
) -> Any:
warnings.warn(
f"{self.old_name} is deprecated in favor of {self.new_name}",
category=DeprecationWarning,
)
return self.method.__get__(obj, obj_type)