Skip to content

Commit 684fd0e

Browse files
committed
Remove dictionary cache support for simple-cache-middleware
- Remove dictionary cache support altogether, for simple cache middleware, in favor of using the ``SimpleCache`` class. - Handle missing keys in ``SimpleCache`` a bit more gracefully than throwing a ``KeyError``, just return ``None`` if the key is not in the cache.
1 parent 97f112d commit 684fd0e

File tree

11 files changed

+117
-166
lines changed

11 files changed

+117
-166
lines changed

docs/web3.utils.rst

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,40 @@ Utils
33

44
.. py:module:: web3.utils
55
6-
The ``utils`` module houses public utility and helper functions.
6+
The ``utils`` module houses public utility functions and classes.
77

88
ABI
99
---
1010

11-
.. py:method:: Utils.get_abi_input_names(abi)
11+
.. py:method:: utils.get_abi_input_names(abi)
1212
1313
Return the ``input`` names for an ABI function or event.
1414

1515

16-
.. py:method:: Utils.get_abi_output_names(abi)
16+
.. py:method:: utils.get_abi_output_names(abi)
1717
1818
Return the ``output`` names an ABI function or event.
1919

2020

21+
Caching
22+
-------
23+
24+
.. py:class:: utils.SimpleCache
25+
26+
The main cache class being used internally by web3.py. In some cases, it may prove
27+
useful to set your own cache size and pass in your own instance of this class where
28+
supported.
29+
30+
2131
Exception Handling
2232
------------------
2333

24-
.. py:method:: Utils.handle_offchain_lookup(offchain_lookup_payload, transaction)
34+
.. py:method:: utils.handle_offchain_lookup(offchain_lookup_payload, transaction)
2535
2636
Handle ``OffchainLookup`` reverts on contract function calls manually. For an example, see :ref:`ccip-read-example`
2737
within the examples section.
2838

2939

30-
.. py:method:: Utils.async_handle_offchain_lookup(offchain_lookup_payload, transaction)
40+
.. py:method:: utils.async_handle_offchain_lookup(offchain_lookup_payload, transaction)
3141
3242
The async version of the ``handle_offchain_lookup()`` utility method described above.

newsfragments/2579.breaking.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Remove support for dictionary-based caches, for simple-cache-middleware, in favor of the internal ``SimpleCache`` class.

newsfragments/2579.feature.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
Async support for caching certain methods via ``async_simple_cache_middleware`` as well as constructing custom async caching middleware via ``async_construct_simple_cache_middleware``.
1+
Async support for caching certain methods via ``async_simple_cache_middleware`` as well as constructing custom async caching middleware via ``async_construct_simple_cache_middleware``. ``SimpleCache`` class was also added to the public ``utils`` module.

tests/core/middleware/test_simple_cache_middleware.py

Lines changed: 12 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
from web3 import Web3
77
from web3._utils.caching import (
8-
SimpleCache,
98
generate_cache_key,
109
)
1110
from web3.middleware import (
@@ -29,6 +28,9 @@
2928
from web3.types import (
3029
RPCEndpoint,
3130
)
31+
from web3.utils.caching import (
32+
SimpleCache,
33+
)
3234

3335

3436
@pytest.fixture
@@ -52,17 +54,7 @@ def w3(w3_base, result_generator_middleware):
5254
return w3_base
5355

5456

55-
def dict_cache_class_return_value_a():
56-
# test dictionary-based cache
57-
return {
58-
generate_cache_key(f"{threading.get_ident()}:{('fake_endpoint', [1])}"): {
59-
"result": "value-a"
60-
},
61-
}
62-
63-
64-
def simple_cache_class_return_value_a():
65-
# test `SimpleCache` class cache
57+
def simple_cache_return_value_a():
6658
_cache = SimpleCache()
6759
_cache.cache(
6860
generate_cache_key(f"{threading.get_ident()}:{('fake_endpoint', [1])}"),
@@ -71,30 +63,20 @@ def simple_cache_class_return_value_a():
7163
return _cache
7264

7365

74-
@pytest.mark.parametrize(
75-
"cache_class",
76-
(
77-
dict_cache_class_return_value_a,
78-
simple_cache_class_return_value_a,
79-
),
80-
)
81-
def test_simple_cache_middleware_pulls_from_cache(w3, cache_class):
82-
66+
def test_simple_cache_middleware_pulls_from_cache(w3):
8367
w3.middleware_onion.add(
8468
construct_simple_cache_middleware(
85-
cache_class=cache_class,
69+
cache=simple_cache_return_value_a(),
8670
rpc_whitelist={RPCEndpoint("fake_endpoint")},
8771
)
8872
)
8973

9074
assert w3.manager.request_blocking("fake_endpoint", [1]) == "value-a"
9175

9276

93-
@pytest.mark.parametrize("cache_class", (dict, SimpleCache))
94-
def test_simple_cache_middleware_populates_cache(w3, cache_class):
77+
def test_simple_cache_middleware_populates_cache(w3):
9578
w3.middleware_onion.add(
9679
construct_simple_cache_middleware(
97-
cache_class=cache_class,
9880
rpc_whitelist={RPCEndpoint("fake_endpoint")},
9981
)
10082
)
@@ -105,8 +87,7 @@ def test_simple_cache_middleware_populates_cache(w3, cache_class):
10587
assert w3.manager.request_blocking("fake_endpoint", [1]) != result
10688

10789

108-
@pytest.mark.parametrize("cache_class", (dict, SimpleCache))
109-
def test_simple_cache_middleware_does_not_cache_none_responses(w3_base, cache_class):
90+
def test_simple_cache_middleware_does_not_cache_none_responses(w3_base):
11091
counter = itertools.count()
11192
w3 = w3_base
11293

@@ -124,7 +105,6 @@ def result_cb(_method, _params):
124105

125106
w3.middleware_onion.add(
126107
construct_simple_cache_middleware(
127-
cache_class=cache_class,
128108
rpc_whitelist={RPCEndpoint("fake_endpoint")},
129109
)
130110
)
@@ -135,8 +115,7 @@ def result_cb(_method, _params):
135115
assert next(counter) == 2
136116

137117

138-
@pytest.mark.parametrize("cache_class", (dict, SimpleCache))
139-
def test_simple_cache_middleware_does_not_cache_error_responses(w3_base, cache_class):
118+
def test_simple_cache_middleware_does_not_cache_error_responses(w3_base):
140119
w3 = w3_base
141120
w3.middleware_onion.add(
142121
construct_error_generator_middleware(
@@ -148,7 +127,6 @@ def test_simple_cache_middleware_does_not_cache_error_responses(w3_base, cache_c
148127

149128
w3.middleware_onion.add(
150129
construct_simple_cache_middleware(
151-
cache_class=cache_class,
152130
rpc_whitelist={RPCEndpoint("fake_endpoint")},
153131
)
154132
)
@@ -161,14 +139,9 @@ def test_simple_cache_middleware_does_not_cache_error_responses(w3_base, cache_c
161139
assert str(err_a) != str(err_b)
162140

163141

164-
@pytest.mark.parametrize("cache_class", (dict, SimpleCache))
165-
def test_simple_cache_middleware_does_not_cache_endpoints_not_in_whitelist(
166-
w3,
167-
cache_class,
168-
):
142+
def test_simple_cache_middleware_does_not_cache_endpoints_not_in_whitelist(w3):
169143
w3.middleware_onion.add(
170144
construct_simple_cache_middleware(
171-
cache_class=cache_class,
172145
rpc_whitelist={RPCEndpoint("fake_endpoint")},
173146
)
174147
)
@@ -184,7 +157,6 @@ def test_simple_cache_middleware_does_not_cache_endpoints_not_in_whitelist(
184157

185158
async def _async_simple_cache_middleware_for_testing(make_request, async_w3):
186159
middleware = await async_construct_simple_cache_middleware(
187-
cache_class=SimpleCache,
188160
rpc_whitelist={RPCEndpoint("fake_endpoint")},
189161
)
190162
return await middleware(make_request, async_w3)
@@ -201,17 +173,10 @@ def async_w3():
201173

202174

203175
@pytest.mark.asyncio
204-
@pytest.mark.parametrize(
205-
"cache_class",
206-
(
207-
dict_cache_class_return_value_a,
208-
simple_cache_class_return_value_a,
209-
),
210-
)
211-
async def test_async_simple_cache_middleware_pulls_from_cache(async_w3, cache_class):
176+
async def test_async_simple_cache_middleware_pulls_from_cache(async_w3):
212177
async def _properly_awaited_middleware(make_request, _async_w3):
213178
middleware = await async_construct_simple_cache_middleware(
214-
cache_class=cache_class,
179+
cache=simple_cache_return_value_a(),
215180
rpc_whitelist={RPCEndpoint("fake_endpoint")},
216181
)
217182
return await middleware(make_request, _async_w3)

tests/core/utilities/test_request.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,15 @@
2525
request,
2626
)
2727
from web3._utils.caching import (
28-
SimpleCache,
2928
generate_cache_key,
3029
)
3130
from web3._utils.request import (
3231
cache_and_return_async_session,
3332
cache_and_return_session,
3433
)
34+
from web3.utils.caching import (
35+
SimpleCache,
36+
)
3537

3638

3739
class MockedResponse:
@@ -149,10 +151,9 @@ def test_cache_session_class():
149151
assert "1" not in cache
150152
assert "1" in evicted_items
151153

152-
with pytest.raises(KeyError):
153-
# This should throw a KeyError since the cache size was 2 and 3 were inserted
154-
# the first inserted cached item was removed and returned in evicted items
155-
cache.get_cache_entry("1")
154+
# Cache size is `3`. We should have "2" and "3" in the cache and "1" should have
155+
# been evicted.
156+
assert cache.get_cache_entry("1") is None
156157

157158
# clear cache
158159
request._session_cache.clear()

web3/_utils/caching.py

Lines changed: 0 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
11
import collections
2-
from collections import (
3-
OrderedDict,
4-
)
52
import hashlib
63
from typing import (
74
Any,
8-
Dict,
9-
Union,
105
)
116

127
from eth_utils import (
@@ -39,66 +34,3 @@ def generate_cache_key(value: Any) -> str:
3934
raise TypeError(
4035
f"Cannot generate cache key for value {value} of type {type(value)}"
4136
)
42-
43-
44-
class SimpleCache:
45-
def __init__(self, size: int = 100):
46-
self._size = size
47-
self._data: OrderedDict[str, Any] = OrderedDict()
48-
49-
def cache(self, key: str, value: Any) -> Dict[str, Any]:
50-
evicted_items = None
51-
# If the key is already in the OrderedDict just update it
52-
# and don't evict any values. Ideally, we could still check to see
53-
# if there are too many items in the OrderedDict but that may rearrange
54-
# the order it should be unlikely that the size could grow over the limit
55-
if key not in self._data:
56-
while len(self._data) >= self._size:
57-
if evicted_items is None:
58-
evicted_items = {}
59-
k, v = self._data.popitem(last=False)
60-
evicted_items[k] = v
61-
self._data[key] = value
62-
return evicted_items
63-
64-
def get_cache_entry(self, key: str) -> Any:
65-
return self._data[key]
66-
67-
def clear(self) -> None:
68-
self._data.clear()
69-
70-
def items(self) -> Dict[str, Any]:
71-
return self._data
72-
73-
def __contains__(self, item: str) -> bool:
74-
return item in self._data
75-
76-
def __len__(self) -> int:
77-
return len(self._data)
78-
79-
80-
def type_aware_cache_entry(
81-
cache: Union[Dict[str, Any], SimpleCache], cache_key: str, value: Any
82-
) -> None:
83-
"""
84-
Commit an entry to a dictionary-based cache or to a `SimpleCache` class.
85-
"""
86-
if isinstance(cache, SimpleCache):
87-
cache.cache(cache_key, value)
88-
else:
89-
cache[cache_key] = value
90-
91-
92-
def type_aware_get_cache_entry(
93-
cache: Union[Dict[str, Any], SimpleCache],
94-
cache_key: str,
95-
) -> Any:
96-
"""
97-
Get a cache entry, with `cache_key`, from a dictionary-based cache or a
98-
`SimpleCache` class.
99-
"""
100-
if isinstance(cache, SimpleCache) and cache_key in cache.items():
101-
return cache.get_cache_entry(cache_key)
102-
elif isinstance(cache, dict) and cache_key in cache:
103-
return cache[cache_key]
104-
return None

web3/_utils/request.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,11 @@
2727
async_lock,
2828
)
2929
from web3._utils.caching import (
30-
SimpleCache,
3130
generate_cache_key,
3231
)
32+
from web3.utils.caching import (
33+
SimpleCache,
34+
)
3335

3436
logger = logging.getLogger(__name__)
3537

@@ -40,7 +42,7 @@ def get_default_http_endpoint() -> URI:
4042
return URI(os.environ.get("WEB3_HTTP_PROVIDER_URI", "http://localhost:8545"))
4143

4244

43-
_session_cache = SimpleCache(size=100)
45+
_session_cache = SimpleCache()
4446
_session_cache_lock = threading.Lock()
4547

4648

@@ -111,7 +113,7 @@ def _close_evicted_sessions(evicted_sessions: List[requests.Session]) -> None:
111113
# --- async --- #
112114

113115

114-
_async_session_cache = SimpleCache(size=100)
116+
_async_session_cache = SimpleCache()
115117
_async_session_cache_lock = threading.Lock()
116118
_async_session_pool = ThreadPoolExecutor(max_workers=1)
117119

0 commit comments

Comments
 (0)