From 5fddf06387b9245bb980d31aa6731d4448c1d3af Mon Sep 17 00:00:00 2001 From: kenneth topp Date: Tue, 24 Sep 2024 19:44:27 -0400 Subject: [PATCH 1/5] Some optimizations for JSON --- tests/core/utilities/test_encoding.py | 63 +++++++++++++++++++++++-- web3/_utils/encoding.py | 41 +++++++++++----- web3/providers/__init__.py | 2 + web3/providers/async_base.py | 30 +++++------- web3/providers/base.py | 4 +- web3/providers/persistent/async_ipc.py | 4 +- web3/providers/persistent/persistent.py | 2 +- web3/providers/persistent/websocket.py | 2 +- 8 files changed, 108 insertions(+), 40 deletions(-) diff --git a/tests/core/utilities/test_encoding.py b/tests/core/utilities/test_encoding.py index 62a50a90d9..5d1b2e2fe1 100644 --- a/tests/core/utilities/test_encoding.py +++ b/tests/core/utilities/test_encoding.py @@ -38,6 +38,7 @@ Web3ValueError, ) from web3.providers import ( + AsyncJSONBaseProvider, JSONBaseProvider, ) @@ -144,8 +145,11 @@ def test_text_if_str_on_text(val): ), ( { - "date": [datetime.datetime.utcnow(), datetime.datetime.now()], - "other_date": datetime.datetime.utcnow().date(), + "date": [ + datetime.datetime.now(datetime.timezone.utc), + datetime.datetime.now(), + ], + "other_date": datetime.datetime.now(datetime.timezone.utc).date(), }, TypeError, "Could not encode to JSON: .*'other_date'.*is not JSON serializable", @@ -227,11 +231,11 @@ def test_text_if_str_on_text(val): def test_friendly_json_encode_with_web3_json_encoder(py_obj, exc_type, expected): if exc_type is None: assert literal_eval( - FriendlyJson().json_encode(py_obj, Web3JsonEncoder) + FriendlyJson.json_encode(py_obj, Web3JsonEncoder) ) == literal_eval(expected) else: with pytest.raises(exc_type, match=expected): - FriendlyJson().json_encode(py_obj) + FriendlyJson.json_encode(py_obj) @pytest.mark.parametrize( @@ -244,7 +248,7 @@ def test_friendly_json_encode_with_web3_json_encoder(py_obj, exc_type, expected) ), ) def test_friendly_json_decode(json_str, expected): - assert isinstance(FriendlyJson().json_decode(json_str), expected) + assert isinstance(FriendlyJson.json_decode(json_str), expected) @pytest.mark.parametrize( @@ -293,3 +297,52 @@ def test_encode_rpc_request(rpc_kwargs, exc_type, expected): rpc_kwargs["method"], rpc_kwargs["params"], ) + + +@pytest.mark.parametrize( + "rpc_response, expected", + ( + ( + '{"jsonrpc": "2.0", "method": "test_method", "params": [], "id": 1}', + {"jsonrpc": "2.0", "method": "test_method", "params": [], "id": 1}, + ), + ), +) +def test_decode_asyncbase_rpc_response(rpc_response, expected): + assert ( + AsyncJSONBaseProvider().decode_rpc_response(rpc_response.encode("utf8")) + == expected + ) + + +@pytest.mark.parametrize( + "rpc_kwargs, exc_type, expected", + ( + ( + {"id": 1, "method": "test", "params": [], "jsonrpc": "2.0"}, + None, + '{"id": 0, "method": "test", "params": [], "jsonrpc": "2.0",}', + ), + ( + { + "id": 0, + "method": "test", + "params": [datetime.datetime(2018, 5, 10, 1, 5, 10)], + }, + TypeError, + r"Could not encode to JSON: .*'params'.* is not JSON serializable", + ), + ), +) +def test_encode_asyncbase_rpc_request(rpc_kwargs, exc_type, expected): + if exc_type is None: + res = AsyncJSONBaseProvider().encode_rpc_request( + rpc_kwargs["method"], rpc_kwargs["params"] + ) + assert literal_eval(res) == literal_eval(expected) + else: + with pytest.raises(exc_type, match=expected): + JSONBaseProvider().encode_rpc_request( + rpc_kwargs["method"], + rpc_kwargs["params"], + ) diff --git a/web3/_utils/encoding.py b/web3/_utils/encoding.py index 74d13b8cb2..a6a7f70b54 100644 --- a/web3/_utils/encoding.py +++ b/web3/_utils/encoding.py @@ -191,41 +191,57 @@ class FriendlyJsonSerde: helpful information in the raised error messages. """ - def _json_mapping_errors(self, mapping: Dict[Any, Any]) -> Iterable[str]: + @classmethod + def _json_mapping_errors( + self, + mapping: Dict[Any, Any], + encoder_cls: Optional[Type[json.JSONEncoder]] = None, + ) -> Iterable[str]: for key, val in mapping.items(): try: - self._friendly_json_encode(val) + self._friendly_json_encode(val, encoder_cls=encoder_cls) except TypeError as exc: yield f"{key!r}: because ({exc})" - def _json_list_errors(self, iterable: Iterable[Any]) -> Iterable[str]: + @classmethod + def _json_list_errors( + self, + iterable: Iterable[Any], + encoder_cls: Optional[Type[json.JSONEncoder]] = None, + ) -> Iterable[str]: for index, element in enumerate(iterable): try: - self._friendly_json_encode(element) + self._friendly_json_encode(element, encoder_cls=encoder_cls) except TypeError as exc: yield f"{index}: because ({exc})" + @classmethod def _friendly_json_encode( - self, obj: Dict[Any, Any], cls: Optional[Type[json.JSONEncoder]] = None + cls, obj: Dict[Any, Any], encoder_cls: Optional[Type[json.JSONEncoder]] = None ) -> str: try: - encoded = json.dumps(obj, cls=cls) + encoded = json.dumps(obj, cls=encoder_cls, separators=(",", ":")) return encoded except TypeError as full_exception: if hasattr(obj, "items"): - item_errors = "; ".join(self._json_mapping_errors(obj)) + item_errors = "; ".join( + cls._json_mapping_errors(obj, encoder_cls=encoder_cls) + ) raise Web3TypeError( f"dict had unencodable value at keys: {{{item_errors}}}" ) elif is_list_like(obj): - element_errors = "; ".join(self._json_list_errors(obj)) + element_errors = "; ".join( + cls._json_list_errors(obj, encoder_cls=encoder_cls) + ) raise Web3TypeError( f"list had unencodable value at index: [{element_errors}]" ) else: raise full_exception - def json_decode(self, json_str: str) -> Dict[Any, Any]: + @classmethod + def json_decode(cls, json_str: str) -> Dict[Any, Any]: try: decoded = json.loads(json_str) return decoded @@ -235,11 +251,12 @@ def json_decode(self, json_str: str) -> Dict[Any, Any]: # so we have to re-raise the same type. raise json.decoder.JSONDecodeError(err_msg, exc.doc, exc.pos) + @classmethod def json_encode( - self, obj: Dict[Any, Any], cls: Optional[Type[json.JSONEncoder]] = None + cls, obj: Dict[Any, Any], encoder_cls: Optional[Type[json.JSONEncoder]] = None ) -> str: try: - return self._friendly_json_encode(obj, cls=cls) + return cls._friendly_json_encode(obj, encoder_cls=encoder_cls) except TypeError as exc: raise Web3TypeError(f"Could not encode to JSON: {exc}") @@ -306,4 +323,4 @@ def to_json(obj: Dict[Any, Any]) -> str: """ Convert a complex object (like a transaction object) to a JSON string """ - return FriendlyJsonSerde().json_encode(obj, cls=Web3JsonEncoder) + return FriendlyJsonSerde.json_encode(obj, encoder_cls=Web3JsonEncoder) diff --git a/web3/providers/__init__.py b/web3/providers/__init__.py index a07b65b90b..afe1cffcac 100644 --- a/web3/providers/__init__.py +++ b/web3/providers/__init__.py @@ -1,5 +1,6 @@ from .async_base import ( AsyncBaseProvider, + AsyncJSONBaseProvider, ) from .rpc import ( AsyncHTTPProvider, @@ -33,6 +34,7 @@ __all__ = [ "AsyncBaseProvider", + "AsyncJSONBaseProvider", "AsyncEthereumTesterProvider", "AsyncHTTPProvider", "AsyncIPCProvider", diff --git a/web3/providers/async_base.py b/web3/providers/async_base.py index 5a4f3f415f..7525185821 100644 --- a/web3/providers/async_base.py +++ b/web3/providers/async_base.py @@ -175,23 +175,24 @@ def __init__(self, **kwargs: Any) -> None: self.request_counter = itertools.count() super().__init__(**kwargs) - def encode_rpc_request(self, method: RPCEndpoint, params: Any) -> bytes: - request_id = next(self.request_counter) - rpc_dict = { + def _build_rpc_dict(self, method: RPCEndpoint, params: Any) -> dict: + return { "jsonrpc": "2.0", "method": method, "params": params or [], - "id": request_id, + "id": next(self.request_counter), } - encoded = FriendlyJsonSerde().json_encode(rpc_dict, cls=Web3JsonEncoder) - return to_bytes(text=encoded) + + def encode_rpc_request(self, method: RPCEndpoint, params: Any) -> str: + rpc_dict = self._build_rpc_dict(method, params) + return FriendlyJsonSerde.json_encode(rpc_dict, encoder_cls=Web3JsonEncoder) @staticmethod def decode_rpc_response(raw_response: bytes) -> RPCResponse: text_response = str( to_text(raw_response) if not is_text(raw_response) else raw_response ) - return cast(RPCResponse, FriendlyJsonSerde().json_decode(text_response)) + return cast(RPCResponse, FriendlyJsonSerde.json_decode(text_response)) async def is_connected(self, show_traceback: bool = False) -> bool: try: @@ -219,13 +220,8 @@ async def is_connected(self, show_traceback: bool = False) -> bool: # -- batch requests -- # - def encode_batch_rpc_request( - self, requests: List[Tuple[RPCEndpoint, Any]] - ) -> bytes: - return ( - b"[" - + b", ".join( - self.encode_rpc_request(method, params) for method, params in requests - ) - + b"]" - ) + def encode_batch_rpc_request(self, requests: List[Tuple[RPCEndpoint, Any]]) -> str: + rpc_dicts = [ + self._build_rpc_dict(method, params) for method, params in requests + ] + return FriendlyJsonSerde.json_encode(rpc_dicts, encoder_cls=Web3JsonEncoder) diff --git a/web3/providers/base.py b/web3/providers/base.py index b653f267bc..adb4a0a30e 100644 --- a/web3/providers/base.py +++ b/web3/providers/base.py @@ -131,13 +131,13 @@ def encode_rpc_request(self, method: RPCEndpoint, params: Any) -> bytes: "params": params or [], "id": next(self.request_counter), } - encoded = FriendlyJsonSerde().json_encode(rpc_dict, Web3JsonEncoder) + encoded = FriendlyJsonSerde.json_encode(rpc_dict, encoder_cls=Web3JsonEncoder) return to_bytes(text=encoded) @staticmethod def decode_rpc_response(raw_response: bytes) -> RPCResponse: text_response = to_text(raw_response) - return cast(RPCResponse, FriendlyJsonSerde().json_decode(text_response)) + return cast(RPCResponse, FriendlyJsonSerde.json_decode(text_response)) def is_connected(self, show_traceback: bool = False) -> bool: try: diff --git a/web3/providers/persistent/async_ipc.py b/web3/providers/persistent/async_ipc.py index 6b09cae002..dd9b889d4c 100644 --- a/web3/providers/persistent/async_ipc.py +++ b/web3/providers/persistent/async_ipc.py @@ -86,14 +86,14 @@ async def is_connected(self, show_traceback: bool = False) -> bool: ) return False - async def socket_send(self, request_data: bytes) -> None: + async def socket_send(self, request_data: str) -> None: if self._writer is None: raise ProviderConnectionError( "Connection to ipc socket has not been initiated for the provider." ) return await asyncio.wait_for( - self._socket_send(request_data), timeout=self.request_timeout + self._socket_send(request_data.encode()), timeout=self.request_timeout ) async def socket_recv(self) -> RPCResponse: diff --git a/web3/providers/persistent/persistent.py b/web3/providers/persistent/persistent.py index 280ac160ab..effda86df4 100644 --- a/web3/providers/persistent/persistent.py +++ b/web3/providers/persistent/persistent.py @@ -164,7 +164,7 @@ async def make_batch_request( # -- abstract methods -- # @abstractmethod - async def socket_send(self, request_data: bytes) -> None: + async def socket_send(self, request_data: str) -> None: """ Send an encoded RPC request to the provider over the persistent connection. """ diff --git a/web3/providers/persistent/websocket.py b/web3/providers/persistent/websocket.py index 258ce22391..5a7eefd6e8 100644 --- a/web3/providers/persistent/websocket.py +++ b/web3/providers/persistent/websocket.py @@ -113,7 +113,7 @@ async def is_connected(self, show_traceback: bool = False) -> bool: ) from e return False - async def socket_send(self, request_data: bytes) -> None: + async def socket_send(self, request_data: str) -> None: if self._ws is None: raise ProviderConnectionError( "Connection to websocket has not been initiated for the provider." From acea67e0ca216f5ec30bc808d35860faf4f4ce66 Mon Sep 17 00:00:00 2001 From: kenneth topp Date: Tue, 24 Sep 2024 23:29:32 -0400 Subject: [PATCH 2/5] typing gymnastics, and ensure async http still uses bytes. --- web3/_utils/encoding.py | 14 +++++++++++--- web3/providers/__init__.py | 12 +++++------- web3/providers/async_base.py | 3 +-- web3/providers/auto.py | 10 ++++++++-- web3/providers/rpc/async_rpc.py | 4 ++-- 5 files changed, 27 insertions(+), 16 deletions(-) diff --git a/web3/_utils/encoding.py b/web3/_utils/encoding.py index a6a7f70b54..e5ee5e913d 100644 --- a/web3/_utils/encoding.py +++ b/web3/_utils/encoding.py @@ -1,4 +1,7 @@ # String encodings and numeric representations +from collections.abc import ( + Mapping, +) import json import re from typing import ( @@ -6,6 +9,7 @@ Callable, Dict, Iterable, + List, Optional, Sequence, Type, @@ -217,13 +221,15 @@ def _json_list_errors( @classmethod def _friendly_json_encode( - cls, obj: Dict[Any, Any], encoder_cls: Optional[Type[json.JSONEncoder]] = None + cls, + obj: Dict[Any, Any] | List[Dict[Any, Any]], + encoder_cls: Optional[Type[json.JSONEncoder]] = None, ) -> str: try: encoded = json.dumps(obj, cls=encoder_cls, separators=(",", ":")) return encoded except TypeError as full_exception: - if hasattr(obj, "items"): + if isinstance(obj, Mapping): item_errors = "; ".join( cls._json_mapping_errors(obj, encoder_cls=encoder_cls) ) @@ -253,7 +259,9 @@ def json_decode(cls, json_str: str) -> Dict[Any, Any]: @classmethod def json_encode( - cls, obj: Dict[Any, Any], encoder_cls: Optional[Type[json.JSONEncoder]] = None + cls, + obj: Dict[Any, Any] | List[Dict[Any, Any]], + encoder_cls: Optional[Type[json.JSONEncoder]] = None, ) -> str: try: return cls._friendly_json_encode(obj, encoder_cls=encoder_cls) diff --git a/web3/providers/__init__.py b/web3/providers/__init__.py index afe1cffcac..c919ce63d6 100644 --- a/web3/providers/__init__.py +++ b/web3/providers/__init__.py @@ -2,8 +2,8 @@ AsyncBaseProvider, AsyncJSONBaseProvider, ) -from .rpc import ( - AsyncHTTPProvider, +from .auto import ( + AutoProvider, ) from .base import ( BaseProvider, @@ -16,9 +16,6 @@ from .ipc import ( IPCProvider, ) -from .rpc import ( - HTTPProvider, -) from .legacy_websocket import ( LegacyWebSocketProvider, ) @@ -28,8 +25,9 @@ PersistentConnectionProvider, WebSocketProvider, ) -from .auto import ( - AutoProvider, +from .rpc import ( + AsyncHTTPProvider, + HTTPProvider, ) __all__ = [ diff --git a/web3/providers/async_base.py b/web3/providers/async_base.py index 7525185821..d0f978e007 100644 --- a/web3/providers/async_base.py +++ b/web3/providers/async_base.py @@ -15,7 +15,6 @@ from eth_utils import ( is_text, - to_bytes, to_text, ) @@ -175,7 +174,7 @@ def __init__(self, **kwargs: Any) -> None: self.request_counter = itertools.count() super().__init__(**kwargs) - def _build_rpc_dict(self, method: RPCEndpoint, params: Any) -> dict: + def _build_rpc_dict(self, method: RPCEndpoint, params: Any) -> dict[str, Any]: return { "jsonrpc": "2.0", "method": method, diff --git a/web3/providers/auto.py b/web3/providers/auto.py index af83533ac7..7d7ebe4c08 100644 --- a/web3/providers/auto.py +++ b/web3/providers/auto.py @@ -20,12 +20,18 @@ from web3.exceptions import ( CannotHandleRequest, ) -from web3.providers import ( +from web3.providers.base import ( BaseProvider, - HTTPProvider, +) +from web3.providers.ipc import ( IPCProvider, +) +from web3.providers.legacy_websocket import ( LegacyWebSocketProvider, ) +from web3.providers.rpc import ( + HTTPProvider, +) from web3.types import ( RPCEndpoint, RPCResponse, diff --git a/web3/providers/rpc/async_rpc.py b/web3/providers/rpc/async_rpc.py index 8f723138eb..4a441dbba5 100644 --- a/web3/providers/rpc/async_rpc.py +++ b/web3/providers/rpc/async_rpc.py @@ -158,7 +158,7 @@ async def make_request(self, method: RPCEndpoint, params: Any) -> RPCResponse: f"Making request HTTP. URI: {self.endpoint_uri}, Method: {method}" ) request_data = self.encode_rpc_request(method, params) - raw_response = await self._make_request(method, request_data) + raw_response = await self._make_request(method, request_data.encode()) response = self.decode_rpc_response(raw_response) self.logger.debug( f"Getting response HTTP. URI: {self.endpoint_uri}, " @@ -172,7 +172,7 @@ async def make_batch_request( self.logger.debug(f"Making batch request HTTP - uri: `{self.endpoint_uri}`") request_data = self.encode_batch_rpc_request(batch_requests) raw_response = await self._request_session_manager.async_make_post_request( - self.endpoint_uri, request_data, **self.get_request_kwargs() + self.endpoint_uri, request_data.encode(), **self.get_request_kwargs() ) self.logger.debug("Received batch response HTTP.") responses_list = cast(List[RPCResponse], self.decode_rpc_response(raw_response)) From 85cc106ff1400bfa53c99682a710f8ed932b56a7 Mon Sep 17 00:00:00 2001 From: kenneth topp Date: Tue, 24 Sep 2024 23:39:19 -0400 Subject: [PATCH 3/5] make typing 3.8 compatable --- web3/_utils/encoding.py | 4 ++-- web3/providers/async_base.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/web3/_utils/encoding.py b/web3/_utils/encoding.py index e5ee5e913d..d8567e1a81 100644 --- a/web3/_utils/encoding.py +++ b/web3/_utils/encoding.py @@ -222,7 +222,7 @@ def _json_list_errors( @classmethod def _friendly_json_encode( cls, - obj: Dict[Any, Any] | List[Dict[Any, Any]], + obj: Union[Dict[Any, Any], List[Dict[Any, Any]]], encoder_cls: Optional[Type[json.JSONEncoder]] = None, ) -> str: try: @@ -260,7 +260,7 @@ def json_decode(cls, json_str: str) -> Dict[Any, Any]: @classmethod def json_encode( cls, - obj: Dict[Any, Any] | List[Dict[Any, Any]], + obj: Union[Dict[Any, Any], List[Dict[Any, Any]]], encoder_cls: Optional[Type[json.JSONEncoder]] = None, ) -> str: try: diff --git a/web3/providers/async_base.py b/web3/providers/async_base.py index d0f978e007..8ea82a08b9 100644 --- a/web3/providers/async_base.py +++ b/web3/providers/async_base.py @@ -6,6 +6,7 @@ Any, Callable, Coroutine, + Dict, List, Optional, Set, @@ -174,7 +175,7 @@ def __init__(self, **kwargs: Any) -> None: self.request_counter = itertools.count() super().__init__(**kwargs) - def _build_rpc_dict(self, method: RPCEndpoint, params: Any) -> dict[str, Any]: + def _build_rpc_dict(self, method: RPCEndpoint, params: Any) -> Dict[str, Any]: return { "jsonrpc": "2.0", "method": method, From 7504d87aad9a60427ccec429a6eebbe84d2aca27 Mon Sep 17 00:00:00 2001 From: kenneth topp Date: Tue, 24 Sep 2024 23:50:59 -0400 Subject: [PATCH 4/5] remove spaces from test --- tests/core/web3-module/test_conversions.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/tests/core/web3-module/test_conversions.py b/tests/core/web3-module/test_conversions.py index ff1b75025b..0f63ce2737 100644 --- a/tests/core/web3-module/test_conversions.py +++ b/tests/core/web3-module/test_conversions.py @@ -1,5 +1,4 @@ import pytest - from hexbytes import ( HexBytes, ) @@ -159,7 +158,7 @@ def test_to_int_hexstr(val, expected): (b"\x01", "0x01"), (b"\x10", "0x10"), (b"\x01\x00", "0x0100"), - (b"\x00\x0F", "0x000f"), + (b"\x00\x0f", "0x000f"), (b"", "0x"), (0, "0x0"), (1, "0x1"), @@ -204,13 +203,13 @@ def test_to_hex_cleanup_only(val, expected): @pytest.mark.parametrize( "val, expected", ( - (AttributeDict({"one": HexBytes("0x1")}), '{"one": "0x01"}'), - (AttributeDict({"two": HexBytes(2)}), '{"two": "0x02"}'), + (AttributeDict({"one": HexBytes("0x1")}), '{"one":"0x01"}'), + (AttributeDict({"two": HexBytes(2)}), '{"two":"0x02"}'), ( AttributeDict({"three": AttributeDict({"four": 4})}), - '{"three": {"four": 4}}', + '{"three":{"four":4}}', ), - ({"three": 3}, '{"three": 3}'), + ({"three": 3}, '{"three":3}'), ), ) def test_to_json(val, expected): @@ -247,7 +246,7 @@ def test_to_json(val, expected): "value": 2907000000000000, } ), - '{"blockHash": "0x849044202a39ae36888481f90d62c3826bca8269c2716d7a38696b4f45e61d83", "blockNumber": 6928809, "from": "0xDEA141eF43A2fdF4e795adA55958DAf8ef5FA619", "gas": 21000, "gasPrice": 19110000000, "hash": "0x1ccddd19830e998d7cf4d921b19fafd5021c9d4c4ba29680b66fb535624940fc", "input": "0x", "nonce": 5522, "r": "0x71ef3eed6242230a219d9dc7737cb5a3a16059708ee322e96b8c5774105b9b00", "s": "0x48a076afe10b4e1ae82ef82b747e9be64e0bbb1cc90e173db8d53e7baba8ac46", "to": "0x3a84E09D30476305Eda6b2DA2a4e199E2Dd1bf79", "transactionIndex": 8, "v": 27, "value": 2907000000000000}', # noqa: E501 + '{"blockHash":"0x849044202a39ae36888481f90d62c3826bca8269c2716d7a38696b4f45e61d83","blockNumber":6928809,"from":"0xDEA141eF43A2fdF4e795adA55958DAf8ef5FA619","gas":21000,"gasPrice":19110000000,"hash":"0x1ccddd19830e998d7cf4d921b19fafd5021c9d4c4ba29680b66fb535624940fc","input":"0x","nonce":5522,"r":"0x71ef3eed6242230a219d9dc7737cb5a3a16059708ee322e96b8c5774105b9b00","s":"0x48a076afe10b4e1ae82ef82b747e9be64e0bbb1cc90e173db8d53e7baba8ac46","to":"0x3a84E09D30476305Eda6b2DA2a4e199E2Dd1bf79","transactionIndex":8,"v":27,"value":2907000000000000}', # noqa: E501 ), ), ) From 77c48113e728c77705909eeb7e224beea5187aea Mon Sep 17 00:00:00 2001 From: kenneth topp Date: Tue, 24 Sep 2024 23:59:57 -0400 Subject: [PATCH 5/5] shakes fist at sky. --- tests/core/web3-module/test_conversions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/core/web3-module/test_conversions.py b/tests/core/web3-module/test_conversions.py index 0f63ce2737..c660c6b618 100644 --- a/tests/core/web3-module/test_conversions.py +++ b/tests/core/web3-module/test_conversions.py @@ -1,4 +1,5 @@ import pytest + from hexbytes import ( HexBytes, )