Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Add AsyncIPCProvider #2984

Merged
merged 13 commits into from
Feb 23, 2024
Merged
Show file tree
Hide file tree
Changes from 11 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
32 changes: 32 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,13 @@ jobs:
environment:
TOXENV: py38-integration-goethereum-ipc

py38-integration-goethereum-ipc_async:
<<: *geth_steps
docker:
- image: cimg/python:3.8
environment:
TOXENV: py38-integration-goethereum-ipc_async

py38-integration-goethereum-ipc_flaky:
<<: *geth_steps
docker:
Expand Down Expand Up @@ -427,6 +434,13 @@ jobs:
environment:
TOXENV: py39-integration-goethereum-ipc

py39-integration-goethereum-ipc_async:
<<: *geth_steps
docker:
- image: cimg/python:3.9
environment:
TOXENV: py39-integration-goethereum-ipc_async

py39-integration-goethereum-ipc_flaky:
<<: *geth_steps
docker:
Expand Down Expand Up @@ -566,6 +580,13 @@ jobs:
environment:
TOXENV: py310-integration-goethereum-ipc

py310-integration-goethereum-ipc_async:
<<: *geth_steps
docker:
- image: cimg/python:3.10
environment:
TOXENV: py310-integration-goethereum-ipc_async

py310-integration-goethereum-ipc_flaky:
<<: *geth_steps
docker:
Expand Down Expand Up @@ -711,6 +732,13 @@ jobs:
environment:
TOXENV: py311-integration-goethereum-ipc

py311-integration-goethereum-ipc_async:
<<: *geth_steps
docker:
- image: cimg/python:3.11
environment:
TOXENV: py311-integration-goethereum-ipc_async

py311-integration-goethereum-ipc_flaky:
<<: *geth_steps
docker:
Expand Down Expand Up @@ -824,6 +852,7 @@ workflows:
- py38-ensip15
- py38-ethpm
- py38-integration-goethereum-ipc
- py38-integration-goethereum-ipc_async
- py38-integration-goethereum-ipc_flaky
- py38-integration-goethereum-http
- py38-integration-goethereum-http_async
Expand All @@ -841,6 +870,7 @@ workflows:
- py39-ensip15
- py39-ethpm
- py39-integration-goethereum-ipc
- py39-integration-goethereum-ipc_async
- py39-integration-goethereum-ipc_flaky
- py39-integration-goethereum-http
- py39-integration-goethereum-http_async
Expand All @@ -858,6 +888,7 @@ workflows:
- py310-ensip15
- py310-ethpm
- py310-integration-goethereum-ipc
- py310-integration-goethereum-ipc_async
- py310-integration-goethereum-ipc_flaky
- py310-integration-goethereum-http
- py310-integration-goethereum-http_async
Expand All @@ -875,6 +906,7 @@ workflows:
- py311-ensip15
- py311-ethpm
- py311-integration-goethereum-ipc
- py311-integration-goethereum-ipc_async
- py311-integration-goethereum-ipc_flaky
- py311-integration-goethereum-http
- py311-integration-goethereum-http_async
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ docs/web3.providers.eth_tester.rst
docs/web3.providers.rst
docs/web3.providers.rpc.rst
docs/web3.providers.websocket.rst
docs/web3.providers.persistent.rst
docs/web3.rst
docs/web3.scripts.release.rst
docs/web3.scripts.rst
Expand Down
1 change: 1 addition & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
"web3.providers.rst",
"web3.providers.rpc.rst",
"web3.providers.websocket.rst",
"web3.providers.persistent.rst",
"web3.providers.eth_tester.rst",
"web3.scripts.*",
"web3.testing.rst",
Expand Down
15 changes: 8 additions & 7 deletions docs/internals.rst
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ back. An example is using the ``eth`` module API to request the latest block num
.. code-block:: python

>>> async def wsV2_one_to_one_example():
... async with AsyncWeb3.persistent_websocket(
... async with AsyncWeb3.persistent_connection(
... WebsocketProviderV2(f"ws://127.0.0.1:8546")
... ) as w3:
... # make a request and expect a single response returned on the same line
Expand Down Expand Up @@ -414,23 +414,24 @@ subscription *id* value, but it also expects to receive many ``eth_subscription`
messages if and when the request is successful. For this reason, the original request
is considered a one-to-one request so that a subscription *id* can be returned to the
user on the same line, but the ``process_subscriptions()`` method on the
:class:`~web3.providers.websocket.WebsocketConnection` class, the public API for
:class:`~web3.providers.websocket.PersistentConnection` class, the public API for
interacting with the active websocket connection, is set up to receive
``eth_subscription`` responses over an asynchronous interator pattern.

.. code-block:: python

>>> async def ws_v2_subscription_example():
... async with AsyncWeb3.persistent_websocket(
... async with AsyncWeb3.persistent_connection(
... WebsocketProviderV2(f"ws://127.0.0.1:8546")
... ) as w3:
... # Subscribe to new block headers and receive the subscription_id.
... # A one-to-one call with a trigger for many responses
... subscription_id = await w3.eth.subscribe("newHeads")
...
... # Listen to the websocket for the many responses utilizing the ``w3.ws``
... # ``WebsocketConnection`` public API method ``process_subscriptions()``
... async for response in w3.ws.process_subscriptions():
... # Listen to the websocket for the many responses utilizing the
... # ``w3.socket`` ``PersistentConnection`` public API method
... # ``process_subscriptions()``
... async for response in w3.socket.process_subscriptions():
... # Receive only one-to-many responses here so that we don't
... # accidentally return the response for a one-to-one request in this
... # block
Expand All @@ -450,7 +451,7 @@ are stored in an internal ``asyncio.Queue`` instance, isolated from any one-to-o
responses. When the ``PersistentConnectionProvider`` is looking for one-to-many
responses internally, it will expect the message listener task to store these messages
in this queue. Since the order of the messages is important, the queue is a FIFO queue.
The ``process_subscriptions()`` method on the ``WebsocketConnection`` class is set up
The ``process_subscriptions()`` method on the ``PersistentConnection`` class is set up
to pop messages from this queue as FIFO over an asynchronous iterator pattern.

If the stream of messages from the websocket is not being interrupted by any other
Expand Down
71 changes: 54 additions & 17 deletions docs/providers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ Providers are web3.py classes that are configured for the kind of connection you
See:

- :class:`~web3.providers.ipc.IPCProvider`
- :class:`~web3.providers.ipc.AsyncIPCProvider`
- :class:`~web3.providers.websocket.WebsocketProvider`
- :class:`~web3.providers.websocket.WebsocketProviderV2`
- :class:`~web3.providers.rpc.HTTPProvider`
Expand Down Expand Up @@ -87,7 +88,8 @@ Auto-initialization Provider Shortcuts
Geth dev Proof of Authority
~~~~~~~~~~~~~~~~~~~~~~~~~~~

To connect to a ``geth --dev`` Proof of Authority instance with defaults:
To connect to a ``geth --dev`` Proof of Authority instance with
the POA middleware loaded by default:

.. code-block:: python

Expand All @@ -97,6 +99,18 @@ To connect to a ``geth --dev`` Proof of Authority instance with defaults:
>>> w3.is_connected()
True

Or, connect to an async web3 instance:

.. code-block:: python

>>> from web3.auto.gethdev import async_w3
>>> await async_w3.provider.connect()

# confirm that the connection succeeded
>>> await async_w3.is_connected()
True


Built In Providers
------------------

Expand Down Expand Up @@ -157,7 +171,7 @@ HTTPProvider
IPCProvider
~~~~~~~~~~~

.. py:class:: web3.providers.ipc.IPCProvider(ipc_path=None, testnet=False, timeout=10)
.. py:class:: web3.providers.ipc.IPCProvider(ipc_path=None, timeout=10)

This provider handles interaction with an IPC Socket based JSON-RPC
server.
Expand All @@ -177,6 +191,29 @@ IPCProvider
- On Windows: ``\\.\pipe\geth.ipc``


AsyncIPCProvider
~~~~~~~~~~~~~~~~

.. py:class:: web3.providers.ipc.AsyncIPCProvider(ipc_path=None, timeout=10)

Similar to the IPCProvider, this provider handles asynchronous interaction
with an IPC Socket based JSON-RPC server.

* ``ipc_path`` is the filesystem path to the IPC socket:

.. code-block:: python

>>> from web3 import AsyncWeb3, AsyncIPCProvider
>>> w3 = AsyncWeb3(AsyncIPCProvider("~/Library/Ethereum/geth.ipc"))

If no ``ipc_path`` is specified, it will use a default depending on your operating
system.

- On Linux and FreeBSD: ``~/.ethereum/geth.ipc``
- On Mac OS: ``~/Library/Ethereum/geth.ipc``
- On Windows: ``\\.\pipe\geth.ipc``


WebsocketProvider
~~~~~~~~~~~~~~~~~

Expand Down Expand Up @@ -277,7 +314,7 @@ Usage
+++++

The ``AsyncWeb3`` class may be used as a context manager, utilizing the ``async with``
syntax, when connecting via ``persistent_websocket()`` using the
syntax, when connecting via ``persistent_connection()`` using the
``WebsocketProviderV2``. This will automatically close the connection when the context
manager exits and is the recommended way to initiate a persistent connection to the
websocket provider. A similar example, using the ``websockets`` connection as an
Expand All @@ -297,13 +334,13 @@ asynchronous context manager, can be found in the `websockets connection`_ docs.
... logger.addHandler(logging.StreamHandler())

>>> async def ws_v2_subscription_context_manager_example():
... async with AsyncWeb3.persistent_websocket(
... async with AsyncWeb3.persistent_connection(
... WebsocketProviderV2(f"ws://127.0.0.1:8546")
... ) as w3:
... # subscribe to new block headers
... subscription_id = await w3.eth.subscribe("newHeads")
...
... async for response in w3.ws.process_subscriptions():
... async for response in w3.socket.process_subscriptions():
... print(f"{response}\n")
... # handle responses here
...
Expand All @@ -325,7 +362,7 @@ asynchronous context manager, can be found in the `websockets connection`_ docs.


The ``AsyncWeb3`` class may also be used as an asynchronous iterator, utilizing the
``async for`` syntax, when connecting via ``persistent_websocket()`` using the
``async for`` syntax, when connecting via ``persistent_connection()`` using the
``WebsocketProviderV2``. This may be used to set up an indefinite websocket connection
and reconnect automatically if the connection is lost. A similar example, using the
``websockets`` connection as an asynchronous iterator, can be found in the
Expand All @@ -341,7 +378,7 @@ and reconnect automatically if the connection is lost. A similar example, using
>>> import websockets

>>> async def ws_v2_subscription_iterator_example():
... async for w3 in AsyncWeb3.persistent_websocket(
... async for w3 in AsyncWeb3.persistent_connection(
... WebsocketProviderV2(f"ws://127.0.0.1:8546")
... ):
... try:
Expand All @@ -354,15 +391,15 @@ and reconnect automatically if the connection is lost. A similar example, using


If neither of the two init patterns above work for your application, the ``__await__()``
method is defined on the ``persistent_websocket()`` connection in a manner that awaits
method is defined on the ``persistent_connection()`` connection in a manner that awaits
connecting to the websocket. You may also choose to instantiate and connect via the
provider in separate lines. Both of these examples are shown below.

.. code-block:: python

>>> async def ws_v2_alternate_init_example_1():
... # awaiting the persistent connection itself will connect to the websocket
... w3 = await AsyncWeb3.persistent_websocket(WebsocketProviderV2(f"ws://127.0.0.1:8546"))
... w3 = await AsyncWeb3.persistent_connection(WebsocketProviderV2(f"ws://127.0.0.1:8546"))
...
... # some code here
...
Expand All @@ -374,7 +411,7 @@ provider in separate lines. Both of these examples are shown below.

>>> async def ws_v2_alternate_init_example_2():
... # instantiation and connection via the provider as separate lines
... w3 = AsyncWeb3.persistent_websocket(WebsocketProviderV2(f"ws://127.0.0.1:8546"))
... w3 = AsyncWeb3.persistent_connection(WebsocketProviderV2(f"ws://127.0.0.1:8546"))
... await w3.provider.connect()
...
... # some code here
Expand All @@ -392,11 +429,11 @@ one-to-many request-to-response requests. Refer to the
:class:`~web3.providers.websocket.request_processor.RequestProcessor`
documentation for details.

_PersistentConnectionWeb3 via AsyncWeb3.persistent_websocket()
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
_PersistentConnectionWeb3 via AsyncWeb3.persistent_connection()
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

When an ``AsyncWeb3`` class is connected to a persistent websocket connection, via the
``persistent_websocket()`` method, it becomes an instance of the
``persistent_connection()`` method, it becomes an instance of the
``_PersistentConnectionWeb3`` class. This class has a few additional methods and
attributes that are not available on the ``AsyncWeb3`` class.

Expand All @@ -407,18 +444,18 @@ attributes that are not available on the ``AsyncWeb3`` class.
The public API for interacting with the websocket connection is available via
the ``ws`` attribute of the ``_PersistentConnectionWeb3`` class. This attribute
is an instance of the
:class:`~web3.providers.websocket.WebsocketConnection` class and is the main
:class:`~web3.providers.websocket.PersistentConnection` class and is the main
interface for interacting with the websocket connection.


Interacting with the Websocket Connection
+++++++++++++++++++++++++++++++++++++++++

.. py:class:: web3.providers.websocket.WebsocketConnection
.. py:class:: web3.providers.websocket.PersistentConnection

This class handles interactions with a websocket connection. It is available
via the ``ws`` attribute of the ``_PersistentConnectionWeb3`` class. The
``WebsocketConnection`` class has the following methods and attributes:
``PersistentConnection`` class has the following methods and attributes:

.. py:attribute:: subscriptions

Expand Down Expand Up @@ -457,7 +494,7 @@ Interacting with the Websocket Connection
responses will not be formatted by web3.py formatters or run through the
middlewares. Instead, use the methods available on the respective web3
module. For example, use ``w3.eth.get_block("latest")`` instead of
``w3.ws.send("eth_getBlockByNumber", ["latest", True])``.
``w3.socket.send("eth_getBlockByNumber", ["latest", True])``.


AutoProvider
Expand Down
1 change: 1 addition & 0 deletions newsfragments/2984.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add AsyncIPCProvider
Loading