Skip to content

Commit a3d52f5

Browse files
committed
Add option to attach methods to classes extending from Module class
* Refactor logic for attaching a `Method` class as a property rather than a method. Instead of implicitly setting `mungers=None`, explicitly set the `is_property` flag on `Method` to `True`. This also facilitates attaching new methods and properties to modules. * Fix up some tests in test_method.py that were falsely passing to actually test correctly. Add tests for new `is_property` flag for the `Method` class. * Create `test_module.py` and add tests for `attach_methods()`
1 parent dd5bd98 commit a3d52f5

File tree

10 files changed

+169
-51
lines changed

10 files changed

+169
-51
lines changed

tests/core/method-class/test_method.py

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def test_method_accepts_callable_for_selector():
3232

3333
def test_method_selector_fn_accepts_str():
3434
method = Method(
35-
mungers=None,
35+
is_property=True,
3636
json_rpc_method='eth_method',
3737
)
3838
assert method.method_selector_fn() == 'eth_method'
@@ -77,7 +77,7 @@ def test_input_munger_parameter_passthrough_matching_arity():
7777
mungers=[lambda m, z, y: ['success']],
7878
json_rpc_method='eth_method',
7979
)
80-
method.input_munger(object(), ['first', 'second'], {}) == 'success'
80+
assert method.input_munger(object(), ['first', 'second'], {}) == ['success']
8181

8282

8383
def test_input_munger_parameter_passthrough_mismatch_arity():
@@ -94,16 +94,15 @@ def test_input_munger_falsy_config_result_in_default_munger():
9494
mungers=[],
9595
json_rpc_method='eth_method',
9696
)
97-
method.input_munger(object(), [], {}) == []
97+
assert method.input_munger(object(), [], {}) == []
9898

9999

100-
def test_default_input_munger_with_input_parameters_exception():
100+
def test_default_input_munger_with_input_parameters():
101101
method = Method(
102102
mungers=[],
103103
json_rpc_method='eth_method',
104104
)
105-
with pytest.raises(TypeError):
106-
method.input_munger(object(), [1], {})
105+
assert method.input_munger(object(), [1], {}) == [1]
107106

108107

109108
@pytest.mark.parametrize(
@@ -125,7 +124,7 @@ def test_default_input_munger_with_input_parameters_exception():
125124
},
126125
['unexpected_argument'],
127126
{},
128-
TypeError,
127+
IndexError,
129128
2
130129
),
131130
(
@@ -184,6 +183,15 @@ def test_default_input_munger_with_input_parameters_exception():
184183
('eth_getBalance', ('0x0000000000000000000000000000000000000000', '0x3')),
185184
2,
186185
),
186+
(
187+
{
188+
'json_rpc_method': 'eth_getBalance',
189+
},
190+
('0x0000000000000000000000000000000000000000', 3),
191+
{},
192+
('eth_getBalance', ('0x0000000000000000000000000000000000000000', '0x3')),
193+
2,
194+
),
187195
(
188196
{
189197
'mungers': [
@@ -206,7 +214,17 @@ def test_default_input_munger_with_input_parameters_exception():
206214
{},
207215
('eth_chainId', ()),
208216
2,
209-
)
217+
),
218+
(
219+
{
220+
'is_property': True,
221+
'json_rpc_method': 'eth_chainId',
222+
},
223+
[],
224+
{},
225+
('eth_chainId', ()),
226+
2,
227+
),
210228
),
211229
ids=[
212230
'raises-error-no-rpc-method',
@@ -215,9 +233,11 @@ def test_default_input_munger_with_input_parameters_exception():
215233
'test-rpc-method-as-callable',
216234
'test-arg-munger',
217235
'test-munger-wrong-length-arg',
218-
'test-request-formatters',
236+
'test-request-formatters-default-root-munger-explicit',
237+
'test-request-formatters-default-root-munger-implicit',
219238
'test-mungers-and-request-formatters',
220239
'test-response-formatters',
240+
'test-set-as-property-default-munger-implicit',
221241
]
222242
)
223243
def test_process_params(
@@ -230,7 +250,7 @@ def test_process_params(
230250
if isclass(expected_request_result) and issubclass(expected_request_result, Exception):
231251
with pytest.raises(expected_request_result):
232252
method = Method(**method_config)
233-
request_params, output_formatter = method.process_params(object(), *args, **kwargs)
253+
method.process_params(object(), *args, **kwargs)
234254
else:
235255
method = Method(**method_config)
236256
request_params, output_formatter = method.process_params(object(), *args, **kwargs)
@@ -248,8 +268,8 @@ class Success(Exception):
248268
pass
249269

250270

251-
def return_exception_raising_formatter(method):
252-
def formatter(params):
271+
def return_exception_raising_formatter(_method):
272+
def formatter(_params):
253273
raise Success()
254274
return compose(formatter)
255275

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import pytest
2+
3+
from web3 import (
4+
EthereumTesterProvider,
5+
Web3,
6+
)
7+
from web3.method import (
8+
Method,
9+
)
10+
11+
12+
@pytest.fixture
13+
def web3_with_external_modules(module1, module2, module3):
14+
return Web3(
15+
EthereumTesterProvider(),
16+
external_modules={
17+
'module1': module1,
18+
'module2': (module2, {
19+
'submodule1': module3,
20+
}),
21+
}
22+
)
23+
24+
25+
def test_attach_methods_to_module(web3_with_external_modules):
26+
w3 = web3_with_external_modules
27+
28+
w3.module1.attach_methods({
29+
# set `property1` on `module1` with `eth_chainId` RPC endpoint
30+
'property1': Method('eth_chainId', is_property=True),
31+
# set `method1` on `module1` with `eth_getBalance` RPC endpoint
32+
'method1': Method('eth_getBalance'),
33+
})
34+
35+
assert w3.eth.chain_id == 61
36+
assert w3.module1.property1 == 61
37+
38+
coinbase = w3.eth.coinbase
39+
assert w3.eth.get_balance(coinbase, 'latest') == 1000000000000000000000000
40+
assert w3.module1.method1(coinbase, 'latest') == 1000000000000000000000000
41+
42+
w3.module2.submodule1.attach_methods({
43+
# set `method2` on `module2.submodule1` with `eth_blockNumber` RPC endpoint
44+
'method2': Method('eth_blockNumber', is_property=True)
45+
})
46+
47+
assert w3.eth.block_number == 0
48+
assert w3.module2.submodule1.method2 == 0
49+
50+
w3.eth.attach_methods({'get_block2': Method('eth_getBlockByNumber')})
51+
52+
assert w3.eth.get_block('latest')['number'] == 0
53+
assert w3.eth.get_block('pending')['number'] == 1
54+
55+
assert w3.eth.get_block2('latest')['number'] == 0
56+
assert w3.eth.get_block2('pending')['number'] == 1
57+
58+
59+
def test_attach_methods_with_mungers(web3_with_external_modules):
60+
w3 = web3_with_external_modules
61+
62+
w3.module1.attach_methods({
63+
'method1': Method('eth_getBlockByNumber', mungers=[
64+
lambda _method, block_id, f, _z: (block_id, f),
65+
lambda _m, block_id, _f: (block_id - 1,),
66+
]),
67+
})
68+
69+
assert w3.eth.get_block(0)['baseFeePerGas'] == 1000000000
70+
assert w3.eth.get_block(1)['baseFeePerGas'] == 875000000
71+
72+
# `method1` should take a higher block number than `eth_getBlockByNumber` due to mungers and no
73+
# other params should matter
74+
assert w3.module1.method1(1, False, '_is_never_used_')['baseFeePerGas'] == 1000000000
75+
assert w3.module1.method1(2, '_will_be_overridden_', None)['baseFeePerGas'] == 875000000

web3/_utils/admin.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,19 +40,19 @@ def admin_start_params_munger(
4040

4141
datadir: Method[Callable[[], str]] = Method(
4242
RPC.admin_datadir,
43-
mungers=None,
43+
is_property=True,
4444
)
4545

4646

4747
node_info: Method[Callable[[], NodeInfo]] = Method(
4848
RPC.admin_nodeInfo,
49-
mungers=None,
49+
is_property=True,
5050
)
5151

5252

5353
peers: Method[Callable[[], List[Peer]]] = Method(
5454
RPC.admin_peers,
55-
mungers=None,
55+
is_property=True,
5656
)
5757

5858

@@ -77,13 +77,13 @@ def __call__(
7777

7878
stop_rpc: Method[Callable[[], bool]] = Method(
7979
RPC.admin_stopRPC,
80-
mungers=None,
80+
is_property=True,
8181
)
8282

8383

8484
stop_ws: Method[Callable[[], bool]] = Method(
8585
RPC.admin_stopWS,
86-
mungers=None,
86+
is_property=True,
8787
)
8888

8989
#

web3/_utils/miner.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,19 +51,19 @@
5151

5252
stop: Method[Callable[[], bool]] = Method(
5353
RPC.miner_stop,
54-
mungers=None,
54+
is_property=True,
5555
)
5656

5757

5858
start_auto_dag: Method[Callable[[], bool]] = Method(
5959
RPC.miner_startAutoDag,
60-
mungers=None,
60+
is_property=True,
6161
)
6262

6363

6464
stop_auto_dag: Method[Callable[[], bool]] = Method(
6565
RPC.miner_stopAutoDag,
66-
mungers=None,
66+
is_property=True,
6767
)
6868

6969

web3/_utils/personal.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,13 @@
4444

4545
list_accounts: Method[Callable[[], List[ChecksumAddress]]] = Method(
4646
RPC.personal_listAccounts,
47-
mungers=None,
47+
is_property=True,
4848
)
4949

5050

5151
list_wallets: Method[Callable[[], List[GethWallet]]] = Method(
5252
RPC.personal_listWallets,
53-
mungers=None,
53+
is_property=True,
5454
)
5555

5656

web3/_utils/txpool.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,17 @@
1616

1717
content: Method[Callable[[], TxPoolContent]] = Method(
1818
RPC.txpool_content,
19-
mungers=None,
19+
is_property=True,
2020
)
2121

2222

2323
inspect: Method[Callable[[], TxPoolInspect]] = Method(
2424
RPC.txpool_inspect,
25-
mungers=None,
25+
is_property=True,
2626
)
2727

2828

2929
status: Method[Callable[[], TxPoolStatus]] = Method(
3030
RPC.txpool_status,
31-
mungers=None,
31+
is_property=True,
3232
)

web3/eth.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ class BaseEth(Module):
119119

120120
_gas_price: Method[Callable[[], Wei]] = Method(
121121
RPC.eth_gasPrice,
122-
mungers=None,
122+
is_property=True,
123123
)
124124

125125
@property
@@ -244,7 +244,7 @@ def estimate_gas_munger(
244244

245245
_max_priority_fee: Method[Callable[..., Wei]] = Method(
246246
RPC.eth_maxPriorityFeePerGas,
247-
mungers=None,
247+
is_property=True,
248248
)
249249

250250
def get_block_munger(
@@ -267,12 +267,12 @@ def get_block_munger(
267267

268268
get_block_number: Method[Callable[[], BlockNumber]] = Method(
269269
RPC.eth_blockNumber,
270-
mungers=None,
270+
is_property=True,
271271
)
272272

273273
get_coinbase: Method[Callable[[], ChecksumAddress]] = Method(
274274
RPC.eth_coinbase,
275-
mungers=None,
275+
is_property=True,
276276
)
277277

278278
def block_id_munger(
@@ -315,27 +315,27 @@ def call_munger(
315315

316316
_get_accounts: Method[Callable[[], Tuple[ChecksumAddress]]] = Method(
317317
RPC.eth_accounts,
318-
mungers=None,
318+
is_property=True,
319319
)
320320

321321
_get_hashrate: Method[Callable[[], int]] = Method(
322322
RPC.eth_hashrate,
323-
mungers=None,
323+
is_property=True,
324324
)
325325

326326
_chain_id: Method[Callable[[], int]] = Method(
327327
RPC.eth_chainId,
328-
mungers=None,
328+
is_property=True,
329329
)
330330

331331
_is_mining: Method[Callable[[], bool]] = Method(
332332
RPC.eth_mining,
333-
mungers=None,
333+
is_property=True,
334334
)
335335

336336
_is_syncing: Method[Callable[[], Union[SyncStatus, bool]]] = Method(
337337
RPC.eth_syncing,
338-
mungers=None,
338+
is_property=True,
339339
)
340340

341341
_get_transaction_receipt: Method[Callable[[_Hash32], TxReceipt]] = Method(
@@ -565,7 +565,7 @@ def icapNamereg(self) -> NoReturn:
565565

566566
_protocol_version: Method[Callable[[], str]] = Method(
567567
RPC.eth_protocolVersion,
568-
mungers=None,
568+
is_property=True,
569569
)
570570

571571
@property
@@ -983,7 +983,7 @@ def getCompilers(self) -> NoReturn:
983983

984984
get_work: Method[Callable[[], List[HexBytes]]] = Method(
985985
RPC.eth_getWork,
986-
mungers=None,
986+
is_property=True,
987987
)
988988

989989
@deprecated_for("generate_gas_price")

0 commit comments

Comments
 (0)