0
# Error Handling
1
2
Exception classes for handling VCR-specific errors during recording and playback operations. VCR.py provides specific exception types to help diagnose and handle different error conditions.
3
4
## Capabilities
5
6
### CannotOverwriteExistingCassetteException
7
8
Exception raised when attempting to record new interactions in a cassette that cannot be overwritten based on the current record mode.
9
10
```python { .api }
11
class CannotOverwriteExistingCassetteException(Exception):
12
"""
13
Raised when VCR cannot overwrite an existing cassette.
14
15
This typically occurs in 'once' record mode when a test tries to make
16
HTTP requests that aren't already recorded in the cassette.
17
18
Attributes:
19
cassette: The cassette that couldn't be overwritten
20
failed_request: The request that couldn't be matched/recorded
21
"""
22
23
def __init__(self, cassette, failed_request):
24
"""
25
Initialize exception with cassette and request details.
26
27
Args:
28
cassette: Cassette object that couldn't be overwritten
29
failed_request: Request object that caused the failure
30
"""
31
32
cassette: Cassette
33
failed_request: Request
34
```
35
36
### UnhandledHTTPRequestError
37
38
Exception raised when a cassette doesn't contain a recorded response for the requested HTTP interaction.
39
40
```python { .api }
41
class UnhandledHTTPRequestError(KeyError):
42
"""
43
Raised when a cassette does not contain the request we want.
44
45
This occurs when VCR intercepts an HTTP request but cannot find
46
a matching recorded interaction in the current cassette.
47
"""
48
```
49
50
## Usage Examples
51
52
### Handling Record Mode Violations
53
54
```python
55
import vcr
56
import requests
57
from vcr.errors import CannotOverwriteExistingCassetteException
58
59
@vcr.use_cassette('existing.yaml', record_mode='once')
60
def test_with_error_handling():
61
"""Test that handles cassette overwrite errors."""
62
try:
63
# This request must already exist in the cassette
64
response = requests.get('https://api.example.com/existing-endpoint')
65
assert response.status_code == 200
66
67
# This new request will cause an error in 'once' mode
68
response = requests.get('https://api.example.com/new-endpoint')
69
70
except CannotOverwriteExistingCassetteException as e:
71
print(f"Cannot add new request to cassette: {e.failed_request.uri}")
72
print(f"Cassette path: {e.cassette._path}")
73
print(f"Record mode: {e.cassette.record_mode}")
74
75
# Handle the error - perhaps switch to NEW_EPISODES mode
76
# or update the test to use existing endpoints
77
pass
78
```
79
80
### Handling Missing Interactions
81
82
```python
83
import vcr
84
import requests
85
from vcr.errors import UnhandledHTTPRequestError
86
87
@vcr.use_cassette('partial.yaml', record_mode='none')
88
def test_missing_interaction():
89
"""Test that handles missing recorded interactions."""
90
try:
91
# This request must exist in the cassette
92
response = requests.get('https://api.example.com/recorded-endpoint')
93
assert response.status_code == 200
94
95
# This request is not in the cassette
96
response = requests.get('https://api.example.com/missing-endpoint')
97
98
except UnhandledHTTPRequestError as e:
99
print(f"No recorded interaction for request: {e}")
100
101
# Handle missing interaction
102
# - Add the interaction to the cassette
103
# - Mock the response
104
# - Skip the test
105
# - Use a different cassette
106
pass
107
```
108
109
### Comprehensive Error Handling
110
111
```python
112
import vcr
113
import requests
114
from vcr.errors import CannotOverwriteExistingCassetteException, UnhandledHTTPRequestError
115
116
class APITestCase:
117
"""Test case with comprehensive VCR error handling."""
118
119
def make_api_request(self, url, method='GET', **kwargs):
120
"""Make API request with VCR error handling."""
121
try:
122
response = requests.request(method, url, **kwargs)
123
return response
124
125
except CannotOverwriteExistingCassetteException as e:
126
# Handle cassette overwrite errors
127
self.handle_cassette_overwrite_error(e)
128
129
except UnhandledHTTPRequestError as e:
130
# Handle missing interaction errors
131
self.handle_missing_interaction_error(e)
132
133
except Exception as e:
134
# Handle other potential errors
135
self.handle_general_error(e)
136
137
def handle_cassette_overwrite_error(self, error):
138
"""Handle cassette overwrite errors."""
139
print(f"Cassette overwrite error:")
140
print(f" Request: {error.failed_request.method} {error.failed_request.uri}")
141
print(f" Cassette: {error.cassette._path}")
142
print(f" Record mode: {error.cassette.record_mode}")
143
144
# Suggest solutions
145
if error.cassette.record_mode == 'once':
146
print(" Solution: Change record_mode to 'new_episodes' or add request to cassette")
147
148
# Find similar requests for debugging
149
if hasattr(error.cassette, 'find_requests_with_most_matches'):
150
matches = error.cassette.find_requests_with_most_matches(error.failed_request)
151
if matches:
152
print(f" Similar requests found: {len(matches)}")
153
for i, (request, succeeded, failed) in enumerate(matches[:3]):
154
print(f" {i+1}. {request.method} {request.uri}")
155
print(f" Matched: {succeeded}")
156
print(f" Failed: {[f[0] for f in failed]}")
157
158
def handle_missing_interaction_error(self, error):
159
"""Handle missing interaction errors."""
160
print(f"Missing interaction error: {error}")
161
print(" Possible solutions:")
162
print(" - Record the interaction by changing record_mode")
163
print(" - Add the interaction manually to the cassette")
164
print(" - Use a different cassette that contains the interaction")
165
print(" - Mock the response instead of using VCR")
166
167
def handle_general_error(self, error):
168
"""Handle other VCR-related errors."""
169
print(f"VCR error: {type(error).__name__}: {error}")
170
```
171
172
### Debug Mode Error Handling
173
174
```python
175
import logging
176
import vcr
177
from vcr.errors import CannotOverwriteExistingCassetteException, UnhandledHTTPRequestError
178
179
# Enable VCR debug logging
180
logging.basicConfig(level=logging.DEBUG)
181
vcr_log = logging.getLogger('vcr')
182
vcr_log.setLevel(logging.DEBUG)
183
184
def debug_vcr_errors(test_function):
185
"""Decorator to add debug information to VCR errors."""
186
def wrapper(*args, **kwargs):
187
try:
188
return test_function(*args, **kwargs)
189
except CannotOverwriteExistingCassetteException as e:
190
print("\n=== VCR CASSETTE OVERWRITE ERROR DEBUG ===")
191
print(f"Test function: {test_function.__name__}")
192
print(f"Failed request: {e.failed_request.method} {e.failed_request.uri}")
193
print(f"Request headers: {dict(e.failed_request.headers)}")
194
print(f"Request body: {e.failed_request.body}")
195
print(f"Cassette path: {e.cassette._path}")
196
print(f"Cassette record mode: {e.cassette.record_mode}")
197
print(f"Cassette interactions count: {len(e.cassette.responses)}")
198
raise
199
except UnhandledHTTPRequestError as e:
200
print("\n=== VCR UNHANDLED REQUEST ERROR DEBUG ===")
201
print(f"Test function: {test_function.__name__}")
202
print(f"Error details: {e}")
203
raise
204
return wrapper
205
206
@debug_vcr_errors
207
@vcr.use_cassette('debug.yaml')
208
def test_with_debug_info():
209
"""Test with enhanced error debugging."""
210
response = requests.get('https://api.example.com/data')
211
```
212
213
### Custom Error Recovery
214
215
```python
216
import vcr
217
import requests
218
from vcr.errors import CannotOverwriteExistingCassetteException, UnhandledHTTPRequestError
219
220
class RecoveringVCRTest:
221
"""Test class that attempts to recover from VCR errors."""
222
223
def __init__(self, cassette_path):
224
self.cassette_path = cassette_path
225
self.backup_responses = {}
226
227
def make_request_with_recovery(self, url, method='GET', **kwargs):
228
"""Make request with automatic error recovery."""
229
230
# First attempt: Use strict mode
231
try:
232
with vcr.use_cassette(self.cassette_path, record_mode='once'):
233
return requests.request(method, url, **kwargs)
234
235
except (CannotOverwriteExistingCassetteException, UnhandledHTTPRequestError):
236
print(f"VCR error for {method} {url}, attempting recovery...")
237
238
# Second attempt: Allow new episodes
239
try:
240
with vcr.use_cassette(self.cassette_path, record_mode='new_episodes'):
241
return requests.request(method, url, **kwargs)
242
243
except Exception as e:
244
print(f"VCR recovery failed: {e}")
245
246
# Final attempt: Use backup response or real request
247
return self.get_backup_response(url, method) or requests.request(method, url, **kwargs)
248
249
def get_backup_response(self, url, method):
250
"""Get backup response for URL if available."""
251
key = f"{method}:{url}"
252
return self.backup_responses.get(key)
253
254
def set_backup_response(self, url, method, response):
255
"""Set backup response for URL."""
256
key = f"{method}:{url}"
257
self.backup_responses[key] = response
258
```
259
260
### Error Analysis and Reporting
261
262
```python
263
from collections import defaultdict
264
import vcr
265
from vcr.errors import CannotOverwriteExistingCassetteException, UnhandledHTTPRequestError
266
267
class VCRErrorReporter:
268
"""Collect and analyze VCR errors across test runs."""
269
270
def __init__(self):
271
self.errors = defaultdict(list)
272
273
def record_error(self, error, context=None):
274
"""Record an error for later analysis."""
275
error_type = type(error).__name__
276
error_info = {
277
'error': str(error),
278
'context': context,
279
}
280
281
if isinstance(error, CannotOverwriteExistingCassetteException):
282
error_info.update({
283
'cassette_path': error.cassette._path,
284
'record_mode': error.cassette.record_mode,
285
'failed_request_uri': error.failed_request.uri,
286
'failed_request_method': error.failed_request.method,
287
})
288
elif isinstance(error, UnhandledHTTPRequestError):
289
error_info.update({
290
'missing_request': str(error),
291
})
292
293
self.errors[error_type].append(error_info)
294
295
def generate_report(self):
296
"""Generate error analysis report."""
297
print("=== VCR ERROR ANALYSIS REPORT ===")
298
299
for error_type, error_list in self.errors.items():
300
print(f"\n{error_type}: {len(error_list)} occurrences")
301
302
if error_type == 'CannotOverwriteExistingCassetteException':
303
# Group by cassette path
304
by_cassette = defaultdict(list)
305
for error in error_list:
306
by_cassette[error['cassette_path']].append(error)
307
308
for cassette_path, cassette_errors in by_cassette.items():
309
print(f" Cassette: {cassette_path}")
310
print(f" Errors: {len(cassette_errors)}")
311
for error in cassette_errors[:3]: # Show first 3
312
print(f" {error['failed_request_method']} {error['failed_request_uri']}")
313
314
elif error_type == 'UnhandledHTTPRequestError':
315
# Group by missing request pattern
316
for error in error_list[:3]: # Show first 3
317
print(f" Missing: {error['missing_request']}")
318
319
# Usage
320
reporter = VCRErrorReporter()
321
322
def test_with_error_reporting():
323
try:
324
# Your VCR test code here
325
pass
326
except (CannotOverwriteExistingCassetteException, UnhandledHTTPRequestError) as e:
327
reporter.record_error(e, context={'test': 'test_with_error_reporting'})
328
raise
329
330
# At end of test run
331
reporter.generate_report()
332
```