Repository: ethereum/web3.py (language: python)

Issue:
Explicitly export symbols via __all__
### What feature should we add?

Currently when importing symbols from package scope causes an error with mypy when running in strict mode:

```python
from web3.providers import WebsocketProviderV2
# error: Module "web3.providers" does not explicitly export attribute "WebsocketProviderV2"  [attr-defined]
```

It would be nice if the package `__init__.py` files could include an __all__ attribute to silence this error by explicitly marking the symbols as being re-exported in the new namespace.


Discussion / hints:

Fix patch:
diff --git a/docs/filters.rst b/docs/filters.rst
index 0f494400bf..a8ed528556 100644
--- a/docs/filters.rst
+++ b/docs/filters.rst
@@ -4,7 +4,7 @@ Events and Logs
 ===============
 
 If you're on this page, you're likely looking for an answer to this question:
-**How do I know when a specific contract is used?** You have at least three options:
+**How do I know when a specific contract is used?** You have several options:
 
 1. Query blocks for transactions that include the contract address in the ``"to"`` field.
    This contrived example is searching the latest block for any transactions sent to the
@@ -39,7 +39,45 @@ If you're on this page, you're likely looking for an answer to this question:
 
 See an advanced example of fetching log history :ref:`here <advanced_token_fetch>`.
 
-3. Use a filter.
+3. Subscribe to events for real-time updates. When using a persistent connection provider
+   (:class:`~web3.providers.persistent.WebSocketProvider` or
+   :class:`~web3.providers.persistent.AsyncIPCProvider`), the
+   :meth:`subscribe() <web3.eth.Eth.subscribe>` method can be used to establish a new
+   event subscription. This example subscribes to ``Transfer`` events of the WETH contract.
+
+  .. code-block:: python
+
+    import asyncio
+    from web3 import AsyncWeb3, WebSocketProvider
+    from eth_abi.abi import decode
+
+    WETH_ADDRESS = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
+
+
+    async def subscribe_to_transfer_events():
+        async with AsyncWeb3(WebSocketProvider("...")) as w3:
+            transfer_event_topic = w3.keccak(text="Transfer(address,address,uint256)")
+            filter_params = {
+                "address": WETH_ADDRESS,
+                "topics": [transfer_event_topic],
+            }
+            subscription_id = await w3.eth.subscribe("logs", filter_params)
+            print(f"Subscribing to transfer events for WETH at {subscription_id}")
+
+            async for payload in w3.socket.process_subscriptions():
+                result = payload["result"]
+
+                from_addr = decode(["address"], result["topics"][1])[0]
+                to_addr = decode(["address"], result["topics"][2])[0]
+                amount = decode(["uint256"], result["data"])[0]
+                print(f"{w3.from_wei(amount, 'ether')} WETH from {from_addr} to {to_addr}")
+
+    asyncio.run(subscribe_to_transfer_events())
+
+
+  For more usage examples see the docs on :ref:`subscription-examples`.
+
+4. Use a filter.
 
 .. warning ::
 
diff --git a/docs/web3.eth.account.rst b/docs/web3.eth.account.rst
index f78b015372..7d2720bb5d 100644
--- a/docs/web3.eth.account.rst
+++ b/docs/web3.eth.account.rst
@@ -218,8 +218,7 @@ is provided by :meth:`w3.eth.sign() <web3.eth.Eth.sign>`.
     >>> message = encode_defunct(text=msg)
     >>> signed_message = w3.eth.account.sign_message(message, private_key=private_key)
     >>> signed_message
-    SignedMessage(messageHash=HexBytes('0x1476abb745d423bf09273f1afd887d951181d25adc66c4834a70491911b7f750'),
-    message_hash=HexBytes('0x1476abb745d423bf09273f1afd887d951181d25adc66c4834a70491911b7f750'),
+    SignedMessage(message_hash=HexBytes('0x1476abb745d423bf09273f1afd887d951181d25adc66c4834a70491911b7f750'),
      r=104389933075820307925104709181714897380569894203213074526835978196648170704563,
      s=28205917190874851400050446352651915501321657673772411533993420917949420456142,
      v=28,
diff --git a/ens/__init__.py b/ens/__init__.py
index cb9a0d618a..2b28bc531c 100644
--- a/ens/__init__.py
+++ b/ens/__init__.py
@@ -1,5 +1,3 @@
-# flake8: noqa
-
 from .async_ens import (
     AsyncENS,
 )
@@ -18,3 +16,16 @@
     UnderfundedBid,
     UnownedName,
 )
+
+__all__ = [
+    "AsyncENS",
+    "BaseENS",
+    "ENS",
+    "AddressMismatch",
+    "BidTooLow",
+    "InvalidLabel",
+    "InvalidName",
+    "UnauthorizedError",
+    "UnderfundedBid",
+    "UnownedName",
+]
diff --git a/newsfragments/3403.docs.rst b/newsfragments/3403.docs.rst
new file mode 100644
index 0000000000..b4879f2685
--- /dev/null
+++ b/newsfragments/3403.docs.rst
@@ -0,0 +1,1 @@
+Add an ``eth_subscribe`` example to the events guide
diff --git a/newsfragments/3404.internal.rst b/newsfragments/3404.internal.rst
new file mode 100644
index 0000000000..85a69fd3a0
--- /dev/null
+++ b/newsfragments/3404.internal.rst
@@ -0,0 +1,1 @@
+Remove uses of signHash in tests, require eth-account >=0.13.0 in doctests
diff --git a/newsfragments/3409.feature.rst b/newsfragments/3409.feature.rst
new file mode 100644
index 0000000000..e0b647ec94
--- /dev/null
+++ b/newsfragments/3409.feature.rst
@@ -0,0 +1,1 @@
+Provide explicit ``__all__`` exports for providers in `web3/providers/__init__.py`; update `web3/__init__.py` to include all provider classes including base classes.
diff --git a/setup.py b/setup.py
index 24c832b847..71c4a434a4 100644
--- a/setup.py
+++ b/setup.py
@@ -25,6 +25,9 @@
         "sphinx-autobuild>=2021.3.14",
         "sphinx_rtd_theme>=1.0.0",
         "towncrier>=21,<22",
+        # web3 will work but emit warnings with eth-account>=0.12.2,
+        # but doctests fail between 0.12.2 and 0.13.0
+        "eth-account>=0.13.0",
     ],
     "test": [
         "eth-tester[py-evm]>=0.11.0b1,<0.13.0b1",
diff --git a/web3/__init__.py b/web3/__init__.py
index 6ad68dfab5..886bfcd62b 100644
--- a/web3/__init__.py
+++ b/web3/__init__.py
@@ -1,5 +1,4 @@
 from eth_account import Account  # noqa: E402
-import sys
 
 from importlib.metadata import version
 
@@ -10,12 +9,20 @@
     AsyncWeb3,
     Web3,
 )
+from web3.providers import (
+    AsyncBaseProvider,
+    AutoProvider,
+    BaseProvider,
+    JSONBaseProvider,
+    PersistentConnection,
+)
 from web3.providers.persistent import (  # noqa: E402
     AsyncIPCProvider,
     PersistentConnectionProvider,
     WebSocketProvider,
 )
 from web3.providers.eth_tester import (  # noqa: E402
+    AsyncEthereumTesterProvider,
     EthereumTesterProvider,
 )
 from web3.providers.ipc import (  # noqa: E402
@@ -32,14 +39,23 @@
 
 __all__ = [
     "__version__",
+    "Account",
+    # web3:
     "AsyncWeb3",
     "Web3",
+    # providers:
+    "AsyncBaseProvider",
+    "AsyncEthereumTesterProvider",
+    "AsyncHTTPProvider",
+    "AsyncIPCProvider",
+    "AutoProvider",
+    "BaseProvider",
+    "EthereumTesterProvider",
     "HTTPProvider",
     "IPCProvider",
+    "JSONBaseProvider",
     "LegacyWebSocketProvider",
+    "PersistentConnection",
+    "PersistentConnectionProvider",
     "WebSocketProvider",
-    "EthereumTesterProvider",
-    "Account",
-    "AsyncHTTPProvider",
-    "AsyncIPCProvider",
 ]
diff --git a/web3/beacon/__init__.py b/web3/beacon/__init__.py
index d10472a91d..a964633843 100644
--- a/web3/beacon/__init__.py
+++ b/web3/beacon/__init__.py
@@ -1,2 +1,7 @@
 from .async_beacon import AsyncBeacon
 from .beacon import Beacon
+
+__all__ = [
+    "AsyncBeacon",
+    "Beacon",
+]
diff --git a/web3/contract/__init__.py b/web3/contract/__init__.py
index 11602cf6d7..6bca3bdb66 100644
--- a/web3/contract/__init__.py
+++ b/web3/contract/__init__.py
@@ -7,3 +7,10 @@
     ContractCaller,
     ContractConstructor,
 )
+
+__all__ = [
+    "AsyncContract",
+    "AsyncContractCaller",
+    "Contract",
+    "ContractConstructor",
+]
diff --git a/web3/eth/__init__.py b/web3/eth/__init__.py
index 6b692229bb..a65f64d1fb 100644
--- a/web3/eth/__init__.py
+++ b/web3/eth/__init__.py
@@ -8,3 +8,10 @@
     Contract,
     Eth,
 )
+
+__all__ = [
+    "AsyncEth",
+    "BaseEth",
+    "Contract",
+    "Eth",
+]
diff --git a/web3/middleware/__init__.py b/web3/middleware/__init__.py
index a04ec92010..512b27ecf7 100644
--- a/web3/middleware/__init__.py
+++ b/web3/middleware/__init__.py
@@ -92,3 +92,20 @@ async def async_combine_middleware(
         initialized = mw(async_w3)
         accumulator_fn = await initialized.async_wrap_make_request(accumulator_fn)
     return accumulator_fn
+
+
+__all__ = [
+    "AttributeDictMiddleware",
+    "Middleware",
+    "Web3Middleware",
+    "BufferedGasEstimateMiddleware",
+    "LocalFilterMiddleware",
+    "FormattingMiddlewareBuilder",
+    "GasPriceStrategyMiddleware",
+    "ENSNameToAddressMiddleware",
+    "ExtraDataToPOAMiddleware",
+    "PythonicMiddleware",
+    "SignAndSendRawMiddlewareBuilder",
+    "StalecheckMiddlewareBuilder",
+    "ValidationMiddleware",
+]
diff --git a/web3/providers/__init__.py b/web3/providers/__init__.py
index afd303f42e..a07b65b90b 100644
--- a/web3/providers/__init__.py
+++ b/web3/providers/__init__.py
@@ -8,6 +8,10 @@
     BaseProvider,
     JSONBaseProvider,
 )
+from .eth_tester import (
+    AsyncEthereumTesterProvider,
+    EthereumTesterProvider,
+)
 from .ipc import (
     IPCProvider,
 )
@@ -26,3 +30,20 @@
 from .auto import (
     AutoProvider,
 )
+
+__all__ = [
+    "AsyncBaseProvider",
+    "AsyncEthereumTesterProvider",
+    "AsyncHTTPProvider",
+    "AsyncIPCProvider",
+    "AutoProvider",
+    "BaseProvider",
+    "EthereumTesterProvider",
+    "HTTPProvider",
+    "IPCProvider",
+    "JSONBaseProvider",
+    "LegacyWebSocketProvider",
+    "PersistentConnection",
+    "PersistentConnectionProvider",
+    "WebSocketProvider",
+]
diff --git a/web3/providers/persistent/__init__.py b/web3/providers/persistent/__init__.py
index 3538bd967c..3477f0a004 100644
--- a/web3/providers/persistent/__init__.py
+++ b/web3/providers/persistent/__init__.py
@@ -13,3 +13,10 @@
 from .websocket import (
     WebSocketProvider,
 )
+
+__all__ = [
+    "PersistentConnectionProvider",
+    "PersistentConnection",
+    "AsyncIPCProvider",
+    "WebSocketProvider",
+]
diff --git a/web3/providers/rpc/__init__.py b/web3/providers/rpc/__init__.py
index cbac394616..9d1b2255cb 100644
--- a/web3/providers/rpc/__init__.py
+++ b/web3/providers/rpc/__init__.py
@@ -4,3 +4,8 @@
 from .rpc import (
     HTTPProvider,
 )
+
+__all__ = [
+    "AsyncHTTPProvider",
+    "HTTPProvider",
+]
diff --git a/web3/utils/__init__.py b/web3/utils/__init__.py
index 13c24de6a0..108327984f 100644
--- a/web3/utils/__init__.py
+++ b/web3/utils/__init__.py
@@ -3,17 +3,26 @@
 classify as breaking changes.
 """
 
-from .abi import (  # NOQA
+from .abi import (
     get_abi_input_names,
     get_abi_output_names,
 )
-from .address import get_create_address  # NOQA
-from .async_exception_handling import (  # NOQA
+from .address import get_create_address
+from .async_exception_handling import (
     async_handle_offchain_lookup,
 )
-from .caching import (  # NOQA
+from .caching import (
     SimpleCache,
 )
-from .exception_handling import (  # NOQA
+from .exception_handling import (
     handle_offchain_lookup,
 )
+
+__all__ = [
+    "SimpleCache",
+    "async_handle_offchain_lookup",
+    "get_abi_input_names",
+    "get_abi_output_names",
+    "get_create_address",
+    "handle_offchain_lookup",
+]


Test patch:
diff --git a/tests/core/eth-module/test_accounts.py b/tests/core/eth-module/test_accounts.py
index 02ff790ae7..4433f877b2 100644
--- a/tests/core/eth-module/test_accounts.py
+++ b/tests/core/eth-module/test_accounts.py
@@ -132,7 +132,7 @@ def test_eth_account_from_key_seed_restrictions(acct):
 
 def test_eth_account_from_key_properties(acct, PRIVATE_BYTES):
     account = acct.from_key(PRIVATE_BYTES)
-    assert callable(account.signHash)
+    assert callable(account.unsafe_sign_hash)
     assert callable(account.sign_transaction)
     assert is_checksum_address(account.address)
     assert account.address == "0xa79F6f349C853F9Ea0B29636779ae3Cb4E3BA729"
@@ -141,7 +141,7 @@ def test_eth_account_from_key_properties(acct, PRIVATE_BYTES):
 
 def test_eth_account_create_properties(acct):
     account = acct.create()
-    assert callable(account.signHash)
+    assert callable(account.unsafe_sign_hash)
     assert callable(account.sign_transaction)
     assert is_checksum_address(account.address)
     assert isinstance(account.key, bytes) and len(account.key) == 32
diff --git a/web3/providers/eth_tester/__init__.py b/web3/providers/eth_tester/__init__.py
index dd3c39a06c..77e230cc80 100644
--- a/web3/providers/eth_tester/__init__.py
+++ b/web3/providers/eth_tester/__init__.py
@@ -2,3 +2,8 @@
     AsyncEthereumTesterProvider,
     EthereumTesterProvider,
 )
+
+__all__ = [
+    "AsyncEthereumTesterProvider",
+    "EthereumTesterProvider",
+]