Skip to content

Commit 190a63d

Browse files
committed
feat: add compose_matchers utility for composing 1 or more matchers (#952)
1 parent 2a67756 commit 190a63d

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
@@ -163,6 +163,28 @@ It should return the replacement value to be serialized or the original unmutate
163163

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

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

168190
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)