Skip to content

Commit

Permalink
feat: support regex path type matching (#532)
Browse files Browse the repository at this point in the history
  • Loading branch information
Noah authored Aug 2, 2021
1 parent 29dcc0f commit 0ff4acf
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 5 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ It should return the replacement value to be serialized or the original unmutate

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

###### `path_type(mapping=None, *, types=(), strict=True)`
###### `path_type(mapping=None, *, types=(), strict=True, regex=False)`

Easy way to build a matcher that uses the path and value type to replace serialized data.
When strict, this will raise a `ValueError` if the types specified are not matched.
Expand All @@ -146,6 +146,7 @@ When strict, this will raise a `ValueError` if the types specified are not match
| `mapping` | Dict of path string to tuples of class types, including primitives e.g. (MyClass, UUID, datetime, int, str) |
| `types` | Tuple of class types used if none of the path strings from the mapping are matched |
| `strict` | If a path is matched but the value at the path does not match one of the class types in the tuple then a `PathTypeError` is raised |
| `regex` | If true, the `mapping` key is treated as a regular expression when matching paths |

```py
from syrupy.matchers import path_type
Expand Down
15 changes: 11 additions & 4 deletions src/syrupy/matchers.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import re
from gettext import gettext
from typing import (
TYPE_CHECKING,
Expand Down Expand Up @@ -29,29 +30,35 @@ def path_type(
*,
types: Tuple["PropertyValueType", ...] = (),
strict: bool = True,
regex: bool = False,
) -> "PropertyMatcher":
"""
Factory to create a matcher using path and type mapping
"""
if not mapping and not types:
raise PathTypeError(gettext("Both mapping and types argument cannot be empty"))

def _path_match(path: str, pattern: str) -> bool:
if regex:
return re.fullmatch(pattern, path) is not None
return path == pattern

def path_type_matcher(
*, data: "SerializableData", path: "PropertyPath"
) -> Optional["SerializableData"]:
path_str = ".".join(str(p) for p, _ in path)
if mapping:
for path_to_match in mapping:
if path_to_match == path_str:
for type_to_match in mapping[path_to_match]:
for pattern in mapping:
if _path_match(path_str, pattern):
for type_to_match in mapping[pattern]:
if isinstance(data, type_to_match):
return Repr(DataSerializer.object_type(data))
if strict:
raise PathTypeError(
gettext(
"{} at '{}' of type {} does not "
"match any of the expected types: {}"
).format(data, path_str, data.__class__, types)
).format(data, path_str, data.__class__, mapping[pattern])
)
for type_to_match in types:
if isinstance(data, type_to_match):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,25 @@
],
}
---
# name: test_matches_regex_in_regex_mode
<class 'dict'> {
'any_number': <class 'int'>,
'any_number_adjacent': 'hi',
'data': <class 'dict'> {
'list': <class 'list'> [
<class 'dict'> {
'date_created': <class 'datetime'>,
'k': '1',
},
<class 'dict'> {
'date_created': <class 'datetime'>,
'k': '2',
},
],
},
'specific_number': 5,
}
---
# name: test_raises_unexpected_type
<class 'dict'> {
'date_created': <class 'datetime'>,
Expand Down
22 changes: 22 additions & 0 deletions tests/syrupy/extensions/amber/test_amber_matchers.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,25 @@ def matcher(data, path):
},
"c": ["Replace this one", "Do not replace this one"],
} == snapshot


def test_matches_regex_in_regex_mode(snapshot):
my_matcher = path_type(
{
r"data\.list\..*\.date_created": (datetime.datetime,),
r"any_number": (int,),
},
regex=True,
)
actual = {
"data": {
"list": [
{"k": "1", "date_created": datetime.datetime.now()},
{"k": "2", "date_created": datetime.datetime.now()},
],
},
"any_number": 3,
"any_number_adjacent": "hi",
"specific_number": 5,
}
assert actual == snapshot(matcher=my_matcher)

0 comments on commit 0ff4acf

Please sign in to comment.