Skip to content

Commit 157dbec

Browse files
committed
feat: add compose_matchers utility for composing 1 or more matchers (#952)
(cherry picked from commit 190a63d)
1 parent 2bd0f54 commit 157dbec

File tree

4 files changed

+67
-1
lines changed

4 files changed

+67
-1
lines changed

README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,28 @@ It should return the replacement value to be serialized or the original unmutate
162162

163163
**NOTE:** Do not mutate the value received as it could cause unintended side effects.
164164

165+
##### Composing Matchers
166+
167+
Multiple matchers can be composed together using `matchers`, e.g.:
168+
169+
```py
170+
from syrupy.matchers import compose_matchers
171+
172+
def test_multiple_matchers(snapshot):
173+
data = {
174+
"number": 1,
175+
"datetime": datetime.datetime.now(),
176+
"float": 1.3
177+
}
178+
179+
assert data == snapshot(
180+
matcher=compose_matchers(
181+
path_type(types=(int, float), replacer=lambda *_: "MATCHER_1"),
182+
path_type(types=(datetime.datetime,), replacer=lambda *_: "MATCHER_2"),
183+
),
184+
)
185+
```
186+
165187
##### Built-In Matchers
166188

167189
Syrupy comes with built-in helpers that can be used to make easy work of using property matchers.

src/syrupy/matchers.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ class PathTypeError(TypeError):
3333
pass
3434

3535

36+
class StrictPathTypeError(PathTypeError):
37+
pass
38+
39+
3640
def path_type(
3741
mapping: Optional[Dict[str, Tuple["PropertyValueType", ...]]] = None,
3842
*,
@@ -63,7 +67,7 @@ def path_type_matcher(
6367
if isinstance(data, type_to_match):
6468
return replacer(data, matches)
6569
if strict:
66-
raise PathTypeError(
70+
raise StrictPathTypeError(
6771
gettext(
6872
"{} at '{}' of type {} does not "
6973
"match any of the expected types: {}"
@@ -107,3 +111,23 @@ def _path_match(path: str, pattern: str, is_regex: bool) -> "MatchResult":
107111
if not is_regex:
108112
pattern = re.escape(pattern)
109113
return re.fullmatch(pattern, path)
114+
115+
116+
def compose_matchers(*matchers: "PropertyMatcher") -> "PropertyMatcher":
117+
"""
118+
Composes 1 or more matchers into a single matcher.
119+
"""
120+
121+
def _matcher(
122+
*, data: "SerializableData", path: "PropertyPath"
123+
) -> Optional["SerializableData"]:
124+
for matcher in matchers:
125+
try:
126+
data = matcher(data=data, path=path)
127+
except StrictPathTypeError:
128+
# ignore strict mode when composing matchers
129+
pass
130+
131+
return data
132+
133+
return _matcher

tests/syrupy/extensions/amber/__snapshots__/test_amber_matchers.ambr

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,13 @@
5454
'specific_number': 5,
5555
})
5656
# ---
57+
# name: test_multiple_matchers
58+
dict({
59+
'datetime': 'MATCHER_2',
60+
'float': 'MATCHER_1',
61+
'number': 'MATCHER_1',
62+
})
63+
# ---
5764
# name: test_raises_unexpected_type
5865
dict({
5966
'date_created': datetime,

tests/syrupy/extensions/amber/test_amber_matchers.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
)
1010
from syrupy.matchers import (
1111
PathTypeError,
12+
compose_matchers,
1213
path_type,
1314
path_value,
1415
)
@@ -128,3 +129,15 @@ def replacer(data, match):
128129
"dir": str(tmp_path),
129130
}
130131
assert actual == snapshot(matcher=my_matcher)
132+
133+
134+
def test_multiple_matchers(snapshot):
135+
data = {"number": 1, "datetime": datetime.datetime.now(), "float": 1.3}
136+
137+
assert data == snapshot(
138+
matcher=compose_matchers(
139+
path_type(types=(list,), replacer=lambda *_: "DO_NOT_MATCH"),
140+
path_type(types=(int, float), replacer=lambda *_: "MATCHER_1"),
141+
path_type(types=(datetime.datetime,), replacer=lambda *_: "MATCHER_2"),
142+
),
143+
)

0 commit comments

Comments
 (0)