Repository: ethereum/web3.py
Pull request #1778 (2020-10-21 22:27:21)

Move filter method to use Method class
### What was wrong?
Filter is the last method in Eth that wasn't using Method. This PR fixes that. 

### Todo:
- [x] Add entry to the [release notes](https://github.com/ethereum/web3.py/blob/master/newsfragments/README.md)
- [x] Make sure tests are using the correct middleware
- [x] Better solution than passing around a string in place of a method
- [x] Lint
- [x] Get rid of TODOs
- [x] Manually test

#### Cute Animal Picture

![image](https://user-images.githubusercontent.com/6540608/99317791-d821eb00-2823-11eb-872e-f4eea1c116f3.png)



kclowes: I think this is ready for a final look @marcgarreau and @pipermerriam!

Patch:
diff --git a/newsfragments/1778.misc.rst b/newsfragments/1778.misc.rst
new file mode 100644
index 00000000..7df91605
--- /dev/null
+++ b/newsfragments/1778.misc.rst
@@ -0,0 +1,1 @@
+Move eth.filter method to use ``Method`` class
diff --git a/web3/_utils/filters.py b/web3/_utils/filters.py
index d31c87c1..fb93fac9 100644
--- a/web3/_utils/filters.py
+++ b/web3/_utils/filters.py
@@ -9,6 +9,7 @@ from typing import (
     Optional,
     Sequence,
     Tuple,
+    Union,
 )
 
 from eth_abi.codec import (
@@ -23,6 +24,7 @@ from eth_typing import (
     TypeStr,
 )
 from eth_utils import (
+    is_hex,
     is_list_like,
     is_string,
     is_text,
@@ -46,15 +48,24 @@ from web3._utils.events import (
 from web3._utils.validation import (
     validate_address,
 )
+from web3.exceptions import (
+    ValidationError,
+)
 from web3.types import (
     ABIEvent,
     BlockIdentifier,
     FilterParams,
     LogReceipt,
+    RPCEndpoint,
 )
 
 if TYPE_CHECKING:
     from web3 import Web3  # noqa: F401
+    from web3.module import (  # noqa: F401
+        Module,
+        ModuleV2,
+    )
+    from web3.eth import Eth  # noqa: F401
 
 
 def construct_event_filter_params(
@@ -122,14 +133,16 @@ class Filter:
     poll_interval = None
     filter_id = None
 
-    def __init__(self, web3: "Web3", filter_id: HexStr) -> None:
-        self.web3 = web3
+    def __init__(self,
+                 filter_id: HexStr,
+                 eth_module: "Eth") -> None:
+        self.eth_module = eth_module
         self.filter_id = filter_id
         self.callbacks = []
         super().__init__()
 
     def __str__(self) -> str:
-        return "Filter for {0}".format(self.filter_id)
+        return f"Filter for {self.filter_id}"
 
     def format_entry(self, entry: LogReceipt) -> LogReceipt:
         """
@@ -148,11 +161,11 @@ class Filter:
         return filter(self.is_valid_entry, entries)
 
     def get_new_entries(self) -> List[LogReceipt]:
-        log_entries = self._filter_valid_entries(self.web3.eth.getFilterChanges(self.filter_id))
+        log_entries = self._filter_valid_entries(self.eth_module.getFilterChanges(self.filter_id))
         return self._format_log_entries(log_entries)
 
     def get_all_entries(self) -> List[LogReceipt]:
-        log_entries = self._filter_valid_entries(self.web3.eth.getFilterLogs(self.filter_id))
+        log_entries = self._filter_valid_entries(self.eth_module.getFilterLogs(self.filter_id))
         return self._format_log_entries(log_entries)
 
     def _format_log_entries(self,
@@ -203,7 +216,7 @@ class LogFilter(Filter):
         """
         self.data_filter_set = data_filter_set
         if any(data_filter_set):
-            self.data_filter_set_function = match_fn(self.web3, data_filter_set)
+            self.data_filter_set_function = match_fn(self.eth_module.codec, data_filter_set)
 
     def is_valid_entry(self, entry: LogReceipt) -> bool:
         if not self.data_filter_set:
@@ -235,7 +248,7 @@ def normalize_data_values(type_string: TypeStr, data_value: Any) -> Any:
 
 
 @curry
-def match_fn(w3: "Web3", match_values_and_abi: Collection[Tuple[str, Any]], data: Any) -> bool:
+def match_fn(codec: ABICodec, match_values_and_abi: Collection[Tuple[str, Any]], data: Any) -> bool:
     """Match function used for filtering non-indexed event arguments.
 
     Values provided through the match_values_and_abi parameter are
@@ -243,13 +256,13 @@ def match_fn(w3: "Web3", match_values_and_abi: Collection[Tuple[str, Any]], data
     """
     abi_types, all_match_values = zip(*match_values_and_abi)
 
-    decoded_values = w3.codec.decode_abi(abi_types, HexBytes(data))
+    decoded_values = codec.decode_abi(abi_types, HexBytes(data))
     for data_value, match_values, abi_type in zip(decoded_values, all_match_values, abi_types):
         if match_values is None:
             continue
         normalized_data = normalize_data_values(abi_type, data_value)
         for value in match_values:
-            if not w3.is_encodable(abi_type, value):
+            if not codec.is_encodable(abi_type, value):
                 raise ValueError(
                     f"Value {value} is of the wrong abi type. "
                     f"Expected {abi_type} typed value."
@@ -260,3 +273,41 @@ def match_fn(w3: "Web3", match_values_and_abi: Collection[Tuple[str, Any]], data
             return False
 
     return True
+
+
+class _UseExistingFilter(Exception):
+    """
+    Internal exception, raised when a filter_id is passed into w3.eth.filter()
+    """
+    def __init__(
+        self,
+        filter_id: Union[str, FilterParams, HexStr]
+    ) -> None:
+        self.filter_id = filter_id
+
+
+@curry
+def select_filter_method(
+    value: Union[str, FilterParams, HexStr],
+    if_new_block_filter: RPCEndpoint,
+    if_new_pending_transaction_filter: RPCEndpoint,
+    if_new_filter: RPCEndpoint,
+) -> RPCEndpoint:
+
+    if is_string(value):
+        if value == "latest":
+            return if_new_block_filter
+        elif value == "pending":
+            return if_new_pending_transaction_filter
+        elif is_hex(value):
+            raise _UseExistingFilter(value)
+        else:
+            raise ValidationError("Filter argument needs to be either 'latest',"
+                                  " 'pending', or a hex-encoded filter_id. Filter argument"
+                                  f" is: {value}")
+    elif isinstance(value, dict):
+        return if_new_filter
+    else:
+        raise ValidationError("Filter argument needs to be either the string "
+                              "'pending' or 'latest', a filter_id, "
+                              f"or a filter params dictionary. Filter argument is: {value}")
diff --git a/web3/_utils/method_formatters.py b/web3/_utils/method_formatters.py
index 6c83d6b1..5d56e0a3 100644
--- a/web3/_utils/method_formatters.py
+++ b/web3/_utils/method_formatters.py
@@ -1,6 +1,7 @@
 import codecs
 import operator
 from typing import (
+    TYPE_CHECKING,
     Any,
     Callable,
     Collection,
@@ -52,6 +53,11 @@ from web3._utils.encoding import (
     hexstr_if_str,
     to_hex,
 )
+from web3._utils.filters import (
+    BlockFilter,
+    LogFilter,
+    TransactionFilter,
+)
 from web3._utils.formatters import (
     hex_to_integer,
     integer_to_hex,
@@ -86,6 +92,14 @@ from web3.types import (
     _Hash32,
 )
 
+if TYPE_CHECKING:
+    from web3 import Web3  # noqa: F401
+    from web3.module import (  # noqa: F401
+        Module,
+        ModuleV2,
+    )
+    from web3.eth import Eth  # noqa: F401
+
 
 def bytes_to_ascii(value: bytes) -> str:
     return codecs.decode(value, 'ascii')
@@ -580,16 +594,60 @@ NULL_RESULT_FORMATTERS: Dict[RPCEndpoint, Callable[..., Any]] = {
 }
 
 
+def filter_wrapper(
+    module: "Eth",
+    method: RPCEndpoint,
+    filter_id: HexStr,
+) -> Union[BlockFilter, TransactionFilter, LogFilter]:
+    if method == RPC.eth_newBlockFilter:
+        return BlockFilter(filter_id, eth_module=module)
+    elif method == RPC.eth_newPendingTransactionFilter:
+        return TransactionFilter(filter_id, eth_module=module)
+    elif method == RPC.eth_newFilter:
+        return LogFilter(filter_id, eth_module=module)
+    else:
+        raise NotImplementedError('Filter wrapper needs to be used with either '
+                                  f'{RPC.eth_newBlockFilter}, {RPC.eth_newPendingTransactionFilter}'
+                                  f' or {RPC.eth_newFilter}')
+
+
+FILTER_RESULT_FORMATTERS: Dict[RPCEndpoint, Callable[..., Any]] = {
+    RPC.eth_newPendingTransactionFilter: filter_wrapper,
+    RPC.eth_newBlockFilter: filter_wrapper,
+    RPC.eth_newFilter: filter_wrapper,
+}
+
+
+@to_tuple
+def apply_module_to_formatters(
+        formatters: Tuple[Callable[..., TReturn]],
+        module: Union["Module", "ModuleV2"],
+        method_name: Union[RPCEndpoint, Callable[..., RPCEndpoint]],
+) -> Iterable[Callable[..., TReturn]]:
+    for f in formatters:
+        yield partial(f, module, method_name)
+
+
 def get_result_formatters(
-    method_name: Union[RPCEndpoint, Callable[..., RPCEndpoint]]
+    method_name: Union[RPCEndpoint, Callable[..., RPCEndpoint]],
+    module: Union["Module", "ModuleV2"],
 ) -> Dict[str, Callable[..., Any]]:
     formatters = combine_formatters(
         (PYTHONIC_RESULT_FORMATTERS,),
         method_name
     )
-    attrdict_formatter = apply_formatter_if(is_dict and not_attrdict, AttributeDict.recursive)
+    formatters_requiring_module = combine_formatters(
+        (FILTER_RESULT_FORMATTERS,),
+        method_name
+    )
 
-    return compose(attrdict_formatter, *formatters)
+    partial_formatters = apply_module_to_formatters(
+        formatters_requiring_module,
+        module,
+        method_name
+    )
+    attrdict_formatter = apply_formatter_if(is_dict and not_attrdict, AttributeDict.recursive)
+    return compose(*partial_formatters, attrdict_formatter, *formatters)
 
 
 def get_error_formatters(
diff --git a/web3/eth.py b/web3/eth.py
index 7f3b0a39..8189cd58 100644
--- a/web3/eth.py
+++ b/web3/eth.py
@@ -46,10 +46,7 @@ from web3._utils.encoding import (
     to_hex,
 )
 from web3._utils.filters import (
-    BlockFilter,
-    Filter,
-    LogFilter,
-    TransactionFilter,
+    select_filter_method,
 )
 from web3._utils.rpc_abi import (
     RPC,
@@ -462,44 +459,42 @@ class Eth(ModuleV2, Module):
         mungers=[estimate_gas_munger]
     )
 
-    def filter(
-        self, filter_params: Optional[Union[str, FilterParams]] = None,
+    def filter_munger(
+        self,
+        filter_params: Optional[Union[str, FilterParams]] = None,
         filter_id: Optional[HexStr] = None
-    ) -> Filter:
+    ) -> Union[List[FilterParams], List[HexStr], List[str]]:
         if filter_id and filter_params:
             raise TypeError(
                 "Ambiguous invocation: provide either a `filter_params` or a `filter_id` argument. "
                 "Both were supplied."
             )
-        if is_string(filter_params):
-            if filter_params == "latest":
-                filter_id = self.web3.manager.request_blocking(
-                    RPC.eth_newBlockFilter, [],
-                )
-                return BlockFilter(self.web3, filter_id)
-            elif filter_params == "pending":
-                filter_id = self.web3.manager.request_blocking(
-                    RPC.eth_newPendingTransactionFilter, [],
-                )
-                return TransactionFilter(self.web3, filter_id)
+        if isinstance(filter_params, dict):
+            return [filter_params]
+        elif is_string(filter_params):
+            if filter_params in ['latest', 'pending']:
+                return [filter_params]
             else:
                 raise ValueError(
                     "The filter API only accepts the values of `pending` or "
                     "`latest` for string based filters"
                 )
-        elif isinstance(filter_params, dict):
-            _filter_id = self.web3.manager.request_blocking(
-                RPC.eth_newFilter,
-                [filter_params],
-            )
-            return LogFilter(self.web3, _filter_id)
         elif filter_id and not filter_params:
-            return LogFilter(self.web3, filter_id)
+            return [filter_id]
         else:
             raise TypeError("Must provide either filter_params as a string or "
                             "a valid filter object, or a filter_id as a string "
                             "or hex.")
 
+    filter: Method[Callable[..., Any]] = Method(
+        method_choice_depends_on_args=select_filter_method(
+            if_new_block_filter=RPC.eth_newBlockFilter,
+            if_new_pending_transaction_filter=RPC.eth_newPendingTransactionFilter,
+            if_new_filter=RPC.eth_newFilter,
+        ),
+        mungers=[filter_munger],
+    )
+
     getFilterChanges: Method[Callable[[HexStr], List[LogReceipt]]] = Method(
         RPC.eth_getFilterChanges,
         mungers=[default_root_munger]
diff --git a/web3/main.py b/web3/main.py
index 5200b153..96485dab 100644
--- a/web3/main.py
+++ b/web3/main.py
@@ -166,14 +166,15 @@ class Web3:
         ens: ENS = cast(ENS, empty)
     ) -> None:
         self.manager = self.RequestManager(self, provider, middlewares)
+        # this codec gets used in the module initialization,
+        # so it needs to come before attach_modules
+        self.codec = ABICodec(build_default_registry())
 
         if modules is None:
             modules = get_default_modules()
 
         attach_modules(self, modules)
 
-        self.codec = ABICodec(build_default_registry())
-
         self.ens = ens
 
     @property
diff --git a/web3/method.py b/web3/method.py
index 00241964..6f14e282 100644
--- a/web3/method.py
+++ b/web3/method.py
@@ -27,6 +27,9 @@ from web3._utils.method_formatters import (
     get_request_formatters,
     get_result_formatters,
 )
+from web3._utils.rpc_abi import (
+    RPC,
+)
 from web3.types import (
     RPCEndpoint,
     TReturn,
@@ -171,15 +174,27 @@ class Method(Generic[TFunc]):
         self, module: Union["Module", "ModuleV2"], *args: Any, **kwargs: Any
     ) -> Tuple[Tuple[Union[RPCEndpoint, Callable[..., Any]], Any], Tuple[Any, Any]]:
         params = self.input_munger(module, args, kwargs)
-        # block_identifier is always the first argument passed in for methods where
-        # method_choice_depends_on_args is called.
+
         if self.method_choice_depends_on_args:
-            block_identifier = args[0]
-            self.json_rpc_method = self.method_choice_depends_on_args(value=block_identifier)
+            # If the method choice depends on the args that get passed in,
+            # the first parameter determines which method needs to be called
+            self.json_rpc_method = self.method_choice_depends_on_args(value=params[0])
+
+            pending_or_latest_filter_methods = [
+                RPC.eth_newPendingTransactionFilter,
+                RPC.eth_newBlockFilter
+            ]
+            if self.json_rpc_method in pending_or_latest_filter_methods:
+                # For pending or latest filter methods, use params to determine
+                # which method to call, but don't pass them through with the request
+                params = []
+
         method = self.method_selector_fn()
-        response_formatters = (self.result_formatters(method), self.error_formatters(method))
+        response_formatters = (self.result_formatters(method, module),
+                               self.error_formatters(method))
 
-        request = (method, _apply_request_formatters(params, self.request_formatters(method)))
+        request = (method,
+                   _apply_request_formatters(params, self.request_formatters(method)))
 
         return request, response_formatters
 
diff --git a/web3/middleware/filter.py b/web3/middleware/filter.py
index 49bb3810..874af7e1 100644
--- a/web3/middleware/filter.py
+++ b/web3/middleware/filter.py
@@ -22,6 +22,8 @@ from eth_typing import (
 )
 from eth_utils import (
     apply_key_map,
+    is_hex,
+    is_string,
     to_hex,
     to_int,
     to_list,
@@ -31,6 +33,12 @@ from eth_utils.toolz import (
     valfilter,
 )
 
+from web3._utils.formatters import (
+    hex_to_integer,
+)
+from web3._utils.rpc_abi import (
+    RPC,
+)
 from web3.types import (  # noqa: F401
     FilterParams,
     LatestBlockParam,
@@ -102,7 +110,6 @@ def block_ranges(
        Ranges do not overlap to facilitate use as ``toBlock``, ``fromBlock``
        json-rpc arguments, which are both inclusive.
     """
-
     if last_block is not None and start_block > last_block:
         raise TypeError(
             "Incompatible start and stop arguments.",
@@ -212,6 +219,8 @@ def get_logs_multipart(
 
 
 class RequestLogs:
+    _from_block: BlockNumber
+
     def __init__(
         self,
         w3: "Web3",
@@ -227,6 +236,8 @@ class RequestLogs:
         self.w3 = w3
         if from_block is None or from_block == "latest":
             self._from_block = BlockNumber(w3.eth.blockNumber + 1)
+        elif is_string(from_block) and is_hex(from_block):
+            self._from_block = BlockNumber(hex_to_integer(from_block))  # type: ignore
         else:
             # cast b/c LatestBlockParam is handled above
             self._from_block = cast(BlockNumber, from_block)
@@ -243,6 +254,8 @@ class RequestLogs:
             to_block = self.w3.eth.blockNumber
         elif self._to_block == "latest":
             to_block = self.w3.eth.blockNumber
+        elif is_hex(self._to_block):
+            to_block = BlockNumber(hex_to_integer(self._to_block))  # type: ignore
         else:
             to_block = cast(BlockNumber, self._to_block)
 
@@ -282,11 +295,13 @@ FILTER_PARAMS_KEY_MAP = {
 
 NEW_FILTER_METHODS = set([
     "eth_newBlockFilter",
-    "eth_newFilter"])
+    "eth_newFilter",
+])
 
 FILTER_CHANGES_METHODS = set([
     "eth_getFilterChanges",
-    "eth_getFilterLogs"])
+    "eth_getFilterLogs",
+])
 
 
 class RequestBlocks:
@@ -331,10 +346,10 @@ def local_filter_middleware(
             filter_id = next(filter_id_counter)
 
             _filter: Union[RequestLogs, RequestBlocks]
-            if method == "eth_newFilter":
+            if method == RPC.eth_newFilter:
                 _filter = RequestLogs(w3, **apply_key_map(FILTER_PARAMS_KEY_MAP, params[0]))
 
-            elif method == "eth_newBlockFilter":
+            elif method == RPC.eth_newBlockFilter:
                 _filter = RequestBlocks(w3)
 
             else:
@@ -349,9 +364,9 @@ def local_filter_middleware(
             if filter_id not in filters:
                 return make_request(method, params)
             _filter = filters[filter_id]
-            if method == "eth_getFilterChanges":
+            if method == RPC.eth_getFilterChanges:
                 return {"result": next(_filter.filter_changes)}
-            elif method == "eth_getFilterLogs":
+            elif method == RPC.eth_getFilterLogs:
                 # type ignored b/c logic prevents RequestBlocks which doesn't implement get_logs
                 return {"result": _filter.get_logs()}  # type: ignore
             else:
diff --git a/web3/module.py b/web3/module.py
index 07db93d6..4e881e99 100644
--- a/web3/module.py
+++ b/web3/module.py
@@ -4,14 +4,22 @@ from typing import (
     Callable,
     Coroutine,
     Optional,
+    TypeVar,
     Union,
 )
 
+from eth_abi.codec import (
+    ABICodec,
+)
 from eth_utils.toolz import (
     curry,
     pipe,
 )
 
+from web3._utils.filters import (
+    LogFilter,
+    _UseExistingFilter,
+)
 from web3.method import (
     Method,
 )
@@ -34,12 +42,18 @@ def apply_result_formatters(
         return result
 
 
+TReturn = TypeVar('TReturn')
+
+
 @curry
 def retrieve_blocking_method_call_fn(
-    w3: "Web3", module: Union["Module", "ModuleV2"], method: Method[Callable[..., Any]]
-) -> Callable[..., RPCResponse]:
-    def caller(*args: Any, **kwargs: Any) -> RPCResponse:
-        (method_str, params), response_formatters = method.process_params(module, *args, **kwargs)
+    w3: "Web3", module: Union["Module", "ModuleV2"], method: Method[Callable[..., TReturn]]
+) -> Callable[..., Union[TReturn, LogFilter]]:
+    def caller(*args: Any, **kwargs: Any) -> Union[TReturn, LogFilter]:
+        try:
+            (method_str, params), response_formatters = method.process_params(module, *args, **kwargs)  # noqa: E501
+        except _UseExistingFilter as err:
+            return LogFilter(eth_module=module, filter_id=err.filter_id)
         result_formatters, error_formatters = response_formatters
         result = w3.manager.request_blocking(method_str, params, error_formatters)
         return apply_result_formatters(result_formatters, result)
@@ -100,3 +114,4 @@ class ModuleV2(Module):
         else:
             self.retrieve_caller_fn = retrieve_blocking_method_call_fn(web3, self)
         self.web3 = web3
+        self.codec: ABICodec = web3.codec


Test patch:
diff --git a/tests/core/filtering/test_contract_past_event_filtering.py b/tests/core/filtering/test_contract_past_event_filtering.py
index 0a5bfa53..85009ef8 100644
--- a/tests/core/filtering/test_contract_past_event_filtering.py
+++ b/tests/core/filtering/test_contract_past_event_filtering.py
@@ -4,9 +4,6 @@ from eth_utils import (
     is_same_address,
 )
 
-# Ignore warning in pyethereum 1.6 - will go away with the upgrade
-pytestmark = pytest.mark.filterwarnings("ignore:implicit cast from 'char *'")
-
 
 @pytest.mark.parametrize('call_as_instance', (True, False))
 @pytest.mark.parametrize('api_style', ('v4', 'build_filter'))
diff --git a/tests/core/filtering/test_existing_filter_instance.py b/tests/core/filtering/test_existing_filter_instance.py
index 919245b6..99480e41 100644
--- a/tests/core/filtering/test_existing_filter_instance.py
+++ b/tests/core/filtering/test_existing_filter_instance.py
@@ -17,7 +17,7 @@ def filter_id(web3):
     return block_filter.filter_id
 
 
-def test_instatiate_existing_filter(web3, sleep_interval, wait_for_block, filter_id):
+def test_instantiate_existing_filter(web3, sleep_interval, wait_for_block, filter_id):
     with pytest.raises(TypeError):
         web3.eth.filter('latest', filter_id)
     with pytest.raises(TypeError):
diff --git a/tests/core/filtering/test_filters_against_many_blocks.py b/tests/core/filtering/test_filters_against_many_blocks.py
index ae27014d..5a864bbf 100644
--- a/tests/core/filtering/test_filters_against_many_blocks.py
+++ b/tests/core/filtering/test_filters_against_many_blocks.py
@@ -79,8 +79,7 @@ def test_block_filter(web3):
     assert len(block_filter.get_new_entries()) == web3.eth.blockNumber
 
 
-def test_transaction_filter_with_mining(
-        web3):
+def test_transaction_filter_with_mining(web3):
 
     transaction_filter = web3.eth.filter("pending")
 
diff --git a/tests/core/filtering/test_utils_functions.py b/tests/core/filtering/test_utils_functions.py
index 142808fd..6d6b5a43 100644
--- a/tests/core/filtering/test_utils_functions.py
+++ b/tests/core/filtering/test_utils_functions.py
@@ -131,7 +131,7 @@ from web3._utils.filters import (
 def test_match_fn_with_various_data_types(web3, data, expected, match_data_and_abi):
     abi_types, match_data = zip(*match_data_and_abi)
     encoded_data = web3.codec.encode_abi(abi_types, data)
-    assert match_fn(web3, match_data_and_abi, encoded_data) == expected
+    assert match_fn(web3.codec, match_data_and_abi, encoded_data) == expected
 
 
 @pytest.mark.parametrize(
@@ -206,7 +206,7 @@ def test_match_fn_with_various_data_types_strict(w3_strict_abi,
                                                  match_data_and_abi):
     abi_types, match_data = zip(*match_data_and_abi)
     encoded_data = w3_strict_abi.codec.encode_abi(abi_types, data)
-    assert match_fn(w3_strict_abi, match_data_and_abi, encoded_data) == expected
+    assert match_fn(w3_strict_abi.codec, match_data_and_abi, encoded_data) == expected
 
 
 @pytest.mark.parametrize(
@@ -233,4 +233,4 @@ def test_wrong_type_match_data(web3):
     abi_types, match_data = zip(*match_data_and_abi)
     encoded_data = web3.codec.encode_abi(abi_types, data)
     with pytest.raises(ValueError):
-        match_fn(web3, match_data_and_abi, encoded_data)
+        match_fn(web3.codec, match_data_and_abi, encoded_data)
diff --git a/tests/core/method-class/test_method.py b/tests/core/method-class/test_method.py
index e6694585..6b78980c 100644
--- a/tests/core/method-class/test_method.py
+++ b/tests/core/method-class/test_method.py
@@ -54,7 +54,7 @@ def test_get_formatters_default_formatter_for_falsy_config():
     )
 
     default_request_formatters = method.request_formatters(method.method_selector_fn())
-    default_result_formatters = method.result_formatters(method.method_selector_fn())
+    default_result_formatters = method.result_formatters(method.method_selector_fn(), 'some module')
     assert _apply_request_formatters(['a', 'b', 'c'], default_request_formatters) == ('a', 'b', 'c')
     assert apply_result_formatters(
         default_result_formatters, ['a', 'b', 'c']) == ['a', 'b', 'c']
diff --git a/tests/core/method-class/test_result_formatters.py b/tests/core/method-class/test_result_formatters.py
index e1b80a4e..97312589 100644
--- a/tests/core/method-class/test_result_formatters.py
+++ b/tests/core/method-class/test_result_formatters.py
@@ -19,7 +19,7 @@ from web3.providers import (
 )
 
 
-def result_formatter(method):
+def result_formatter(method, module):
     def formatter(self):
         return 'OKAY'
     return compose(formatter)
diff --git a/tests/core/middleware/test_filter_middleware.py b/tests/core/middleware/test_filter_middleware.py
index 4a9b525c..a6e76a84 100644
--- a/tests/core/middleware/test_filter_middleware.py
+++ b/tests/core/middleware/test_filter_middleware.py
@@ -23,10 +23,7 @@ from web3.providers.base import (
 
 class DummyProvider(BaseProvider):
     def make_request(self, method, params):
-        raise NotImplementedError("Cannot make request for {0}:{1}".format(
-            method,
-            params,
-        ))
+        raise NotImplementedError(f"Cannot make request for {method}:{params}")
 
 
 BLOCK_HASH = '0xfe88c94d860f01a17f961bf4bdfb6e0c6cd10d3fda5cc861e805ca1240c58553'
diff --git a/tests/core/utilities/test_select_filter_method.py b/tests/core/utilities/test_select_filter_method.py
new file mode 100644
index 00000000..d7d94ccc
--- /dev/null
+++ b/tests/core/utilities/test_select_filter_method.py
@@ -0,0 +1,38 @@
+import pytest
+
+from web3._utils.filters import (
+    _UseExistingFilter,
+    select_filter_method,
+)
+from web3.exceptions import (
+    ValidationError,
+)
+
+
+@pytest.mark.parametrize('value, expected', (
+    ('latest', 'new_block_filter'),
+    ('pending', 'new_pending_tx_filter'),
+    ({'to': '0x' + '00' * 20}, 'new_filter'),
+))
+def test_select_filter_method(value, expected):
+    filter_method = select_filter_method(
+        if_new_block_filter='new_block_filter',
+        if_new_pending_transaction_filter='new_pending_tx_filter',
+        if_new_filter='new_filter',
+    )
+    assert filter_method(value) == expected
+
+
+@pytest.mark.parametrize('value, error', (
+    ('0x0', _UseExistingFilter),
+    ('disallowed string', ValidationError),
+    (1, ValidationError),  # filter id needs to be a hexstr
+))
+def test_select_filter_method_raises_error(value, error):
+    filter_method = select_filter_method(
+        if_new_block_filter='new_block_filter',
+        if_new_pending_transaction_filter='new_pending_tx_filter',
+        if_new_filter='new_filter',
+    )
+    with pytest.raises(error):
+        filter_method(value)