From 24d215e5f7c44f99cef5e40c07ca2622afe14cf4 Mon Sep 17 00:00:00 2001 From: Marcello Dalponte <6449878+mdalp@users.noreply.github.com> Date: Tue, 2 Sep 2025 12:18:57 +0200 Subject: [PATCH 1/9] Lazy import deprecated websockets.legacy client to avoid raising a warning --- web3/providers/legacy_websocket.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/web3/providers/legacy_websocket.py b/web3/providers/legacy_websocket.py index a4907c3326..79c09a1f5e 100644 --- a/web3/providers/legacy_websocket.py +++ b/web3/providers/legacy_websocket.py @@ -1,7 +1,9 @@ +from __future__ import annotations import asyncio import json import logging import os +import typing from threading import ( Thread, ) @@ -21,10 +23,6 @@ from eth_typing import ( URI, ) -from websockets.legacy.client import ( - WebSocketClientProtocol, - connect, -) from web3._utils.batching import ( sort_batch_response_by_response_ids, @@ -43,6 +41,11 @@ RPCResponse, ) +if typing.TYPE_CHECKING: + from websockets.legacy.client import ( + WebSocketClientProtocol, + ) + RESTRICTED_WEBSOCKET_KWARGS = {"uri", "loop"} DEFAULT_WEBSOCKET_TIMEOUT = 30 @@ -72,6 +75,7 @@ def __init__(self, endpoint_uri: URI, websocket_kwargs: Any) -> None: async def __aenter__(self) -> WebSocketClientProtocol: if self.ws is None: + from websockets.legacy.client import connect self.ws = await connect(uri=self.endpoint_uri, **self.websocket_kwargs) return self.ws From 814657a9d50ce5ff375b4605ac42592460c2bddb Mon Sep 17 00:00:00 2001 From: Marcello Dalponte <6449878+mdalp@users.noreply.github.com> Date: Tue, 2 Sep 2025 13:13:29 +0200 Subject: [PATCH 2/9] Use recommended implementation of ws connection for websocket provider --- web3/providers/persistent/websocket.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/web3/providers/persistent/websocket.py b/web3/providers/persistent/websocket.py index f5035adf76..bb06910f24 100644 --- a/web3/providers/persistent/websocket.py +++ b/web3/providers/persistent/websocket.py @@ -1,7 +1,9 @@ +from __future__ import annotations import asyncio import json import logging import os +import typing from typing import ( Any, Dict, @@ -19,8 +21,7 @@ ConnectionClosedOK, WebSocketException, ) -from websockets.legacy.client import ( - WebSocketClientProtocol, +from websockets import ( connect, ) @@ -36,6 +37,9 @@ RPCResponse, ) +if typing.TYPE_CHECKING: + from websockets import ClientConnection + DEFAULT_PING_INTERVAL = 30 # 30 seconds DEFAULT_PING_TIMEOUT = 300 # 5 minutes @@ -57,12 +61,14 @@ class WebSocketProvider(PersistentConnectionProvider): logger = logging.getLogger("web3.providers.WebSocketProvider") is_async: bool = True + _ws: ClientConnection + def __init__( self, - endpoint_uri: Optional[Union[URI, str]] = None, - websocket_kwargs: Optional[Dict[str, Any]] = None, + endpoint_uri: URI | str | None = None, + websocket_kwargs: dict[str, Any] | None = None, # uses binary frames by default - use_text_frames: Optional[bool] = False, + use_text_frames: bool | None = False, # `PersistentConnectionProvider` kwargs can be passed through **kwargs: Any, ) -> None: @@ -72,7 +78,7 @@ def __init__( ) super().__init__(**kwargs) self.use_text_frames = use_text_frames - self._ws: Optional[WebSocketClientProtocol] = None + self._ws: ClientConnection | None = None if not any( self.endpoint_uri.startswith(prefix) @@ -119,7 +125,7 @@ async def socket_send(self, request_data: bytes) -> None: "Connection to websocket has not been initiated for the provider." ) - payload: Union[bytes, str] = request_data + payload: bytes | str = request_data if self.use_text_frames: payload = request_data.decode("utf-8") From 13c51da7022a6dac4f0d769c9a5214ac391f5c02 Mon Sep 17 00:00:00 2001 From: Marcello Dalponte <6449878+mdalp@users.noreply.github.com> Date: Tue, 2 Sep 2025 13:13:55 +0200 Subject: [PATCH 3/9] Use recommended implementation of ws connection for tests/utils.py --- tests/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/utils.py b/tests/utils.py index 4779d33658..63df4457de 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -5,7 +5,7 @@ from websockets import ( WebSocketException, ) -from websockets.legacy.client import ( +from websockets import ( connect, ) From d5dc1e4237b45f73be47f9ae6e2e36e45b739ca7 Mon Sep 17 00:00:00 2001 From: Marcello Dalponte <6449878+mdalp@users.noreply.github.com> Date: Tue, 2 Sep 2025 13:14:42 +0200 Subject: [PATCH 4/9] Remove out of scope typing from `AsyncBaseProvider` --- web3/providers/async_base.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/web3/providers/async_base.py b/web3/providers/async_base.py index b1f6314fa1..9d1b5b61dd 100644 --- a/web3/providers/async_base.py +++ b/web3/providers/async_base.py @@ -174,9 +174,6 @@ async def disconnect(self) -> None: "Persistent connection providers must implement this method" ) - # WebSocket typing - _ws: "WebSocketClientProtocol" - # IPC typing _reader: Optional[asyncio.StreamReader] _writer: Optional[asyncio.StreamWriter] From 68105d0ca4306be5bf1d5a62a2a44e1be79d1206 Mon Sep 17 00:00:00 2001 From: Marcello Dalponte <6449878+mdalp@users.noreply.github.com> Date: Tue, 2 Sep 2025 13:22:19 +0200 Subject: [PATCH 5/9] Remove a no-longer available check on _ws.closed as it's also redundant --- web3/providers/persistent/websocket.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web3/providers/persistent/websocket.py b/web3/providers/persistent/websocket.py index bb06910f24..15c0d861a1 100644 --- a/web3/providers/persistent/websocket.py +++ b/web3/providers/persistent/websocket.py @@ -142,7 +142,7 @@ async def _provider_specific_connect(self) -> None: async def _provider_specific_disconnect(self) -> None: # this should remain idempotent - if self._ws is not None and not self._ws.closed: + if self._ws is not None: await self._ws.close() self._ws = None From bdf5e5ade4f8edadff9cffe6da4839cb02e88215 Mon Sep 17 00:00:00 2001 From: Marcello Dalponte <6449878+mdalp@users.noreply.github.com> Date: Tue, 2 Sep 2025 13:25:23 +0200 Subject: [PATCH 6/9] Run pre-commit --- tests/utils.py | 2 -- web3/providers/async_base.py | 4 ---- web3/providers/legacy_websocket.py | 28 ++++++++++++++------------ web3/providers/persistent/websocket.py | 18 +++++++++-------- 4 files changed, 25 insertions(+), 27 deletions(-) diff --git a/tests/utils.py b/tests/utils.py index 63df4457de..827857d70c 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -4,8 +4,6 @@ from websockets import ( WebSocketException, -) -from websockets import ( connect, ) diff --git a/web3/providers/async_base.py b/web3/providers/async_base.py index 9d1b5b61dd..fa393b036d 100644 --- a/web3/providers/async_base.py +++ b/web3/providers/async_base.py @@ -54,10 +54,6 @@ ) if TYPE_CHECKING: - from websockets.legacy.client import ( - WebSocketClientProtocol, - ) - from web3 import ( # noqa: F401 AsyncWeb3, WebSocketProvider, diff --git a/web3/providers/legacy_websocket.py b/web3/providers/legacy_websocket.py index 79c09a1f5e..23cbab3b9f 100644 --- a/web3/providers/legacy_websocket.py +++ b/web3/providers/legacy_websocket.py @@ -1,22 +1,21 @@ -from __future__ import annotations +from __future__ import ( + annotations, +) + import asyncio import json import logging import os -import typing from threading import ( Thread, ) from types import ( TracebackType, ) +import typing from typing import ( Any, List, - Optional, - Tuple, - Type, - Union, cast, ) @@ -69,19 +68,22 @@ def get_default_endpoint() -> URI: class PersistentWebSocket: def __init__(self, endpoint_uri: URI, websocket_kwargs: Any) -> None: - self.ws: Optional[WebSocketClientProtocol] = None + self.ws: WebSocketClientProtocol | None = None self.endpoint_uri = endpoint_uri self.websocket_kwargs = websocket_kwargs async def __aenter__(self) -> WebSocketClientProtocol: if self.ws is None: - from websockets.legacy.client import connect + from websockets.legacy.client import ( + connect, + ) + self.ws = await connect(uri=self.endpoint_uri, **self.websocket_kwargs) return self.ws async def __aexit__( self, - exc_type: Type[BaseException], + exc_type: type[BaseException], exc_val: BaseException, exc_tb: TracebackType, ) -> None: @@ -99,8 +101,8 @@ class LegacyWebSocketProvider(JSONBaseProvider): def __init__( self, - endpoint_uri: Optional[Union[URI, str]] = None, - websocket_kwargs: Optional[Any] = None, + endpoint_uri: URI | str | None = None, + websocket_kwargs: Any | None = None, websocket_timeout: int = DEFAULT_WEBSOCKET_TIMEOUT, **kwargs: Any, ) -> None: @@ -148,8 +150,8 @@ def make_request(self, method: RPCEndpoint, params: Any) -> RPCResponse: return future.result() def make_batch_request( - self, requests: List[Tuple[RPCEndpoint, Any]] - ) -> List[RPCResponse]: + self, requests: list[tuple[RPCEndpoint, Any]] + ) -> list[RPCResponse]: self.logger.debug( "Making batch request WebSocket. URI: %s, Methods: %s", self.endpoint_uri, diff --git a/web3/providers/persistent/websocket.py b/web3/providers/persistent/websocket.py index 15c0d861a1..bf747c866b 100644 --- a/web3/providers/persistent/websocket.py +++ b/web3/providers/persistent/websocket.py @@ -1,4 +1,7 @@ -from __future__ import annotations +from __future__ import ( + annotations, +) + import asyncio import json import logging @@ -6,9 +9,6 @@ import typing from typing import ( Any, - Dict, - Optional, - Union, ) from eth_typing import ( @@ -17,13 +17,13 @@ from toolz import ( merge, ) +from websockets import ( + connect, +) from websockets.exceptions import ( ConnectionClosedOK, WebSocketException, ) -from websockets import ( - connect, -) from web3.exceptions import ( PersistentConnectionClosedOK, @@ -38,7 +38,9 @@ ) if typing.TYPE_CHECKING: - from websockets import ClientConnection + from websockets import ( + ClientConnection, + ) DEFAULT_PING_INTERVAL = 30 # 30 seconds DEFAULT_PING_TIMEOUT = 300 # 5 minutes From aa1f15584a64986aba491354a48138881b77f3f8 Mon Sep 17 00:00:00 2001 From: Marcello Dalponte <6449878+mdalp@users.noreply.github.com> Date: Tue, 2 Sep 2025 17:34:58 +0200 Subject: [PATCH 7/9] Fix import of ClientConnection for py3.8 --- web3/providers/persistent/websocket.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/web3/providers/persistent/websocket.py b/web3/providers/persistent/websocket.py index bf747c866b..c05560a101 100644 --- a/web3/providers/persistent/websocket.py +++ b/web3/providers/persistent/websocket.py @@ -6,7 +6,6 @@ import json import logging import os -import typing from typing import ( Any, ) @@ -17,7 +16,15 @@ from toolz import ( merge, ) -from websockets import ( + +# python3.8 supports up to version 13, +# which does not default to the asyncio implementation yet. +# For this reason connect and ClientConnection need to be imported +# from asyncio.client explicitly. +# When web3.py stops supporting python3.8, +# it'll be possible to use `from websockets import connect, ClientConnection`. +from websockets.asyncio.client import ( + ClientConnection, connect, ) from websockets.exceptions import ( @@ -37,11 +44,6 @@ RPCResponse, ) -if typing.TYPE_CHECKING: - from websockets import ( - ClientConnection, - ) - DEFAULT_PING_INTERVAL = 30 # 30 seconds DEFAULT_PING_TIMEOUT = 300 # 5 minutes From 304f994d238d90baeb9a17a53619aa72b05db2ea Mon Sep 17 00:00:00 2001 From: Marcello Dalponte <6449878+mdalp@users.noreply.github.com> Date: Tue, 2 Sep 2025 21:27:46 +0200 Subject: [PATCH 8/9] Add newsfragment --- newsfragments/3749.internal.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 newsfragments/3749.internal.rst diff --git a/newsfragments/3749.internal.rst b/newsfragments/3749.internal.rst new file mode 100644 index 0000000000..2c4a8c63ed --- /dev/null +++ b/newsfragments/3749.internal.rst @@ -0,0 +1 @@ +Remove websockets deprecation warning by using the asyncio websocket provider From b4739277339e21c38c00f64f08a0028bed2de4a7 Mon Sep 17 00:00:00 2001 From: Marcello Dalponte <6449878+mdalp@users.noreply.github.com> Date: Mon, 15 Sep 2025 11:45:37 +0200 Subject: [PATCH 9/9] Import TYPE_CHECKING explicitly --- web3/providers/legacy_websocket.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web3/providers/legacy_websocket.py b/web3/providers/legacy_websocket.py index 23cbab3b9f..0744124bc7 100644 --- a/web3/providers/legacy_websocket.py +++ b/web3/providers/legacy_websocket.py @@ -12,8 +12,8 @@ from types import ( TracebackType, ) -import typing from typing import ( + TYPE_CHECKING, Any, List, cast, @@ -40,7 +40,7 @@ RPCResponse, ) -if typing.TYPE_CHECKING: +if TYPE_CHECKING: from websockets.legacy.client import ( WebSocketClientProtocol, )