Skip to content

Commit 02c38ac

Browse files
committed
(bugfix) Use a defined __hash__() for unhashable, unnamed middleware:
Fixes a bug where the middleware which are not hashable, classes or the ``build`` method of builder classes, end up being given the same identifier when a name is not provided for the middleware in the onion. This fixes the issue by defining a unique hash for the base ``Web3Middleware`` class and using that hash in the case where the middleware is unhashable and therefore indistinguishable from another middleware.
1 parent 9e21be0 commit 02c38ac

File tree

3 files changed

+30
-11
lines changed

3 files changed

+30
-11
lines changed

newsfragments/3463.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Specify a unique ``__hash__()`` for unhashable ``Web3Middleware`` types and use this hash as the middleware onion key when a name is not provided for the middleware. This fixes a bug where different middleware were given the same name and therefore raised errors.

web3/datastructures.py

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ def add(self, element: TValue, name: Optional[TKey] = None) -> None:
182182
if name is None:
183183
name = cast(TKey, element)
184184

185-
name = self._repr_if_not_hashable(name)
185+
name = self._build_tkey(name)
186186

187187
if name in self._queue:
188188
if name is element:
@@ -219,7 +219,7 @@ def inject(
219219
if name is None:
220220
name = cast(TKey, element)
221221

222-
name = self._repr_if_not_hashable(name)
222+
name = self._build_tkey(name)
223223

224224
self._queue.move_to_end(name, last=False)
225225
elif layer == len(self._queue):
@@ -233,7 +233,7 @@ def clear(self) -> None:
233233
self._queue.clear()
234234

235235
def replace(self, old: TKey, new: TKey) -> TValue:
236-
old_name = self._repr_if_not_hashable(old)
236+
old_name = self._build_tkey(old)
237237

238238
if old_name not in self._queue:
239239
raise Web3ValueError(
@@ -248,15 +248,25 @@ def replace(self, old: TKey, new: TKey) -> TValue:
248248
self._queue[old_name] = new
249249
return to_be_replaced
250250

251-
def _repr_if_not_hashable(self, value: TKey) -> TKey:
251+
@staticmethod
252+
def _build_tkey(value: TKey) -> TKey:
252253
try:
253254
value.__hash__()
255+
return value
254256
except TypeError:
255-
value = cast(TKey, repr(value))
256-
return value
257+
# unhashable, unnamed elements
258+
if not callable(value):
259+
raise Web3TypeError(
260+
f"Expected a callable or hashable type, got {type(value)}"
261+
)
262+
# This will either be ``Web3Middleware`` class or the ``build`` method of a
263+
# ``Web3MiddlewareBuilder``. Instantiate with empty ``Web3`` and use a
264+
# unique identifier with the ``__hash__()`` as the TKey.
265+
v = value(None)
266+
return cast(TKey, f"{v.__class__}<{v.__hash__()}>")
257267

258268
def remove(self, old: TKey) -> None:
259-
old_name = self._repr_if_not_hashable(old)
269+
old_name = self._build_tkey(old)
260270
if old_name not in self._queue:
261271
raise Web3ValueError("You can only remove something that has been added")
262272
del self._queue[old_name]
@@ -270,8 +280,8 @@ def middleware(self) -> Sequence[Any]:
270280
return [(val, key) for key, val in reversed(self._queue.items())]
271281

272282
def _replace_with_new_name(self, old: TKey, new: TKey) -> None:
273-
old_name = self._repr_if_not_hashable(old)
274-
new_name = self._repr_if_not_hashable(new)
283+
old_name = self._build_tkey(old)
284+
new_name = self._build_tkey(new)
275285

276286
self._queue[new_name] = new
277287
found_old = False
@@ -293,11 +303,11 @@ def __add__(self, other: Any) -> "NamedElementOnion[TKey, TValue]":
293303
return NamedElementOnion(cast(List[Any], combined.items()))
294304

295305
def __contains__(self, element: Any) -> bool:
296-
element_name = self._repr_if_not_hashable(element)
306+
element_name = self._build_tkey(element)
297307
return element_name in self._queue
298308

299309
def __getitem__(self, element: TKey) -> TValue:
300-
element_name = self._repr_if_not_hashable(element)
310+
element_name = self._build_tkey(element)
301311
return self._queue[element_name]
302312

303313
def __len__(self) -> int:

web3/middleware/base.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,14 @@ class Web3Middleware:
4040
def __init__(self, w3: Union["AsyncWeb3", "Web3"]) -> None:
4141
self._w3 = w3
4242

43+
def __hash__(self) -> int:
44+
return hash(f"{self.__class__.__name__}({str(self.__dict__)})")
45+
46+
def __eq__(self, other: Any) -> bool:
47+
if not isinstance(other, Web3Middleware):
48+
return False
49+
return self.__hash__() == other.__hash__()
50+
4351
# -- sync -- #
4452

4553
def wrap_make_request(self, make_request: "MakeRequestFn") -> "MakeRequestFn":

0 commit comments

Comments
 (0)