0
# Filters
1
2
Property inclusion and exclusion system for controlling serialization scope in snapshots. Filters allow you to include or exclude specific properties from snapshot serialization, useful for ignoring dynamic fields, private data, or focusing on specific aspects of complex objects.
3
4
## Capabilities
5
6
### Property-Based Filtering
7
8
Filter properties by their names, supporting both inclusion and exclusion patterns.
9
10
```python { .api }
11
def props(*prop_names: str) -> PropertyFilter:
12
"""
13
Create filter that includes only specified property names.
14
15
Parameters:
16
- *included: Property names to include in serialization
17
18
Returns:
19
PropertyFilter: Function that returns True for included properties
20
"""
21
```
22
23
Usage examples:
24
25
```python
26
def test_include_specific_props(snapshot):
27
from syrupy.filters import props
28
29
user_data = {
30
"id": 123,
31
"name": "Alice",
32
"email": "alice@example.com",
33
"password_hash": "secret_hash_value",
34
"created_at": "2023-12-01T10:00:00Z",
35
"last_login": "2023-12-01T15:30:00Z",
36
"internal_id": "internal_abc123"
37
}
38
39
# Only include public fields in snapshot
40
assert user_data == snapshot(include=props("id", "name", "email"))
41
42
def test_exclude_sensitive_props(snapshot):
43
from syrupy.filters import props
44
45
config = {
46
"app_name": "MyApp",
47
"version": "1.0.0",
48
"database_url": "postgresql://localhost/mydb",
49
"secret_key": "super_secret_key_123",
50
"api_token": "token_abc123def456",
51
"debug": True
52
}
53
54
# Exclude sensitive configuration
55
assert config == snapshot(exclude=props("secret_key", "api_token", "database_url"))
56
57
def test_nested_object_filtering(snapshot):
58
from syrupy.filters import props
59
60
response = {
61
"user": {
62
"id": 123,
63
"name": "Alice",
64
"email": "alice@example.com",
65
"private_notes": "Internal notes",
66
"password_hash": "secret"
67
},
68
"session": {
69
"id": "sess_123",
70
"token": "secret_token",
71
"expires": "2023-12-01T16:00:00Z"
72
}
73
}
74
75
# Include only public user fields
76
# Note: This filters at each level independently
77
assert response == snapshot(include=props("user", "id", "name", "email", "session", "expires"))
78
```
79
80
### Path-Based Filtering
81
82
Filter properties using full path strings delimited with dots, allowing precise control over nested structures.
83
84
```python { .api }
85
def paths(*included: str) -> PropertyFilter:
86
"""
87
Create filter using full path strings for precise property targeting.
88
89
Parameters:
90
- *included: Dot-delimited path strings to include
91
92
Returns:
93
PropertyFilter: Function that returns True for included paths
94
"""
95
```
96
97
Usage examples:
98
99
```python
100
def test_precise_path_filtering(snapshot):
101
from syrupy.filters import paths
102
103
complex_data = {
104
"user": {
105
"profile": {
106
"name": "Alice",
107
"email": "alice@example.com",
108
"private_info": "secret"
109
},
110
"settings": {
111
"theme": "dark",
112
"notifications": True,
113
"private_key": "secret_key"
114
}
115
},
116
"metadata": {
117
"version": "1.0",
118
"debug_info": "internal_data"
119
}
120
}
121
122
# Include only specific nested paths
123
assert complex_data == snapshot(include=paths(
124
"user.profile.name",
125
"user.profile.email",
126
"user.settings.theme",
127
"metadata.version"
128
))
129
130
def test_exclude_specific_paths(snapshot):
131
from syrupy.filters import paths
132
133
api_response = {
134
"data": {
135
"users": [
136
{"id": 1, "name": "Alice", "internal_id": "int_123"},
137
{"id": 2, "name": "Bob", "internal_id": "int_456"}
138
]
139
},
140
"meta": {
141
"total_count": 2,
142
"request_id": "req_789",
143
"debug_trace": "trace_data"
144
}
145
}
146
147
# Exclude internal/debug paths
148
assert api_response == snapshot(exclude=paths(
149
"data.users.*.internal_id", # Exclude internal_id from all users
150
"meta.request_id",
151
"meta.debug_trace"
152
))
153
```
154
155
### Nested Path Inclusion
156
157
Advanced filtering that automatically includes parent paths when targeting nested properties.
158
159
```python { .api }
160
def paths_include(*path_parts: Union[Tuple[str, ...], List[str]]) -> PropertyFilter:
161
"""
162
Create include filter with automatic parent path inclusion.
163
164
When including a nested path, automatically includes all parent paths
165
necessary to reach the target property.
166
167
Parameters:
168
- *nested_paths: Dot-delimited paths to include with parents
169
170
Returns:
171
PropertyFilter: Function that includes paths and their parents
172
"""
173
```
174
175
Usage examples:
176
177
```python
178
def test_nested_path_inclusion(snapshot):
179
from syrupy.filters import paths_include
180
181
deep_structure = {
182
"level1": {
183
"level2": {
184
"level3": {
185
"target_field": "important_data",
186
"other_field": "not_needed"
187
},
188
"sibling": "also_not_needed"
189
},
190
"other_branch": "ignore_this"
191
},
192
"root_field": "ignore_this_too"
193
}
194
195
# Include only level1.level2.level3.target_field and necessary parents
196
# This automatically includes: level1, level1.level2, level1.level2.level3
197
assert deep_structure == snapshot(include=paths_include(
198
"level1.level2.level3.target_field"
199
))
200
201
def test_multiple_nested_inclusions(snapshot):
202
from syrupy.filters import paths_include
203
204
config = {
205
"database": {
206
"host": "localhost",
207
"port": 5432,
208
"credentials": {
209
"username": "admin",
210
"password": "secret"
211
}
212
},
213
"cache": {
214
"redis": {
215
"host": "cache-host",
216
"port": 6379
217
}
218
},
219
"logging": {
220
"level": "INFO",
221
"file": "/var/log/app.log"
222
}
223
}
224
225
# Include multiple nested paths - parents automatically included
226
assert config == snapshot(include=paths_include(
227
"database.host",
228
"database.port",
229
"cache.redis.host",
230
"logging.level"
231
))
232
```
233
234
### Combined Filter Usage
235
236
Using filters together with matchers and extensions for comprehensive snapshot control.
237
238
```python { .api }
239
# Filters can be combined with matchers and extensions
240
PropertyFilter = Callable[[PropertyName, PropertyPath], bool]
241
```
242
243
Usage examples:
244
245
```python
246
def test_filter_with_matcher(snapshot):
247
from syrupy.filters import props
248
from syrupy.matchers import path_type
249
250
user_activity = {
251
"user_id": 12345,
252
"username": "alice",
253
"last_login": "2023-12-01T10:30:00Z",
254
"login_count": 42,
255
"internal_tracking_id": "track_abc123",
256
"debug_info": {"trace": "internal_data"}
257
}
258
259
# Exclude internal fields AND replace dynamic values
260
assert user_activity == snapshot(
261
exclude=props("internal_tracking_id", "debug_info"),
262
matcher=path_type({
263
"user_id": (int, "<user_id>"),
264
"last_login": (str, "<timestamp>")
265
})
266
)
267
268
def test_filter_with_extension(snapshot):
269
from syrupy.filters import paths
270
from syrupy.extensions.json import JSONSnapshotExtension
271
272
api_data = {
273
"public_api": {
274
"users": [{"id": 1, "name": "Alice"}],
275
"total": 1
276
},
277
"internal_meta": {
278
"query_time": 0.05,
279
"cache_hit": True,
280
"debug_trace": "internal"
281
}
282
}
283
284
# Include only public API data and save as clean JSON
285
assert api_data == snapshot(
286
include=paths("public_api.users", "public_api.total")
287
).use_extension(JSONSnapshotExtension)
288
```
289
290
### Custom Filter Development
291
292
Create custom filter functions for specialized filtering needs.
293
294
```python { .api }
295
PropertyFilter = Callable[[PropertyName, PropertyPath], bool]
296
297
# PropertyPath structure for custom filters
298
PropertyName = Hashable
299
PropertyValueType = Type[SerializableData]
300
PropertyPathEntry = Tuple[PropertyName, PropertyValueType]
301
PropertyPath = Tuple[PropertyPathEntry, ...]
302
```
303
304
Usage examples:
305
306
```python
307
def test_custom_type_filter(snapshot):
308
def exclude_private_types(prop_name, path):
309
"""Exclude properties containing sensitive types"""
310
# Get the property value type from the path
311
if path and len(path) > 0:
312
prop_type = path[-1][1] # Last entry's type
313
314
# Exclude certain types
315
if prop_type in (type(lambda: None), type): # Functions and type objects
316
return False
317
318
# Exclude properties with "private" or "secret" in name
319
if isinstance(prop_name, str):
320
if "private" in prop_name.lower() or "secret" in prop_name.lower():
321
return False
322
323
return True # Include by default
324
325
test_data = {
326
"public_value": "visible",
327
"private_key": "hidden",
328
"secret_token": "hidden",
329
"normal_field": "visible",
330
"callback_func": lambda x: x, # Function type - excluded
331
"metadata": {"version": "1.0"}
332
}
333
334
assert test_data == snapshot(include=exclude_private_types)
335
336
def test_depth_based_filter(snapshot):
337
def limit_depth(prop_name, path):
338
"""Limit serialization depth to prevent infinite recursion"""
339
max_depth = 3
340
return len(path) <= max_depth
341
342
# Deeply nested structure
343
deep_data = {
344
"level1": {
345
"level2": {
346
"level3": {
347
"level4": "too deep - excluded",
348
"value": "included"
349
},
350
"value": "included"
351
},
352
"value": "included"
353
}
354
}
355
356
assert deep_data == snapshot(include=limit_depth)
357
358
def test_conditional_filter(snapshot):
359
def filter_by_value_type(prop_name, path):
360
"""Custom logic based on property name patterns"""
361
prop_str = str(prop_name)
362
363
# Include all numeric IDs
364
if prop_str.endswith("_id") or prop_str == "id":
365
return True
366
367
# Include all timestamps
368
if "time" in prop_str.lower() or "date" in prop_str.lower():
369
return True
370
371
# Include names and titles
372
if prop_str in ["name", "title", "description"]:
373
return True
374
375
# Exclude everything else
376
return False
377
378
mixed_data = {
379
"user_id": 123,
380
"name": "Alice",
381
"email": "alice@example.com", # Excluded
382
"created_time": "2023-12-01",
383
"password": "secret", # Excluded
384
"description": "User account"
385
}
386
387
assert mixed_data == snapshot(include=filter_by_value_type)
388
```