Repository: ethereum/web3.py (python)

Issue:
`request_information_cache_size` no longer available in v7
### What happened?

Hi, folks. Working through the upgrade to V7 in my code. In our websocket flow (eth_subscribe) there are a significant amount of initial requests we make at startup. In V6, the default size for the request cache was an issue but we were able to increase its size via `request_information_cache_size`. 

In V7 that's no longer an option. 

Ideally we need a way to offer users back the ability to modify the size of the cache.

https://github.com/ethereum/web3.py/blob/6279d995b9f0a005eb15fe3ba9c66638144b2d2d/web3/providers/persistent/request_processor.py#L90

I've confirmed that increasing this value fixes our problem.

 

### Code that produced the error

```python

```

### Full error output

```shell

```

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

_No response_

### web3 Version

7.10.0

### Python Version

3.12.10

### Operating System

macos

### Output from `pip freeze`

```shell

```

Pull request description:
Allow setting the request information cache size:
### What was wrong?

- This was a feature that had been requested while the WebsocketProviderV2 was being developed, in beta. We did not forward-port this to the WebSocketProvider in ``v7``. relevant ``v6`` PR: https://github.com/ethereum/web3.py/pull/3226

Closes #3660

### Todo:

- [x] Clean up commit history
- [x] Add or update documentation related to these changes
- [x] Add entry to the [release notes](https://github.com/ethereum/web3.py/blob/main/newsfragments/README.md)

#### Cute Animal Picture

<img width="475" alt="Screenshot 2025-04-14 at 15 42 59" src="https://github.com/user-attachments/assets/8631d6b6-f826-45d6-abda-8a8a4dd50560" />


Patch:
diff --git a/docs/providers.rst b/docs/providers.rst
index 9968189b..21e9c490 100644
--- a/docs/providers.rst
+++ b/docs/providers.rst
@@ -233,7 +233,9 @@ Persistent Connection Base Class
 .. py:class:: web3.providers.persistent.PersistentConnectionProvider(\
         request_timeout: float = 50.0, \
         subscription_response_queue_size: int = 500, \
-        silence_listener_task_exceptions: bool = False\
+        silence_listener_task_exceptions: bool = False \
+        max_connection_retries: int = 5, \
+        request_information_cache_size: int = 500, \
     )
 
     This is a base provider class, inherited by the following providers:
@@ -262,6 +264,13 @@ Persistent Connection Base Class
       exceptions raised by the listener task are silenced. Defaults to ``False``,
       raising any exceptions that occur in the listener task.
 
+    * ``max_connection_retries`` is the maximum number of times to retry a connection
+      to the provider when initializing the provider. Defaults to ``5``.
+
+    * ``request_information_cache_size`` specifies the size of the transient cache for
+      storing request details, enabling the provider to process responses based on the
+      original request information. Defaults to ``500``.
+
 AsyncIPCProvider
 ++++++++++++++++
 
diff --git a/newsfragments/3662.feature.rst b/newsfragments/3662.feature.rst
new file mode 100644
index 00000000..7d95728b
--- /dev/null
+++ b/newsfragments/3662.feature.rst
@@ -0,0 +1,1 @@
+Allow setting the ``request_information_cache_size`` for ``PersistentConnectionProvider`` implementations.
diff --git a/web3/providers/persistent/persistent.py b/web3/providers/persistent/persistent.py
index 16502862..5d7385bb 100644
--- a/web3/providers/persistent/persistent.py
+++ b/web3/providers/persistent/persistent.py
@@ -87,12 +87,14 @@ class PersistentConnectionProvider(AsyncJSONBaseProvider, ABC):
         subscription_response_queue_size: int = 500,
         silence_listener_task_exceptions: bool = False,
         max_connection_retries: int = 5,
+        request_information_cache_size: int = 500,
         **kwargs: Any,
     ) -> None:
         super().__init__(**kwargs)
         self._request_processor = RequestProcessor(
             self,
             subscription_response_queue_size=subscription_response_queue_size,
+            request_information_cache_size=request_information_cache_size,
         )
         self._message_listener_task: Optional["asyncio.Task[None]"] = None
         self._batch_request_counter: Optional[int] = None
diff --git a/web3/providers/persistent/request_processor.py b/web3/providers/persistent/request_processor.py
index 65d4d998..6d747115 100644
--- a/web3/providers/persistent/request_processor.py
+++ b/web3/providers/persistent/request_processor.py
@@ -85,9 +85,12 @@ class RequestProcessor:
         self,
         provider: "PersistentConnectionProvider",
         subscription_response_queue_size: int = 500,
+        request_information_cache_size: int = 500,
     ) -> None:
         self._provider = provider
-        self._request_information_cache: SimpleCache = SimpleCache(500)
+        self._request_information_cache: SimpleCache = SimpleCache(
+            request_information_cache_size
+        )
         self._request_response_cache: SimpleCache = SimpleCache(500)
         self._subscription_response_queue: TaskReliantQueue[
             Union[RPCResponse, TaskNotRunning]
@@ -152,6 +155,12 @@ class RequestProcessor:
             cache_key,
             request_info,
         )
+        if self._request_information_cache.is_full():
+            self._provider.logger.warning(
+                "Request information cache is full. This may result in unexpected "
+                "behavior. Consider increasing the ``request_information_cache_size`` "
+                "on the provider."
+            )
         return cache_key
 
     def pop_cached_request_information(
diff --git a/web3/utils/caching.py b/web3/utils/caching.py
index 75f8dbbc..839ec929 100644
--- a/web3/utils/caching.py
+++ b/web3/utils/caching.py
@@ -25,6 +25,12 @@ class SimpleCache:
         self._size = size
         self._data: OrderedDict[str, Any] = OrderedDict()
 
+    def __contains__(self, key: str) -> bool:
+        return key in self._data
+
+    def __len__(self) -> int:
+        return len(self._data)
+
     def cache(self, key: str, value: Any) -> Tuple[Any, Dict[str, Any]]:
         evicted_items = {}
         # If the key is already in the OrderedDict just update it
@@ -59,11 +65,8 @@ class SimpleCache:
     def popitem(self, last: bool = True) -> Tuple[str, Any]:
         return self._data.popitem(last=last)
 
-    def __contains__(self, key: str) -> bool:
-        return key in self._data
-
-    def __len__(self) -> int:
-        return len(self._data)
+    def is_full(self) -> bool:
+        return len(self._data) >= self._size
 
     # -- async utility methods -- #
 


Test patch:
diff --git a/tests/core/providers/test_websocket_provider.py b/tests/core/providers/test_websocket_provider.py
index b8478bf9..2965addb 100644
--- a/tests/core/providers/test_websocket_provider.py
+++ b/tests/core/providers/test_websocket_provider.py
@@ -510,3 +510,27 @@ async def test_websocket_provider_raises_errors_from_cache_not_tied_to_a_request
         with pytest.raises(Web3RPCError, match="Request shutdown"):
             await asyncio.sleep(0.1)
             await async_w3.eth.block_number
+
+
+@pytest.mark.asyncio
+async def test_req_info_cache_size_can_be_set_and_warns_when_full(caplog):
+    with patch(
+        "web3.providers.persistent.websocket.connect",
+        new=lambda *_1, **_2: _mocked_ws_conn(),
+    ):
+        async_w3 = await AsyncWeb3(
+            WebSocketProvider("ws://mocked", request_information_cache_size=1)
+        )
+        async_w3.provider._request_processor.cache_request_information(
+            RPCEndpoint("eth_getBlockByNumber"),
+            ["latest"],
+            tuple(),
+            tuple(),
+        )
+
+        assert len(async_w3.provider._request_processor._request_information_cache) == 1
+        assert (
+            "Request information cache is full. This may result in unexpected "
+            "behavior. Consider increasing the ``request_information_cache_size`` "
+            "on the provider."
+        ) in caplog.text