Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 99 additions & 4 deletions docs/providers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions newsfragments/3704.feature.rst
Original file line number Diff line number Diff line change
@@ -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.
108 changes: 108 additions & 0 deletions tests/core/providers/test_auto_provider.py
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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
2 changes: 2 additions & 0 deletions web3/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
Web3,
)
from web3.providers import (
AsyncAutoProvider,
AsyncBaseProvider,
AutoProvider,
BaseProvider,
Expand Down Expand Up @@ -41,6 +42,7 @@
"AsyncWeb3",
"Web3",
# providers:
"AsyncAutoProvider",
"AsyncBaseProvider",
"AsyncEthereumTesterProvider",
"AsyncHTTPProvider",
Expand Down
2 changes: 2 additions & 0 deletions web3/providers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@
WebSocketProvider,
)
from .auto import (
AsyncAutoProvider,
AutoProvider,
)

__all__ = [
"AsyncAutoProvider",
"AsyncBaseProvider",
"AsyncEthereumTesterProvider",
"AsyncHTTPProvider",
Expand Down
Loading