|
7 | 7 | import itertools |
8 | 8 | import re |
9 | 9 | from typing import ( |
| 10 | + TYPE_CHECKING, |
10 | 11 | Any, |
11 | 12 | Callable, |
12 | 13 | Collection, |
| 14 | + Coroutine, |
13 | 15 | Dict, |
14 | 16 | Iterable, |
15 | 17 | List, |
|
53 | 55 | decode_hex, |
54 | 56 | is_bytes, |
55 | 57 | is_list_like, |
| 58 | + is_string, |
56 | 59 | is_text, |
57 | 60 | to_text, |
58 | 61 | to_tuple, |
|
66 | 69 | pipe, |
67 | 70 | ) |
68 | 71 |
|
| 72 | +from web3._utils.decorators import ( |
| 73 | + reject_recursive_repeats, |
| 74 | +) |
69 | 75 | from web3._utils.ens import ( |
70 | 76 | is_ens_name, |
71 | 77 | ) |
|
82 | 88 | ABIEventParams, |
83 | 89 | ABIFunction, |
84 | 90 | ABIFunctionParams, |
| 91 | + TReturn, |
85 | 92 | ) |
86 | 93 | from web3.utils import ( # public utils module |
87 | 94 | get_abi_input_names, |
88 | 95 | ) |
89 | 96 |
|
| 97 | +if TYPE_CHECKING: |
| 98 | + from web3 import ( # noqa: F401 |
| 99 | + AsyncWeb3, |
| 100 | + ) |
| 101 | + |
90 | 102 |
|
91 | 103 | def filter_by_type(_type: str, contract_abi: ABI) -> List[Union[ABIFunction, ABIEvent]]: |
92 | 104 | return [abi for abi in contract_abi if abi["type"] == _type] |
@@ -971,3 +983,70 @@ def __new__(self, args: Any) -> "ABIDecodedNamedTuple": |
971 | 983 | return super().__new__(self, *args) |
972 | 984 |
|
973 | 985 | return ABIDecodedNamedTuple |
| 986 | + |
| 987 | + |
| 988 | +# -- async -- # |
| 989 | + |
| 990 | + |
| 991 | +async def async_data_tree_map( |
| 992 | + async_w3: "AsyncWeb3", |
| 993 | + func: Callable[ |
| 994 | + ["AsyncWeb3", TypeStr, Any], Coroutine[Any, Any, Tuple[TypeStr, Any]] |
| 995 | + ], |
| 996 | + data_tree: Any, |
| 997 | +) -> "ABITypedData": |
| 998 | + """ |
| 999 | + Map an awaitable method to every ABITypedData element in the tree. |
| 1000 | +
|
| 1001 | + The awaitable method should receive three positional args: |
| 1002 | + async_w3, abi_type, and data |
| 1003 | + """ |
| 1004 | + |
| 1005 | + async def async_map_to_typed_data(elements: Any) -> "ABITypedData": |
| 1006 | + if isinstance(elements, ABITypedData) and elements.abi_type is not None: |
| 1007 | + formatted = await func(async_w3, *elements) |
| 1008 | + return ABITypedData(formatted) |
| 1009 | + else: |
| 1010 | + return elements |
| 1011 | + |
| 1012 | + return await async_recursive_map(async_w3, async_map_to_typed_data, data_tree) |
| 1013 | + |
| 1014 | + |
| 1015 | +@reject_recursive_repeats |
| 1016 | +async def async_recursive_map( |
| 1017 | + async_w3: "AsyncWeb3", |
| 1018 | + func: Callable[[Any], Coroutine[Any, Any, TReturn]], |
| 1019 | + data: Any, |
| 1020 | +) -> TReturn: |
| 1021 | + """ |
| 1022 | + Apply an awaitable method to data and any collection items inside data |
| 1023 | + (using async_map_collection). |
| 1024 | +
|
| 1025 | + Define the awaitable method so that it only applies to the type of value that you |
| 1026 | + want it to apply to. |
| 1027 | + """ |
| 1028 | + |
| 1029 | + async def async_recurse(item: Any) -> TReturn: |
| 1030 | + return await async_recursive_map(async_w3, func, item) |
| 1031 | + |
| 1032 | + items_mapped = await async_map_if_collection(async_recurse, data) |
| 1033 | + return await func(items_mapped) |
| 1034 | + |
| 1035 | + |
| 1036 | +async def async_map_if_collection( |
| 1037 | + func: Callable[[Any], Coroutine[Any, Any, Any]], value: Any |
| 1038 | +) -> Any: |
| 1039 | + """ |
| 1040 | + Apply an awaitable method to each element of a collection or value of a dictionary. |
| 1041 | + If the value is not a collection, return it unmodified. |
| 1042 | + """ |
| 1043 | + |
| 1044 | + datatype = type(value) |
| 1045 | + if isinstance(value, Mapping): |
| 1046 | + return datatype({key: await func(val) for key, val in value.values()}) |
| 1047 | + if is_string(value): |
| 1048 | + return value |
| 1049 | + elif isinstance(value, Iterable): |
| 1050 | + return datatype([await func(item) for item in value]) |
| 1051 | + else: |
| 1052 | + return value |
0 commit comments