diff --git a/docs/providers.rst b/docs/providers.rst index 1fc8503216..cd4d437871 100644 --- a/docs/providers.rst +++ b/docs/providers.rst @@ -65,7 +65,26 @@ Valid formats for this environment variable are: - ``file:///path/to/node/rpc-json/file.ipc`` - ``http://192.168.1.2:8545`` - ``https://node.ontheweb.com`` -- ``ws://127.0.0.1:8546`` +- ``ws://127.0.0.1:8546`` (requires ``AsyncWeb3``) +- ``wss://mainnet.infura.io/ws/v3/YOUR_KEY`` (requires ``AsyncWeb3``) + +For WebSocket connections (``ws://`` or ``wss://``), you can also use the +``WEB3_WS_PROVIDER_URI`` environment variable. Note that WebSocket providers +are asynchronous and require ``AsyncWeb3`` with ``AsyncAutoProvider``: + +.. code-block:: python + + >>> import asyncio + >>> from web3 import AsyncWeb3, AsyncAutoProvider + + >>> async def main(): + ... # AsyncAutoProvider will automatically detect WEB3_PROVIDER_URI + ... # or WEB3_WS_PROVIDER_URI and use the appropriate async provider + ... w3 = AsyncWeb3(AsyncAutoProvider()) + ... if await w3.is_connected(): + ... print(await w3.eth.block_number) + + >>> asyncio.run(main()) Auto-initialization Provider Shortcuts @@ -530,9 +549,85 @@ Interacting with the Persistent Connection AutoProvider ~~~~~~~~~~~~ -:class:`~web3.providers.auto.AutoProvider` is the default used when initializing -:class:`web3.Web3` without any providers. There's rarely a reason to use it -explicitly. +.. py:class:: web3.providers.auto.AutoProvider(potential_providers=None) + + :class:`~web3.providers.auto.AutoProvider` is the default used when initializing + :class:`web3.Web3` without any providers. It automatically detects and connects + to available synchronous providers. + + * ``potential_providers`` is an optional ordered sequence of provider classes + or functions to attempt connection with. If not specified, defaults to + ``(load_provider_from_environment, IPCProvider, HTTPProvider)``. + + ``AutoProvider`` will iterate through the list of potential providers and use + the first one that successfully connects to a node. + + .. code-block:: python + + >>> from web3 import Web3 + + # These are equivalent - AutoProvider is used by default + >>> w3 = Web3() + >>> w3 = Web3(Web3.AutoProvider()) + + .. note:: + + ``AutoProvider`` only supports synchronous providers (HTTP, IPC). For + WebSocket connections, use ``AsyncAutoProvider`` with ``AsyncWeb3``. + + +AsyncAutoProvider +~~~~~~~~~~~~~~~~~ + +.. py:class:: web3.providers.auto.AsyncAutoProvider(potential_providers=None) + + :class:`~web3.providers.auto.AsyncAutoProvider` is the asynchronous equivalent + of ``AutoProvider``. It automatically detects and connects to available + asynchronous providers, including WebSocket. + + * ``potential_providers`` is an optional ordered sequence of async provider + classes or functions to attempt connection with. If not specified, defaults to + ``(load_async_provider_from_environment, AsyncHTTPProvider)``. + + ``AsyncAutoProvider`` checks the ``WEB3_PROVIDER_URI`` and ``WEB3_WS_PROVIDER_URI`` + environment variables and automatically selects the appropriate async provider + based on the URI scheme: + + - ``http://`` or ``https://`` → ``AsyncHTTPProvider`` + - ``ws://`` or ``wss://`` → ``WebSocketProvider`` + - ``file://`` → ``AsyncIPCProvider`` + + .. code-block:: python + + >>> import asyncio + >>> from web3 import AsyncWeb3, AsyncAutoProvider + + >>> async def main(): + ... w3 = AsyncWeb3(AsyncAutoProvider()) + ... if await w3.is_connected(): + ... block = await w3.eth.block_number + ... print(f"Current block: {block}") + + >>> asyncio.run(main()) + + For WebSocket connections that require explicit connection management, you can + combine ``AsyncAutoProvider`` with the context manager pattern: + + .. code-block:: python + + >>> import os + >>> import asyncio + >>> from web3 import AsyncWeb3, AsyncAutoProvider + + >>> os.environ["WEB3_PROVIDER_URI"] = "wss://mainnet.infura.io/ws/v3/YOUR_KEY" + + >>> async def main(): + ... # Note: For WebSocket URIs, AsyncAutoProvider returns a WebSocketProvider + ... # which requires explicit connection. Use AsyncWeb3 context manager: + ... async with AsyncWeb3(AsyncAutoProvider()) as w3: + ... print(await w3.eth.block_number) + + >>> asyncio.run(main()) .. py:currentmodule:: web3.providers.eth_tester diff --git a/newsfragments/3704.feature.rst b/newsfragments/3704.feature.rst new file mode 100644 index 0000000000..d43df0da1e --- /dev/null +++ b/newsfragments/3704.feature.rst @@ -0,0 +1 @@ +Introduce ``AsyncAutoProvider`` for automatic detection of async providers (AsyncHTTP, WebSocket, AsyncIPC) and ``load_async_provider_from_uri`` / ``load_async_provider_from_environment`` helpers. diff --git a/tests/core/providers/test_auto_provider.py b/tests/core/providers/test_auto_provider.py index 9ef35dfb7f..84580aa35d 100644 --- a/tests/core/providers/test_auto_provider.py +++ b/tests/core/providers/test_auto_provider.py @@ -1,12 +1,21 @@ import pytest import os +from web3.exceptions import ( + Web3ValidationError, +) from web3.providers import ( + AsyncAutoProvider, + AsyncHTTPProvider, HTTPProvider, IPCProvider, + WebSocketProvider, ) from web3.providers.auto import ( + load_async_provider_from_environment, + load_async_provider_from_uri, load_provider_from_environment, + load_provider_from_uri, ) from web3.providers.ipc import ( get_dev_ipc_path, @@ -65,3 +74,102 @@ def test_get_dev_ipc_path(monkeypatch, tmp_path): monkeypatch.setenv("WEB3_PROVIDER_URI", uri) path = get_dev_ipc_path() assert path == uri + + +@pytest.mark.parametrize( + "ws_uri", + ( + "ws://localhost:8546", + "wss://mainnet.infura.io/ws/v3/YOUR_KEY", + ), +) +def test_load_provider_from_uri_raises_for_websocket(ws_uri): + """ + Test that load_provider_from_uri raises Web3ValidationError for ws/wss URIs. + + WebSocket requires async provider, so sync load_provider_from_uri should fail. + """ + with pytest.raises(Web3ValidationError) as exc_info: + load_provider_from_uri(ws_uri) + + assert "WebSocket URI" in str(exc_info.value) + assert "AsyncAutoProvider" in str(exc_info.value) + + +def test_load_provider_from_env_raises_for_websocket(monkeypatch): + """ + Test that load_provider_from_environment raises for WebSocket URIs. + """ + monkeypatch.setenv("WEB3_PROVIDER_URI", "ws://localhost:8546") + with pytest.raises(Web3ValidationError): + load_provider_from_environment() + + +@pytest.mark.parametrize( + "uri, expected_type", + ( + ("http://1.2.3.4:5678", AsyncHTTPProvider), + ("https://node.ontheweb.com", AsyncHTTPProvider), + ("ws://localhost:8546", WebSocketProvider), + ("wss://mainnet.infura.io/ws/v3/KEY", WebSocketProvider), + ), +) +def test_load_async_provider_from_uri(uri, expected_type): + """ + Test that load_async_provider_from_uri correctly identifies provider types. + """ + provider = load_async_provider_from_uri(uri) + assert isinstance(provider, expected_type) + + +@pytest.mark.parametrize( + "uri, expected_type, env_var", + ( + ("http://1.2.3.4:5678", AsyncHTTPProvider, "WEB3_PROVIDER_URI"), + ("ws://localhost:8546", WebSocketProvider, "WEB3_PROVIDER_URI"), + ("wss://localhost:8546", WebSocketProvider, "WEB3_WS_PROVIDER_URI"), + ), +) +def test_load_async_provider_from_env(monkeypatch, uri, expected_type, env_var): + """ + Test that load_async_provider_from_environment correctly loads async providers. + """ + monkeypatch.setenv(env_var, uri) + provider = load_async_provider_from_environment() + assert isinstance(provider, expected_type) + + +def test_load_async_provider_from_env_returns_none(monkeypatch): + """ + Test that load_async_provider_from_environment returns None when no env vars set. + """ + monkeypatch.delenv("WEB3_PROVIDER_URI", raising=False) + monkeypatch.delenv("WEB3_WS_PROVIDER_URI", raising=False) + provider = load_async_provider_from_environment() + assert provider is None + + +def test_async_auto_provider_default_providers(): + """ + Test that AsyncAutoProvider has correct default providers. + """ + provider = AsyncAutoProvider() + assert provider._potential_providers is not None + assert len(provider._potential_providers) > 0 + + +def test_async_auto_provider_custom_providers(): + """ + Test that AsyncAutoProvider accepts custom providers. + """ + custom_providers = (AsyncHTTPProvider,) + provider = AsyncAutoProvider(potential_providers=custom_providers) + assert provider._potential_providers == custom_providers + + +def test_async_auto_provider_is_async(): + """ + Test that AsyncAutoProvider is marked as async. + """ + provider = AsyncAutoProvider() + assert provider.is_async is True diff --git a/web3/__init__.py b/web3/__init__.py index f43f786d83..70b3b1d984 100644 --- a/web3/__init__.py +++ b/web3/__init__.py @@ -10,6 +10,7 @@ Web3, ) from web3.providers import ( + AsyncAutoProvider, AsyncBaseProvider, AutoProvider, BaseProvider, @@ -41,6 +42,7 @@ "AsyncWeb3", "Web3", # providers: + "AsyncAutoProvider", "AsyncBaseProvider", "AsyncEthereumTesterProvider", "AsyncHTTPProvider", diff --git a/web3/providers/__init__.py b/web3/providers/__init__.py index a422cf9116..a5d9055480 100644 --- a/web3/providers/__init__.py +++ b/web3/providers/__init__.py @@ -25,10 +25,12 @@ WebSocketProvider, ) from .auto import ( + AsyncAutoProvider, AutoProvider, ) __all__ = [ + "AsyncAutoProvider", "AsyncBaseProvider", "AsyncEthereumTesterProvider", "AsyncHTTPProvider", diff --git a/web3/providers/auto.py b/web3/providers/auto.py index 8742a148b2..3e60714a91 100644 --- a/web3/providers/auto.py +++ b/web3/providers/auto.py @@ -14,12 +14,19 @@ from web3.exceptions import ( CannotHandleRequest, + Web3ValidationError, ) from web3.providers import ( HTTPProvider, IPCProvider, JSONBaseProvider, ) +from web3.providers.async_base import ( + AsyncJSONBaseProvider, +) +from web3.providers.rpc import ( + AsyncHTTPProvider, +) from web3.types import ( RPCEndpoint, RPCResponse, @@ -30,6 +37,21 @@ def load_provider_from_environment() -> JSONBaseProvider | None: + """ + Load a synchronous provider from the WEB3_PROVIDER_URI environment variable. + + Returns + ------- + JSONBaseProvider | None + JSONBaseProvider or None if the environment variable is not set. + + Raises + ------ + Web3ValidationError + If the URI contains a WebSocket scheme (ws/wss), + which requires AsyncAutoProvider. + + """ uri_string = URI(os.environ.get("WEB3_PROVIDER_URI", "")) if not uri_string: return None @@ -40,11 +62,121 @@ def load_provider_from_environment() -> JSONBaseProvider | None: def load_provider_from_uri( uri_string: URI, headers: dict[str, tuple[str, str]] | None = None ) -> JSONBaseProvider: + """ + Create a synchronous provider based on a URI string. + + Parameters + ---------- + uri_string : URI + URI string for connecting to the node. + headers : dict[str, tuple[str, str]] | None, optional + Optional HTTP headers for HTTPProvider. + + Returns + ------- + JSONBaseProvider + An instance of JSONBaseProvider (IPCProvider or HTTPProvider). + + Raises + ------ + Web3ValidationError + If the URI contains a WebSocket scheme (ws/wss). + NotImplementedError + If the URI scheme is not supported. + + """ uri = urlparse(uri_string) + if uri.scheme == "file": return IPCProvider(uri.path) elif uri.scheme in HTTP_SCHEMES: return HTTPProvider(uri_string, headers) + elif uri.scheme in WS_SCHEMES: + raise Web3ValidationError( + f"WebSocket URI '{uri_string}' requires an async provider. " + "Use AsyncAutoProvider with AsyncWeb3 instead of AutoProvider with Web3. " + "Example: async with AsyncWeb3(AsyncAutoProvider()) as w3: ..." + ) + else: + raise NotImplementedError( + "Web3 does not know how to connect to scheme " + f"{uri.scheme!r} in {uri_string!r}" + ) + + +def load_async_provider_from_environment() -> AsyncJSONBaseProvider | None: + """ + Load an asynchronous provider from environment variables. + + Checks WEB3_PROVIDER_URI and WEB3_WS_PROVIDER_URI. + + Returns + ------- + AsyncJSONBaseProvider | None + AsyncJSONBaseProvider or None if no environment variables are set. + + """ + # First check the main environment variable + uri_string = URI(os.environ.get("WEB3_PROVIDER_URI", "")) + if uri_string: + return load_async_provider_from_uri(uri_string) + + # Then check the WebSocket-specific variable + ws_uri_string = URI(os.environ.get("WEB3_WS_PROVIDER_URI", "")) + if ws_uri_string: + return load_async_provider_from_uri(ws_uri_string) + + return None + + +def load_async_provider_from_uri( + uri_string: URI, headers: dict[str, tuple[str, str]] | None = None +) -> AsyncJSONBaseProvider: + """ + Create an asynchronous provider based on a URI string. + + Parameters + ---------- + uri_string : URI + URI string for connecting to the node. + headers : dict[str, tuple[str, str]] | None, optional + Optional HTTP headers for AsyncHTTPProvider. + + Returns + ------- + AsyncJSONBaseProvider + An instance of AsyncJSONBaseProvider. + + Raises + ------ + NotImplementedError + If the URI scheme is not supported. + + Note + ---- + WebSocket providers (ws/wss) require using AsyncWeb3 + with a context manager for proper connection management: + ``async with AsyncWeb3(WebSocketProvider(uri)) as w3: ...`` + + """ + # Import here to avoid circular imports + from web3.providers.persistent import ( + WebSocketProvider, + ) + + uri = urlparse(uri_string) + + if uri.scheme == "file": + # For IPC use AsyncIPCProvider + from web3.providers.persistent import ( + AsyncIPCProvider, + ) + + return AsyncIPCProvider(uri.path) + elif uri.scheme in HTTP_SCHEMES: + return AsyncHTTPProvider(uri_string) + elif uri.scheme in WS_SCHEMES: + return WebSocketProvider(uri_string) else: raise NotImplementedError( "Web3 does not know how to connect to scheme " @@ -52,13 +184,55 @@ def load_provider_from_uri( ) +def _is_async_provider(provider: Any) -> bool: + """ + Check if a provider is asynchronous. + + Parameters + ---------- + provider : Any + The provider object to check. + + Returns + ------- + bool + True if the provider is asynchronous. + + """ + return getattr(provider, "is_async", False) + + class AutoProvider(JSONBaseProvider): + """ + Provider that automatically detects available synchronous providers. + + AutoProvider iterates through a list of potential providers and uses + the first one that successfully connects to a node. + + Attributes + ---------- + default_providers : tuple + Tuple of default provider functions/classes. + + Example + ------- + >>> from web3 import Web3 + >>> w3 = Web3(AutoProvider()) + >>> # or simply + >>> w3 = Web3() # AutoProvider is used by default + + Note + ---- + For WebSocket connections, use AsyncAutoProvider with AsyncWeb3. + + """ + default_providers = ( load_provider_from_environment, IPCProvider, HTTPProvider, ) - _active_provider = None + _active_provider: JSONBaseProvider | None = None def __init__( self, @@ -66,12 +240,19 @@ def __init__( | (Sequence[Callable[..., JSONBaseProvider] | type[JSONBaseProvider]]) = None, ) -> None: """ - :param iterable potential_providers: ordered series of provider classes - to attempt with + Initialize AutoProvider. + + Parameters + ---------- + potential_providers : Sequence | None, optional + Ordered sequence of provider classes or functions to attempt connection + with. If not specified, default_providers is used. + + Note + ---- + AutoProvider initializes each potential provider (without arguments) + in an attempt to find an active node. - AutoProvider will initialize each potential provider (without arguments), - in an attempt to find an active node. The list will default to - :attribute:`default_providers`. """ super().__init__() if potential_providers: @@ -80,6 +261,27 @@ def __init__( self._potential_providers = self.default_providers def make_request(self, method: RPCEndpoint, params: Any) -> RPCResponse: + """ + Execute an RPC request through the active provider. + + Parameters + ---------- + method : RPCEndpoint + RPC method to call. + params : Any + Parameters for the RPC method. + + Returns + ------- + RPCResponse + Response from the provider. + + Raises + ------ + CannotHandleRequest + If no active provider could be found. + + """ try: return self._proxy_request(method, params) except OSError: @@ -88,18 +290,74 @@ def make_request(self, method: RPCEndpoint, params: Any) -> RPCResponse: def make_batch_request( self, requests: list[tuple[RPCEndpoint, Any]] ) -> list[RPCResponse] | RPCResponse: + """ + Execute a batch RPC request through the active provider. + + Parameters + ---------- + requests : list[tuple[RPCEndpoint, Any]] + List of (method, params) tuples. + + Returns + ------- + list[RPCResponse] | RPCResponse + List of responses or a single error response. + + Raises + ------ + CannotHandleRequest + If no active provider could be found. + + """ try: return self._proxy_batch_request(requests) except OSError: return self._proxy_batch_request(requests, use_cache=False) def is_connected(self, show_traceback: bool = False) -> bool: + """ + Check if the provider is connected to a node. + + Parameters + ---------- + show_traceback : bool, optional + If True, show traceback on error. + + Returns + ------- + bool + True if the connection is active. + + """ provider = self._get_active_provider(use_cache=True) return provider is not None and provider.is_connected(show_traceback) def _proxy_request( self, method: RPCEndpoint, params: Any, use_cache: bool = True ) -> RPCResponse: + """ + Proxy the request to the active provider. + + Parameters + ---------- + method : RPCEndpoint + RPC method. + params : Any + Method parameters. + use_cache : bool, optional + Whether to use the cached provider. + + Returns + ------- + RPCResponse + Response from the provider. + + Raises + ------ + CannotHandleRequest + If no provider is found. + + """ provider = self._get_active_provider(use_cache) if provider is None: raise CannotHandleRequest( @@ -112,6 +370,27 @@ def _proxy_request( def _proxy_batch_request( self, requests: list[tuple[RPCEndpoint, Any]], use_cache: bool = True ) -> list[RPCResponse] | RPCResponse: + """ + Proxy the batch request to the active provider. + + Parameters + ---------- + requests : list[tuple[RPCEndpoint, Any]] + List of requests. + use_cache : bool, optional + Whether to use the cached provider. + + Returns + ------- + list[RPCResponse] | RPCResponse + Responses from the provider. + + Raises + ------ + CannotHandleRequest + If no provider is found. + + """ provider = self._get_active_provider(use_cache) if provider is None: raise CannotHandleRequest( @@ -122,12 +401,289 @@ def _proxy_batch_request( return provider.make_batch_request(requests) def _get_active_provider(self, use_cache: bool) -> JSONBaseProvider | None: + """ + Find and return the active provider. + + Parameters + ---------- + use_cache : bool + Whether to use the cached provider. + + Returns + ------- + JSONBaseProvider | None + Active provider or None. + + Raises + ------ + Web3ValidationError + If an async provider is discovered. + + """ if use_cache and self._active_provider is not None: return self._active_provider for Provider in self._potential_providers: provider = Provider() - if provider is not None and provider.is_connected(): + if provider is None: + continue + + # Verify that the provider is not asynchronous + if _is_async_provider(provider): + raise Web3ValidationError( + f"AutoProvider discovered an async provider ({type(provider).__name__}), " # noqa: E501 + "but AutoProvider only supports sync providers. " + "Use AsyncAutoProvider with AsyncWeb3 for async providers." + ) + + if provider.is_connected(): + self._active_provider = provider + return provider + + return None + + +class AsyncAutoProvider(AsyncJSONBaseProvider): + """ + Asynchronous provider that automatically detects available async providers. + + AsyncAutoProvider iterates through a list of potential providers and uses + the first one that successfully connects to a node. + + Attributes + ---------- + default_providers : tuple + Tuple of default provider functions/classes. + + Example + ------- + >>> from web3 import AsyncWeb3 + >>> from web3.providers.auto import AsyncAutoProvider + >>> + >>> async def main(): + ... w3 = AsyncWeb3(AsyncAutoProvider()) + ... if await w3.is_connected(): + ... print(await w3.eth.block_number) + + Note + ---- + For WebSocket providers that require explicit connect/disconnect, + it is recommended to use a context manager or explicitly manage + the connection. + + """ + + default_providers = ( + load_async_provider_from_environment, + AsyncHTTPProvider, + ) + _active_provider: AsyncJSONBaseProvider | None = None + + def __init__( + self, + potential_providers: None + | ( + Sequence[Callable[..., AsyncJSONBaseProvider] | type[AsyncJSONBaseProvider]] + ) = None, + ) -> None: + """ + Initialize AsyncAutoProvider. + + Parameters + ---------- + potential_providers : Sequence | None, optional + Ordered sequence of provider classes or functions to attempt connection + with. If not specified, default_providers is used. + + """ + super().__init__() + if potential_providers: + self._potential_providers = potential_providers + else: + self._potential_providers = self.default_providers + + async def make_request(self, method: RPCEndpoint, params: Any) -> RPCResponse: + """ + Execute an asynchronous RPC request through the active provider. + + Parameters + ---------- + method : RPCEndpoint + RPC method to call. + params : Any + Parameters for the RPC method. + + Returns + ------- + RPCResponse + Response from the provider. + + Raises + ------ + CannotHandleRequest + If no active provider could be found. + + """ + try: + return await self._proxy_request(method, params) + except OSError: + return await self._proxy_request(method, params, use_cache=False) + + async def make_batch_request( + self, requests: list[tuple[RPCEndpoint, Any]] + ) -> list[RPCResponse] | RPCResponse: + """ + Execute an asynchronous batch RPC request through the active provider. + + Parameters + ---------- + requests : list[tuple[RPCEndpoint, Any]] + List of (method, params) tuples. + + Returns + ------- + list[RPCResponse] | RPCResponse + List of responses or a single error response. + + Raises + ------ + CannotHandleRequest + If no active provider could be found. + + """ + try: + return await self._proxy_batch_request(requests) + except OSError: + return await self._proxy_batch_request(requests, use_cache=False) + + async def is_connected(self, show_traceback: bool = False) -> bool: + """ + Check if the provider is connected to a node. + + Parameters + ---------- + show_traceback : bool, optional + If True, show traceback on error. + + Returns + ------- + bool + True if the connection is active. + + """ + provider = await self._get_active_provider(use_cache=True) + if provider is None: + return False + return await provider.is_connected(show_traceback) + + async def _proxy_request( + self, method: RPCEndpoint, params: Any, use_cache: bool = True + ) -> RPCResponse: + """ + Proxy the request to the active provider. + + Parameters + ---------- + method : RPCEndpoint + RPC method. + params : Any + Method parameters. + use_cache : bool, optional + Whether to use the cached provider. + + Returns + ------- + RPCResponse + Response from the provider. + + Raises + ------ + CannotHandleRequest + If no provider is found. + + """ + provider = await self._get_active_provider(use_cache) + if provider is None: + raise CannotHandleRequest( + "Could not discover provider while making request: " + f"method:{method}\nparams:{params}\n" + ) + + return await provider.make_request(method, params) + + async def _proxy_batch_request( + self, requests: list[tuple[RPCEndpoint, Any]], use_cache: bool = True + ) -> list[RPCResponse] | RPCResponse: + """ + Proxy the batch request to the active provider. + + Parameters + ---------- + requests : list[tuple[RPCEndpoint, Any]] + List of requests. + use_cache : bool, optional + Whether to use the cached provider. + + Returns + ------- + list[RPCResponse] | RPCResponse + Responses from the provider. + + Raises + ------ + CannotHandleRequest + If no provider is found. + + """ + provider = await self._get_active_provider(use_cache) + if provider is None: + raise CannotHandleRequest( + "Could not discover provider while making batch request: " + f"requests:{requests}\n" + ) + + return await provider.make_batch_request(requests) + + async def _get_active_provider( + self, use_cache: bool + ) -> AsyncJSONBaseProvider | None: + """ + Find and return the active asynchronous provider. + + Parameters + ---------- + use_cache : bool + Whether to use the cached provider. + + Returns + ------- + AsyncJSONBaseProvider | None + Active provider or None. + + Raises + ------ + Web3ValidationError + If a sync provider is discovered. + + """ + if use_cache and self._active_provider is not None: + return self._active_provider + + for Provider in self._potential_providers: + provider = Provider() + if provider is None: + continue + + # Verify that the provider is asynchronous + if not _is_async_provider(provider): + raise Web3ValidationError( + f"AsyncAutoProvider discovered a sync provider " + f"({type(provider).__name__}), but AsyncAutoProvider only " + "supports async providers. Use AutoProvider with Web3 for " + "sync providers." + ) + + if await provider.is_connected(): self._active_provider = provider return provider