`unittest.mock.ANY` is unusable in a `files` lookup
Python 3.12.7 respx 0.21.1
The documentation includes both of the following examples for files lookups:
respx.post("https://example.org/", files={"some_file": ANY})
respx.post("https://example.org/", files={"some_file": ("filename.txt", ANY)})
These don't actually work because since e670690fc547cddeb38867ffc4d37afdb1fa7a61, routes are compared via their hash(), and adding a route triggers a comparison to existing routes. ANY is unhashable, so any route containing it (at least in a files lookup) is also unhashable. Here's a minimal test to demonstrate (but to be clear, the same thing happens with the tuple form if it contains an ANY):
from unittest.mock import ANY
import respx
def test_example():
# This first one succeeds because there are no existing routes yet, so no comparisons happen.
respx.post("/path", files={"filename1": ANY})
# This one causes a hash error when a comparison to the existing route is attempted
respx.post("/path", files={"filename2": ANY})
Output:
F [100%]
=================================================================================================== FAILURES ===================================================================================================
_________________________________________________________________________________________________ test_example _________________________________________________________________________________________________
def test_example():
# This first one succeeds because there are no existing routes yet, so no comparisons happen.
respx.post("/path", files={"filename1": ANY})
# This one causes a hash error when a comparison to the existing route is attempted
> respx.post("/path", files={"filename2": ANY})
tests/test_example.py:10:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
.venv/lib/python3.12/site-packages/respx/api.py:81: in post
return mock.post(url, name=name, **lookups)
.venv/lib/python3.12/site-packages/respx/router.py:182: in post
return self.request(method="POST", url=url, name=name, **lookups)
.venv/lib/python3.12/site-packages/respx/router.py:164: in request
return self.route(method=method, url=url, name=name, **lookups)
.venv/lib/python3.12/site-packages/respx/router.py:132: in route
return self.add(route, name=name)
.venv/lib/python3.12/site-packages/respx/router.py:145: in add
route = self.routes.add(route, name=name)
.venv/lib/python3.12/site-packages/respx/models.py:480: in add
if route in self._routes:
.venv/lib/python3.12/site-packages/respx/models.py:149: in __eq__
return self.pattern == other.pattern
.venv/lib/python3.12/site-packages/respx/patterns.py:133: in __eq__
return hash(self) == hash(other)
.venv/lib/python3.12/site-packages/respx/patterns.py:130: in __hash__
return hash((self.__class__, self.lookup, self.value))
.venv/lib/python3.12/site-packages/respx/patterns.py:130: in __hash__
return hash((self.__class__, self.lookup, self.value))
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Files contains {'filename1': (<ANY>, <ANY>)}>
def __hash__(self):
> return hash((self.__class__, self.lookup, self._multi_items(self.value)))
E TypeError: unhashable type: '_ANY'
.venv/lib/python3.12/site-packages/respx/patterns.py:306: TypeError
=========================================================================================== short test summary info ============================================================================================
FAILED tests/test_example.py::test_example - TypeError: unhashable type: '_ANY'
1 failed in 0.18s
Note that this problem doesn't appear to strike when using ANY with a params lookup instead of files, so however it's handled there may be applicable here.
Thanks for reporting .. there are tests with ANY, will try and add your specific case and see what breaks
I found the problem and it probably affected headers, params and data patterns as well, which uses the same MultiItemsMixin base class.
@mikenerone, if possible, please try #289 before I merge.
@lundberg I no longer have my original situation where this cropped up, but I constructed this test to confirm the fix (via the pytest fixture):
def test_example(respx_mock):
respx_mock.post("/path", files={"filename1": ANY}).respond(200)
respx_mock.post("/path", files={"filename2": ANY}).respond(201)
assert httpx.post("http://www.blah.com/path", files={"filename2": b""}).status_code == 201
assert httpx.post("http://www.blah.com/path", files={"filename1": b""}).status_code == 200
I can confirm that this test fails in current master with TypeError: unhashable type: '_ANY', but in the branch from #289 (fix/276), the error does not occur and the matches are distinguished properly.
Just bumped into this problem myself, are you still planning on merging the fixing PR? (Thanks for the great library, makes life a lot easier!)