Skip to content

Make AsyncWeb3 class generic to fix example from docs #3759

@aleexharris

Description

@aleexharris

What happened?

Summary

The documented example from Web3.py (link) which looks like this:

from aiohttp import ClientSession
from web3 import AsyncWeb3, AsyncHTTPProvider

async def bug() -> None:
    w3 = AsyncWeb3(AsyncHTTPProvider(endpoint_uri))
    custom_session = ClientSession()
    await w3.provider.cache_async_session(custom_session)
    w3.provider.disconnect()

Causes type checkers like mypy, pyright and ty to throw an error along these lines:

error[unresolved-attribute]: Type `AsyncBaseProvider` has no attribute `cache_async_session`
  --> src/silly_test.py:10:11
   |
 8 |     # If you want to pass in your own session:
 9 |     custom_session = ClientSession()
10 |     await w3.provider.cache_async_session(custom_session) # This method is an async method so it needs to be handled accordingly
   |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
11 |     # when you're finished, disconnect:
12 |     w3.provider.disconnect()
   |
info: rule `unresolved-attribute` is enabled by default

Fill this section in if you know how this could or should be fixed

The AsyncWeb3.provider property is annotated to return an instance of AsyncBaseProvider. The issue is that AsyncBaseProvider doesn't have a cache_async_session attribute or method.

While the AsyncHTTPProvider subclass does have a cache_async_session method, the type annotations don't provide enough information for the type checker to know that w3.provider is an instance of AsyncHTTPProvider instead of the more general AsyncBaseProvider.

To fix this, you could try making the AsyncWeb3 class generic. The following approach might resolve the issue, but you will need to test it:

diff --git a/web3/main.py b/web3/main.py
index b388f95b..35286871 100644
--- a/web3/main.py
+++ b/web3/main.py
@@ -43,6 +43,8 @@ from typing import (
     Type,
     Union,
     cast,
+    Generic,
+    TypeVar,
 )
 
 from eth_typing import (
@@ -447,7 +449,10 @@ class Web3(BaseWeb3):
 # -- async -- #
 
 
-class AsyncWeb3(BaseWeb3):
+T = TypeVar("T", bound=AsyncBaseProvider)
+
+
+class AsyncWeb3(BaseWeb3, Generic[T]):
     # mypy Types
     eth: AsyncEth
     net: AsyncNet
@@ -460,7 +465,7 @@ class AsyncWeb3(BaseWeb3):
 
     def __init__(
         self,
-        provider: Optional[AsyncBaseProvider] = None,
+        provider: Optional[T] = None,
         middleware: Optional[Sequence[Any]] = None,
         modules: Optional[Dict[str, Union[Type[Module], Sequence[Any]]]] = None,
         external_modules: Optional[
@@ -486,11 +491,11 @@ class AsyncWeb3(BaseWeb3):
         return await self.provider.is_connected(show_traceback)
 
     @property
-    def provider(self) -> AsyncBaseProvider:
-        return cast(AsyncBaseProvider, self.manager.provider)
+    def provider(self) -> T:
+        return cast(T, self.manager.provider)
 
     @provider.setter
-    def provider(self, provider: AsyncBaseProvider) -> None:
+    def provider(self, provider: T) -> None:
         self.manager.provider = provider

web3 Version

7.13.0

Python Version

3.12

Operating System

ubuntu 24

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions