0
# Request Matching
1
2
Functions for determining if recorded requests match incoming requests, supporting multiple matching strategies for different use cases. Request matchers are the core mechanism VCR.py uses to decide when to replay recorded responses.
3
4
## Capabilities
5
6
### Basic Matcher Functions
7
8
Core matcher functions that compare specific aspects of HTTP requests.
9
10
```python { .api }
11
def method(r1: Request, r2: Request) -> None:
12
"""
13
Match HTTP method (GET, POST, etc.).
14
15
Args:
16
r1, r2: Request objects to compare
17
18
Raises:
19
AssertionError: If methods don't match
20
"""
21
22
def uri(r1: Request, r2: Request) -> None:
23
"""
24
Match complete URI.
25
26
Args:
27
r1, r2: Request objects to compare
28
29
Raises:
30
AssertionError: If URIs don't match
31
"""
32
33
def host(r1: Request, r2: Request) -> None:
34
"""
35
Match request host/domain.
36
37
Args:
38
r1, r2: Request objects to compare
39
40
Raises:
41
AssertionError: If hosts don't match
42
"""
43
44
def scheme(r1: Request, r2: Request) -> None:
45
"""
46
Match URI scheme (http/https).
47
48
Args:
49
r1, r2: Request objects to compare
50
51
Raises:
52
AssertionError: If schemes don't match
53
"""
54
55
def port(r1: Request, r2: Request) -> None:
56
"""
57
Match port numbers.
58
59
Args:
60
r1, r2: Request objects to compare
61
62
Raises:
63
AssertionError: If ports don't match
64
"""
65
66
def path(r1: Request, r2: Request) -> None:
67
"""
68
Match URI path component.
69
70
Args:
71
r1, r2: Request objects to compare
72
73
Raises:
74
AssertionError: If paths don't match
75
"""
76
77
def query(r1: Request, r2: Request) -> None:
78
"""
79
Match query string parameters.
80
81
Args:
82
r1, r2: Request objects to compare
83
84
Raises:
85
AssertionError: If query strings don't match
86
"""
87
```
88
89
### Header and Body Matchers
90
91
Advanced matcher functions for header and body content comparison.
92
93
```python { .api }
94
def headers(r1: Request, r2: Request) -> None:
95
"""
96
Match request headers.
97
98
Args:
99
r1, r2: Request objects to compare
100
101
Raises:
102
AssertionError: If headers don't match
103
"""
104
105
def raw_body(r1: Request, r2: Request) -> None:
106
"""
107
Match raw request body content.
108
109
Args:
110
r1, r2: Request objects to compare
111
112
Raises:
113
AssertionError: If raw bodies don't match
114
"""
115
116
def body(r1: Request, r2: Request) -> None:
117
"""
118
Match processed request body content.
119
120
Args:
121
r1, r2: Request objects to compare
122
123
Raises:
124
AssertionError: If processed bodies don't match
125
"""
126
```
127
128
### Matching Utility Functions
129
130
Functions for executing and analyzing matching results.
131
132
```python { .api }
133
def requests_match(r1: Request, r2: Request, matchers: list) -> bool:
134
"""
135
Check if two requests match using specified matchers.
136
137
Args:
138
r1, r2: Request objects to compare
139
matchers: List of matcher functions to apply
140
141
Returns:
142
bool: True if all matchers pass, False otherwise
143
"""
144
145
def get_matchers_results(r1: Request, r2: Request, matchers: list) -> tuple:
146
"""
147
Get detailed results of matcher comparison.
148
149
Args:
150
r1, r2: Request objects to compare
151
matchers: List of matcher functions to apply
152
153
Returns:
154
tuple: (succeeded_matchers, failed_matchers_with_errors)
155
"""
156
```
157
158
## Usage Examples
159
160
### Default Matching Configuration
161
162
```python
163
import vcr
164
165
# Default VCR matching (most common)
166
my_vcr = vcr.VCR(
167
match_on=['method', 'scheme', 'host', 'port', 'path', 'query']
168
)
169
170
@my_vcr.use_cassette('test.yaml')
171
def test_default_matching():
172
# Requests matched on all default criteria
173
# Headers and body content ignored by default
174
pass
175
```
176
177
### Strict Matching with Headers
178
179
```python
180
# Include headers in matching criteria
181
strict_vcr = vcr.VCR(
182
match_on=['method', 'uri', 'headers']
183
)
184
185
@strict_vcr.use_cassette('strict.yaml')
186
def test_strict_matching():
187
# Requests must have identical headers to match
188
# Useful for APIs that vary responses based on headers
189
pass
190
```
191
192
### Body-Based Matching
193
194
```python
195
# Match based on request body content
196
body_vcr = vcr.VCR(
197
match_on=['method', 'uri', 'body']
198
)
199
200
@body_vcr.use_cassette('body_match.yaml')
201
def test_body_matching():
202
# POST/PUT requests matched on body content
203
# Useful for APIs where request body affects response
204
pass
205
```
206
207
### Loose Matching
208
209
```python
210
# Match only on essential criteria
211
loose_vcr = vcr.VCR(
212
match_on=['method', 'host', 'path']
213
)
214
215
@loose_vcr.use_cassette('loose.yaml')
216
def test_loose_matching():
217
# Ignores query parameters, ports, schemes
218
# Useful when minor URL variations don't affect response
219
pass
220
```
221
222
### Custom Matcher Registration
223
224
```python
225
def custom_matcher(r1, r2):
226
"""Custom matcher that ignores timestamp parameters."""
227
from urllib.parse import urlparse, parse_qs
228
229
# Parse query parameters
230
q1 = parse_qs(urlparse(r1.uri).query)
231
q2 = parse_qs(urlparse(r2.uri).query)
232
233
# Remove timestamp parameters
234
for params in [q1, q2]:
235
params.pop('timestamp', None)
236
params.pop('_t', None)
237
238
# Compare remaining parameters
239
if q1 != q2:
240
raise AssertionError(f"Query parameters don't match: {q1} != {q2}")
241
242
# Register and use custom matcher
243
my_vcr = vcr.VCR(
244
match_on=['method', 'host', 'path', 'custom_query']
245
)
246
my_vcr.register_matcher('custom_query', custom_matcher)
247
```
248
249
### JSON Body Matcher
250
251
```python
252
import json
253
254
def json_body_matcher(r1, r2):
255
"""Match JSON bodies ignoring key order."""
256
try:
257
json1 = json.loads(r1.body) if r1.body else None
258
json2 = json.loads(r2.body) if r2.body else None
259
260
if json1 != json2:
261
raise AssertionError(f"JSON bodies don't match: {json1} != {json2}")
262
except (json.JSONDecodeError, TypeError):
263
# Fall back to string comparison for non-JSON bodies
264
if r1.body != r2.body:
265
raise AssertionError(f"Bodies don't match: {r1.body} != {r2.body}")
266
267
my_vcr = vcr.VCR(
268
match_on=['method', 'uri', 'json_body']
269
)
270
my_vcr.register_matcher('json_body', json_body_matcher)
271
```
272
273
### Header Subset Matcher
274
275
```python
276
def auth_header_matcher(r1, r2):
277
"""Match only authentication-related headers."""
278
auth_headers = ['authorization', 'x-api-key', 'x-auth-token']
279
280
for header in auth_headers:
281
h1 = r1.headers.get(header)
282
h2 = r2.headers.get(header)
283
if h1 != h2:
284
raise AssertionError(f"Auth header {header} doesn't match: {h1} != {h2}")
285
286
my_vcr = vcr.VCR(
287
match_on=['method', 'uri', 'auth_headers']
288
)
289
my_vcr.register_matcher('auth_headers', auth_header_matcher)
290
```
291
292
## Advanced Matching Scenarios
293
294
### Per-Test Matching Override
295
296
```python
297
base_vcr = vcr.VCR(match_on=['method', 'uri'])
298
299
# Override matching for specific test
300
@base_vcr.use_cassette('test.yaml', match_on=['method', 'host', 'path'])
301
def test_with_different_matching():
302
# Uses different matching criteria for this test only
303
pass
304
```
305
306
### Conditional Matching
307
308
```python
309
def smart_matcher(r1, r2):
310
"""Apply different matching logic based on request type."""
311
if r1.method in ['GET', 'HEAD']:
312
# For read operations, match on URI only
313
if r1.uri != r2.uri:
314
raise AssertionError("URIs don't match for read operation")
315
else:
316
# For write operations, also match on body
317
if r1.uri != r2.uri or r1.body != r2.body:
318
raise AssertionError("URI or body doesn't match for write operation")
319
320
my_vcr = vcr.VCR(match_on=['smart_match'])
321
my_vcr.register_matcher('smart_match', smart_matcher)
322
```
323
324
### Debugging Matcher Failures
325
326
```python
327
from vcr.matchers import get_matchers_results
328
329
def debug_matching():
330
"""Helper to debug why requests aren't matching."""
331
# This would typically be used within VCR internals or custom debugging
332
333
recorded_request = Request('GET', 'https://api.example.com/data?v=1', None, {})
334
incoming_request = Request('GET', 'https://api.example.com/data?v=2', None, {})
335
336
matchers = [method, scheme, host, port, path, query]
337
succeeded, failed = get_matchers_results(recorded_request, incoming_request, matchers)
338
339
print(f"Succeeded matchers: {succeeded}")
340
print(f"Failed matchers: {failed}")
341
# Output shows which specific matchers failed and why
342
```
343
344
## Matcher Implementation Guidelines
345
346
### Creating Custom Matchers
347
348
```python
349
def example_matcher(r1, r2):
350
"""
351
Template for custom matcher functions.
352
353
Args:
354
r1, r2: Request objects to compare
355
356
Raises:
357
AssertionError: If requests don't match (include descriptive message)
358
"""
359
if some_comparison(r1, r2):
360
# Requests match - return normally
361
return
362
else:
363
# Requests don't match - raise AssertionError with details
364
raise AssertionError(f"Custom match failed: {r1.some_attr} != {r2.some_attr}")
365
```
366
367
### Performance Considerations
368
369
```python
370
def efficient_matcher(r1, r2):
371
"""Example of performance-conscious matcher."""
372
# Check quick comparisons first
373
if r1.method != r2.method:
374
raise AssertionError("Methods don't match")
375
376
# More expensive operations last
377
if expensive_comparison(r1.body, r2.body):
378
raise AssertionError("Expensive comparison failed")
379
```