Repository: ethereum/web3.py

Issue:
Web3 instantiation from subclass throws error
* Version: 5.30.0
* Python: 3.8
* OS: osx
* pip freeze output is intentionally omitted.

### What was wrong?
We use a sub-class of Web3, for the purpose of this example, call it SubclassWeb3, as follows:

```
class SubclassWeb3(Web3):
def __init__(self, ....):
  ... do bunch of stuff...
  super().__init__(*args, **kwargs)
  ... do bunch more stuff...
```

This setup worked fine with web3 version == 5.23.1. However, switching to web3==5.30.0 breaks this, with error

```
[rest of callstack omitted]
 File “[redacted]/web3/main.py”, line 251, in __init__
    self.attach_modules(modules)
  File “[redacted]/web3/main.py”, line 340, in attach_modules
    _attach_modules(self, modules)
  File “[redacted]/web3/_utils/module.py”, line 68, in attach_modules
    setattr(parent_module, module_name, module_class(w3))
  File “[redacted]/web3/module.py”, line 93, in __init__
    self.codec: ABICodec = web3.codec
AttributeError: ‘NoneType’ object has no attribute ‘codec’
```

This used to work in older versions because the parent_module is assumed to be a Web3 object and stored accordingly - [see logic](https://github.com/ethereum/web3.py/blob/2b229d2e16792e52bb47108e665c6a8937c188a2/web3/_utils/module.py#L33-L35).

Compare this to the somewhat brittle name checking that happens in 5.30.0 - [see logic](https://github.com/ethereum/web3.py/blob/ee5f0543ad91e4ba991997e35f50719ceb58135d/web3/_utils/module.py#L58-L60). This doesn't quite work because if this logic is being executed from a subclass of Web3, the name of the parent module appears as SubclassWeb3 (and not Web3).

### How can it be fixed?

Fill this section in if you know how this could or should be fixed.


Discussion:

Resolving patch:
diff --git a/web3/_utils/module.py b/web3/_utils/module.py
--- a/web3/_utils/module.py
+++ b/web3/_utils/module.py
@@ -55,9 +55,13 @@ def attach_modules(
                 "already has an attribute with that name"
             )
 
-        # The parent module is the ``Web3`` instance on first run of the loop
-        if type(parent_module).__name__ == 'Web3':
-            w3 = parent_module
+        # The parent module is the ``Web3`` instance on first run of the loop and w3 is
+        # None. Thus, set w3 to the parent_module. The import needs to happen locally
+        # due to circular import issues.
+        if w3 is None:
+            from web3 import Web3
+            if isinstance(parent_module, Web3):
+                w3 = parent_module
 
         module_init_params = _validate_init_params_and_return_if_found(module_class)
         if len(module_init_params) == 1:


Test patch:
diff --git a/tests/core/contracts/test_implicit_contract.py b/tests/core/contracts/test_implicit_contract.py
--- a/tests/core/contracts/test_implicit_contract.py
+++ b/tests/core/contracts/test_implicit_contract.py
@@ -70,13 +70,13 @@ def test_implicitcontract_transact_default(web3, math_contract, get_transaction_
     assert is_integer(start_count)  # Verify correct type
     # When a function is called that defaults to transact
     blocknum, starting_txns = get_transaction_count("pending")
-    with pytest.warns(DeprecationWarning,
-                      match='deprecated in favor of classic contract syntax') as warnings:
+    with pytest.warns(
+        DeprecationWarning,
+        match='deprecated in favor of classic contract syntax'
+    ):
         math_contract.increment(transact={})
         # Check that a transaction was made and not a call
         assert math_contract.counter() - start_count == 1
-        # Check that the correct number of warnings are raised
-        assert len(warnings) == 2
     # (Auto-mining is enabled, so query by block number)
     assert get_transaction_count(blocknum) == starting_txns + 1
     # Check that only one block was mined
@@ -102,12 +102,13 @@ def test_implicitcontract_transact_override(math_contract, get_transaction_count
     assert is_integer(start_count)  # Verify correct type
     # When a function is called with call override that defaults to transact
     blocknum, starting_txns = get_transaction_count("pending")
-    with pytest.warns(DeprecationWarning,
-                      match='deprecated in favor of classic contract syntax') as warnings:
+    with pytest.warns(
+        DeprecationWarning,
+        match='deprecated in favor of classic contract syntax'
+    ):
         math_contract.increment(call={})
         # Check that a call was made and not a transact
         assert math_contract.counter() - start_count == 0
-        assert len(warnings) == 2
     # (Auto-mining is enabled, so query by block number)
     assert get_transaction_count(blocknum) == starting_txns
     # Check that no blocks were mined
diff --git a/tests/core/web3-module/test_web3_inheritance.py b/tests/core/web3-module/test_web3_inheritance.py
new file mode 100644
--- /dev/null
+++ b/tests/core/web3-module/test_web3_inheritance.py
@@ -0,0 +1,12 @@
+from web3 import (
+    EthereumTesterProvider,
+    Web3,
+)
+
+
+def test_classes_may_inherit_from_web3():
+    class InheritsFromWeb3(Web3):
+        pass
+
+    inherited_w3 = InheritsFromWeb3(EthereumTesterProvider())
+    assert inherited_w3.eth.chain_id == 61
diff --git a/tests/ens/test_v6_warnings.py b/tests/ens/test_v6_warnings.py
--- a/tests/ens/test_v6_warnings.py
+++ b/tests/ens/test_v6_warnings.py
@@ -25,7 +25,8 @@ def test_resolver_future_warning_when_name_is_missing(ens):
 
     # assert no warning when `name` is passed in as a kwarg
     with warnings.catch_warnings():
-        warnings.simplefilter("error")  # turn all warnings to errors
+        # turn all `FutureWarning` warnings to errors:
+        warnings.simplefilter("error", category=FutureWarning)
         assert ens.resolver(name="EtH")
 
     # assert TypeError if both arguments passed in