Skip to content

Commit 975fcad

Browse files
committed
Remove AttributeDict method formatters and opt for middleware
- ``AttributeDict`` leads to typing complications when properties are accessed via attribute, rather than key / value. This doesn't complicate too much since a user can opt for key / value every time but it isn't ideal to provide an option that breaks typing. Those who wish to turn off all recursive conversion to ``AttributeDict`` can simply remove the ``attrdict_middleware`` (or ``async_attrdict_middleware``) and not worry about possible typing complications. - In order for this to work for async as well, support for ``async_attrdict_middleware` was introduced. This is also now one of the default middlewares for the ``EthereumTesterProvider`` as this was the default behavior achieved via the result formatters.
1 parent 58a8b2c commit 975fcad

File tree

8 files changed

+105
-55
lines changed

8 files changed

+105
-55
lines changed

newsfragments/2805.breaking.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
``dict`` to ``AttributeDict`` conversion is no longer a default result formatter. This conversion is now done via a default middleware that may be removed.

web3/_utils/events.py

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,9 @@ def get_event_abi_types_for_decoding(
208208

209209
@curry
210210
def get_event_data(
211-
abi_codec: ABICodec, event_abi: ABIEvent, log_entry: LogReceipt
211+
abi_codec: ABICodec,
212+
event_abi: ABIEvent,
213+
log_entry: LogReceipt,
212214
) -> EventData:
213215
"""
214216
Given an event ABI and a log entry for that event, return the decoded
@@ -269,18 +271,21 @@ def get_event_data(
269271
)
270272
)
271273

272-
event_data = {
273-
"args": event_args,
274-
"event": event_abi["name"],
275-
"logIndex": log_entry["logIndex"],
276-
"transactionIndex": log_entry["transactionIndex"],
277-
"transactionHash": log_entry["transactionHash"],
278-
"address": log_entry["address"],
279-
"blockHash": log_entry["blockHash"],
280-
"blockNumber": log_entry["blockNumber"],
281-
}
274+
event_data = EventData(
275+
args=event_args,
276+
event=event_abi["name"],
277+
logIndex=log_entry["logIndex"],
278+
transactionIndex=log_entry["transactionIndex"],
279+
transactionHash=log_entry["transactionHash"],
280+
address=log_entry["address"],
281+
blockHash=log_entry["blockHash"],
282+
blockNumber=log_entry["blockNumber"],
283+
)
284+
285+
if isinstance(log_entry, AttributeDict):
286+
return cast(EventData, AttributeDict.recursive(event_data))
282287

283-
return cast(EventData, AttributeDict.recursive(event_data))
288+
return event_data
284289

285290

286291
@to_tuple

web3/_utils/method_formatters.py

Lines changed: 35 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
is_0x_prefixed,
3333
is_address,
3434
is_bytes,
35-
is_dict,
3635
is_integer,
3736
is_null,
3837
is_string,
@@ -88,6 +87,7 @@
8887
)
8988
from web3.datastructures import (
9089
AttributeDict,
90+
ReadableAttributeDict,
9191
)
9292
from web3.exceptions import (
9393
BlockNotFound,
@@ -155,6 +155,19 @@ def is_attrdict(val: Any) -> bool:
155155
not_attrdict = complement(is_attrdict)
156156

157157

158+
@curry
159+
def type_aware_apply_formatters_to_dict(
160+
formatters: Formatters,
161+
value: Union[AttributeDict[str, Any], Dict[str, Any]],
162+
) -> Union[ReadableAttributeDict[str, Any], Dict[str, Any]]:
163+
formatted_dict: Dict[str, Any] = apply_formatters_to_dict(formatters, dict(value))
164+
return (
165+
AttributeDict.recursive(formatted_dict)
166+
if is_attrdict(value)
167+
else formatted_dict
168+
)
169+
170+
158171
TRANSACTION_RESULT_FORMATTERS = {
159172
"blockHash": apply_formatter_if(is_not_null, to_hexbytes(32)),
160173
"blockNumber": apply_formatter_if(is_not_null, to_integer_if_hex),
@@ -179,7 +192,9 @@ def is_attrdict(val: Any) -> bool:
179192
}
180193

181194

182-
transaction_result_formatter = apply_formatters_to_dict(TRANSACTION_RESULT_FORMATTERS)
195+
transaction_result_formatter = type_aware_apply_formatters_to_dict(
196+
TRANSACTION_RESULT_FORMATTERS
197+
)
183198

184199

185200
def apply_list_to_array_formatter(formatter: Any) -> Callable[..., Any]:
@@ -198,7 +213,7 @@ def apply_list_to_array_formatter(formatter: Any) -> Callable[..., Any]:
198213
}
199214

200215

201-
log_entry_formatter = apply_formatters_to_dict(LOG_ENTRY_FORMATTERS)
216+
log_entry_formatter = type_aware_apply_formatters_to_dict(LOG_ENTRY_FORMATTERS)
202217

203218

204219
RECEIPT_FORMATTERS = {
@@ -219,7 +234,7 @@ def apply_list_to_array_formatter(formatter: Any) -> Callable[..., Any]:
219234
}
220235

221236

222-
receipt_formatter = apply_formatters_to_dict(RECEIPT_FORMATTERS)
237+
receipt_formatter = type_aware_apply_formatters_to_dict(RECEIPT_FORMATTERS)
223238

224239
BLOCK_FORMATTERS = {
225240
"baseFeePerGas": to_integer_if_hex,
@@ -256,7 +271,7 @@ def apply_list_to_array_formatter(formatter: Any) -> Callable[..., Any]:
256271
}
257272

258273

259-
block_formatter = apply_formatters_to_dict(BLOCK_FORMATTERS)
274+
block_formatter = type_aware_apply_formatters_to_dict(BLOCK_FORMATTERS)
260275

261276

262277
SYNCING_FORMATTERS = {
@@ -268,7 +283,7 @@ def apply_list_to_array_formatter(formatter: Any) -> Callable[..., Any]:
268283
}
269284

270285

271-
syncing_formatter = apply_formatters_to_dict(SYNCING_FORMATTERS)
286+
syncing_formatter = type_aware_apply_formatters_to_dict(SYNCING_FORMATTERS)
272287

273288

274289
TRANSACTION_POOL_CONTENT_FORMATTERS = {
@@ -283,7 +298,7 @@ def apply_list_to_array_formatter(formatter: Any) -> Callable[..., Any]:
283298
}
284299

285300

286-
transaction_pool_content_formatter = apply_formatters_to_dict(
301+
transaction_pool_content_formatter = type_aware_apply_formatters_to_dict(
287302
TRANSACTION_POOL_CONTENT_FORMATTERS
288303
)
289304

@@ -294,7 +309,7 @@ def apply_list_to_array_formatter(formatter: Any) -> Callable[..., Any]:
294309
}
295310

296311

297-
transaction_pool_inspect_formatter = apply_formatters_to_dict(
312+
transaction_pool_inspect_formatter = type_aware_apply_formatters_to_dict(
298313
TRANSACTION_POOL_INSPECT_FORMATTERS
299314
)
300315

@@ -308,7 +323,7 @@ def apply_list_to_array_formatter(formatter: Any) -> Callable[..., Any]:
308323
),
309324
}
310325

311-
fee_history_formatter = apply_formatters_to_dict(FEE_HISTORY_FORMATTERS)
326+
fee_history_formatter = type_aware_apply_formatters_to_dict(FEE_HISTORY_FORMATTERS)
312327

313328
STORAGE_PROOF_FORMATTERS = {
314329
"key": HexBytes,
@@ -324,19 +339,19 @@ def apply_list_to_array_formatter(formatter: Any) -> Callable[..., Any]:
324339
"nonce": to_integer_if_hex,
325340
"storageHash": to_hexbytes(32),
326341
"storageProof": apply_list_to_array_formatter(
327-
apply_formatters_to_dict(STORAGE_PROOF_FORMATTERS)
342+
type_aware_apply_formatters_to_dict(STORAGE_PROOF_FORMATTERS)
328343
),
329344
}
330345

331-
proof_formatter = apply_formatters_to_dict(ACCOUNT_PROOF_FORMATTERS)
346+
proof_formatter = type_aware_apply_formatters_to_dict(ACCOUNT_PROOF_FORMATTERS)
332347

333348
FILTER_PARAMS_FORMATTERS = {
334349
"fromBlock": apply_formatter_if(is_integer, integer_to_hex),
335350
"toBlock": apply_formatter_if(is_integer, integer_to_hex),
336351
}
337352

338353

339-
filter_params_formatter = apply_formatters_to_dict(FILTER_PARAMS_FORMATTERS)
354+
filter_params_formatter = type_aware_apply_formatters_to_dict(FILTER_PARAMS_FORMATTERS)
340355

341356

342357
filter_result_formatter = apply_one_of_formatters(
@@ -351,7 +366,9 @@ def apply_list_to_array_formatter(formatter: Any) -> Callable[..., Any]:
351366
"maxPriorityFeePerGas": to_hex_if_integer,
352367
}
353368

354-
transaction_request_formatter = apply_formatters_to_dict(TRANSACTION_REQUEST_FORMATTERS)
369+
transaction_request_formatter = type_aware_apply_formatters_to_dict(
370+
TRANSACTION_REQUEST_FORMATTERS
371+
)
355372
transaction_param_formatter = compose(
356373
remove_key_if("to", lambda txn: txn["to"] in {"", b"", None}),
357374
remove_key_if("gasPrice", lambda txn: txn["gasPrice"] in {"", b"", None}),
@@ -398,22 +415,22 @@ def apply_list_to_array_formatter(formatter: Any) -> Callable[..., Any]:
398415
"tx": transaction_result_formatter,
399416
}
400417

401-
signed_tx_formatter = apply_formatters_to_dict(SIGNED_TX_FORMATTER)
418+
signed_tx_formatter = type_aware_apply_formatters_to_dict(SIGNED_TX_FORMATTER)
402419

403-
FILTER_PARAM_NORMALIZERS = apply_formatters_to_dict(
420+
FILTER_PARAM_NORMALIZERS = type_aware_apply_formatters_to_dict(
404421
{"address": apply_formatter_if(is_string, lambda x: [x])}
405422
)
406423

407424

408425
GETH_WALLET_FORMATTER = {"address": to_checksum_address}
409426

410-
geth_wallet_formatter = apply_formatters_to_dict(GETH_WALLET_FORMATTER)
427+
geth_wallet_formatter = type_aware_apply_formatters_to_dict(GETH_WALLET_FORMATTER)
411428

412429
GETH_WALLETS_FORMATTER = {
413430
"accounts": apply_list_to_array_formatter(geth_wallet_formatter),
414431
}
415432

416-
geth_wallets_formatter = apply_formatters_to_dict(GETH_WALLETS_FORMATTER)
433+
geth_wallets_formatter = type_aware_apply_formatters_to_dict(GETH_WALLETS_FORMATTER)
417434

418435

419436
PYTHONIC_REQUEST_FORMATTERS: Dict[RPCEndpoint, Callable[..., Any]] = {
@@ -556,10 +573,6 @@ def apply_list_to_array_formatter(formatter: Any) -> Callable[..., Any]:
556573
RPC.net_peerCount: to_integer_if_hex,
557574
}
558575

559-
ATTRDICT_FORMATTER = {
560-
"*": apply_formatter_if(is_dict and not_attrdict, AttributeDict.recursive)
561-
}
562-
563576
METHOD_NORMALIZERS: Dict[RPCEndpoint, Callable[..., Any]] = {
564577
RPC.eth_getLogs: apply_formatter_at_index(FILTER_PARAM_NORMALIZERS, 0),
565578
RPC.eth_newFilter: apply_formatter_at_index(FILTER_PARAM_NORMALIZERS, 0),
@@ -820,10 +833,7 @@ def get_result_formatters(
820833
partial_formatters = apply_module_to_formatters(
821834
formatters_requiring_module, module, method_name
822835
)
823-
attrdict_formatter = apply_formatter_if(
824-
is_dict and not_attrdict, AttributeDict.recursive
825-
)
826-
return compose(*partial_formatters, attrdict_formatter, *formatters)
836+
return compose(*partial_formatters, *formatters)
827837

828838

829839
def get_error_formatters(

web3/contract/async_contract.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -414,8 +414,8 @@ async def get_logs(
414414
)
415415

416416
# Convert raw binary data to Python proxy objects as described by ABI
417-
return tuple(
418-
get_event_data(self.w3.codec, abi, entry) for entry in logs # type: ignore
417+
return tuple( # type: ignore
418+
get_event_data(self.w3.codec, abi, entry) for entry in logs
419419
)
420420

421421
@combomethod

web3/manager.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
)
4141
from web3.middleware import (
4242
abi_middleware,
43+
async_attrdict_middleware,
4344
async_buffered_gas_estimate_middleware,
4445
async_gas_price_strategy_middleware,
4546
async_validation_middleware,
@@ -115,7 +116,7 @@ def __init__(
115116

116117
if middlewares is None:
117118
middlewares = (
118-
self.async_default_middlewares(w3)
119+
self.async_default_middlewares()
119120
if self.provider.is_async
120121
else self.default_middlewares(w3)
121122
)
@@ -143,20 +144,21 @@ def default_middlewares(w3: "Web3") -> List[Tuple[Middleware, str]]:
143144
(request_parameter_normalizer, "request_param_normalizer"), # Delete
144145
(gas_price_strategy_middleware, "gas_price_strategy"),
145146
(name_to_address_middleware(w3), "name_to_address"), # Add Async
146-
(attrdict_middleware, "attrdict"), # Delete
147+
(attrdict_middleware, "attrdict"),
147148
(pythonic_middleware, "pythonic"), # Delete
148149
(validation_middleware, "validation"),
149150
(abi_middleware, "abi"), # Delete
150151
(buffered_gas_estimate_middleware, "gas_estimate"),
151152
]
152153

153154
@staticmethod
154-
def async_default_middlewares(w3: "Web3") -> List[Tuple[Middleware, str]]:
155+
def async_default_middlewares() -> List[Tuple[Middleware, str]]:
155156
"""
156157
List the default async middlewares for the request manager.
157158
"""
158159
return [
159160
(async_gas_price_strategy_middleware, "gas_price_strategy"),
161+
(async_attrdict_middleware, "attrdict"),
160162
(async_validation_middleware, "validation"),
161163
(async_buffered_gas_estimate_middleware, "gas_estimate"),
162164
]

web3/middleware/__init__.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,6 @@
66
Sequence,
77
)
88

9-
from web3.middleware.async_cache import ( # noqa: F401
10-
_async_simple_cache_middleware as async_simple_cache_middleware,
11-
)
129
from web3.types import (
1310
Middleware,
1411
RPCEndpoint,
@@ -18,7 +15,11 @@
1815
from .abi import ( # noqa: F401
1916
abi_middleware,
2017
)
18+
from .async_cache import ( # noqa: F401
19+
_async_simple_cache_middleware as async_simple_cache_middleware,
20+
)
2121
from .attrdict import ( # noqa: F401
22+
async_attrdict_middleware,
2223
attrdict_middleware,
2324
)
2425
from .buffered_gas_estimate import ( # noqa: F401

web3/middleware/attrdict.py

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,6 @@
44
Callable,
55
)
66

7-
from eth_utils import (
8-
is_dict,
9-
)
107
from eth_utils.toolz import (
118
assoc,
129
)
@@ -15,6 +12,7 @@
1512
AttributeDict,
1613
)
1714
from web3.types import (
15+
AsyncMiddleware,
1816
RPCEndpoint,
1917
RPCResponse,
2018
)
@@ -24,21 +22,48 @@
2422

2523

2624
def attrdict_middleware(
27-
make_request: Callable[[RPCEndpoint, Any], Any], w3: "Web3"
25+
make_request: Callable[[RPCEndpoint, Any], Any], _w3: "Web3"
2826
) -> Callable[[RPCEndpoint, Any], RPCResponse]:
2927
"""
30-
Converts any result which is a dictionary into an a
28+
Converts any result which is a dictionary into an `AttributeDict`.
29+
30+
Note: Accessing `AttributeDict` properties via attribute
31+
(e.g. my_attribute_dict.property1) will not preserve typing.
3132
"""
3233

3334
def middleware(method: RPCEndpoint, params: Any) -> RPCResponse:
3435
response = make_request(method, params)
3536

3637
if "result" in response:
37-
result = response["result"]
38-
if is_dict(result) and not isinstance(result, AttributeDict):
39-
return assoc(response, "result", AttributeDict.recursive(result))
40-
else:
41-
return response
38+
return assoc(
39+
response, "result", AttributeDict.recursive(response["result"])
40+
)
41+
else:
42+
return response
43+
44+
return middleware
45+
46+
47+
# --- async --- #
48+
49+
50+
async def async_attrdict_middleware(
51+
make_request: Callable[[RPCEndpoint, Any], Any], _async_w3: "Web3"
52+
) -> AsyncMiddleware:
53+
"""
54+
Converts any result which is a dictionary into an `AttributeDict`.
55+
56+
Note: Accessing `AttributeDict` properties via attribute
57+
(e.g. my_attribute_dict.property1) will not preserve typing.
58+
"""
59+
60+
async def middleware(method: RPCEndpoint, params: Any) -> RPCResponse:
61+
response = await make_request(method, params)
62+
63+
if "result" in response:
64+
return assoc(
65+
response, "result", AttributeDict.recursive(response["result"])
66+
)
4267
else:
4368
return response
4469

0 commit comments

Comments
 (0)