Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

### Fixed

- Fix regression causing `params` pattern to stop working under some conditions,
by doing a strict detection of `ANY` in multi items patterns (#313)
- Fix regression causing `params` pattern to stop working under some conditions, by
doing a strict detection of `ANY` in multi items patterns (#313)

### CI

Expand Down
24 changes: 17 additions & 7 deletions respx/patterns.py
Original file line number Diff line number Diff line change
Expand Up @@ -535,10 +535,7 @@ def _contains(self, value: Union[bytes, str]) -> Match:
class JSON(ContentMixin, PathPattern):
lookups = (Lookup.EQUAL,)
key = "json"
value: str

def clean(self, value: Union[str, List, Dict]) -> str:
return self.hash(value)
value: Union[str, List, Dict, Any]

def parse(self, request: httpx.Request) -> str:
content = super().parse(request)
Expand All @@ -557,10 +554,23 @@ def parse(self, request: httpx.Request) -> str:
else:
value = json

return self.hash(value)
return value

def hash(self, value: Union[str, List, Dict]) -> str:
return jsonlib.dumps(value, sort_keys=True)
def __hash__(self):
return hash(
(
self.__class__,
self.lookup,
jsonlib.dumps(self.value, sort_keys=True, default=self._encode_any),
)
)

def _encode_any(self, o: Any) -> Any:
if o is ANY:
return str(ANY)
raise TypeError( # pragma: no cover
f"Object of type {type(o)} is not JSON serializable"
)


class Data(MultiItemsMixin, Pattern):
Expand Down
23 changes: 23 additions & 0 deletions tests/test_patterns.py
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,9 @@ def test_files_pattern(lookup, files, request_files, expected):
False,
),
(Lookup.EQUAL, "json-string", "json-string", True),
(Lookup.EQUAL, {"foo": ANY}, {"foo": "bar"}, True),
(Lookup.EQUAL, {"foo": ANY}, {"ham": "spam"}, False),
(Lookup.EQUAL, ANY, "any-value", True),
],
)
def test_json_pattern(lookup, value, json, expected):
Expand All @@ -585,6 +588,11 @@ def test_json_pattern(lookup, value, json, expected):
({"pk": 123}, "pk", 123, True),
({"foo": {"bar": "baz"}}, "foo__ham", "spam", False),
([{"name": "lundberg"}], "1__name", "lundberg", False),
([{"name": "lundberg"}], "0__name", ANY, True),
([{"name": "lundberg"}], "1__name", ANY, False),
({"ham": [{"spam": "spam"}, {"egg": "yolk"}]}, "ham__1", ANY, True),
({"ham": [{"spam": "spam"}, {"egg": "yolk"}]}, "ham__1__egg", ANY, True),
({"ham": [{"spam": "spam"}, {"egg": "yolk"}]}, "ham__1__foo", ANY, False),
],
)
def test_json_pattern_path(json, path, value, expected):
Expand All @@ -594,6 +602,21 @@ def test_json_pattern_path(json, path, value, expected):
assert bool(match) is expected


@pytest.mark.parametrize(
("value", "other", "expected"),
[
({"foo": "bar"}, {"foo": "bar"}, True),
({"foo": "bar", "ham": "spam"}, {"ham": "spam", "foo": "bar"}, True),
(["foobar", ANY], ["foobar", ANY], True),
(["foobar", "hamspam"], ["foobar", ANY], False),
(ANY, ANY, True),
("foobar", ANY, False),
],
)
def test_json_pattern_hash(value, other, expected):
assert (JSON(value) == JSON(other)) is expected


def test_invalid_pattern():
with pytest.raises(KeyError, match="is not a valid Pattern"):
M(foo="baz")
Expand Down
Loading