or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

core-configuration.mddata-filtering.mderror-handling.mdindex.mdrecord-modes.mdrequest-matching.mdrequest-response.mdserialization.mdtest-integration.md

request-matching.mddocs/

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

```