0
# Serialization
1
2
Cassette serialization and deserialization support for YAML and JSON formats with extensible serializer registration. VCR.py provides flexible serialization options for storing recorded HTTP interactions.
3
4
## Capabilities
5
6
### YAML Serializer
7
8
Default serializer using YAML format for human-readable cassette files.
9
10
```python { .api }
11
# From vcr.serializers.yamlserializer module
12
13
def deserialize(cassette_string: str) -> dict:
14
"""
15
Deserialize YAML cassette data into Python dictionary.
16
17
Args:
18
cassette_string: YAML-formatted cassette data as string
19
20
Returns:
21
dict: Parsed cassette data with interactions and metadata
22
23
Raises:
24
yaml.YAMLError: If YAML parsing fails
25
"""
26
27
def serialize(cassette_dict: dict) -> str:
28
"""
29
Serialize cassette data dictionary to YAML format.
30
31
Args:
32
cassette_dict: Cassette data as Python dictionary
33
34
Returns:
35
str: YAML-formatted cassette data
36
37
Raises:
38
yaml.YAMLError: If YAML serialization fails
39
"""
40
```
41
42
### JSON Serializer
43
44
Alternative serializer using JSON format for compact cassette files.
45
46
```python { .api }
47
# From vcr.serializers.jsonserializer module
48
49
def deserialize(cassette_string: str) -> dict:
50
"""
51
Deserialize JSON cassette data into Python dictionary.
52
53
Args:
54
cassette_string: JSON-formatted cassette data as string
55
56
Returns:
57
dict: Parsed cassette data with interactions and metadata
58
59
Raises:
60
json.JSONDecodeError: If JSON parsing fails
61
"""
62
63
def serialize(cassette_dict: dict) -> str:
64
"""
65
Serialize cassette data dictionary to JSON format.
66
67
Args:
68
cassette_dict: Cassette data as Python dictionary
69
70
Returns:
71
str: JSON-formatted cassette data
72
73
Raises:
74
TypeError: If data contains non-JSON-serializable objects
75
"""
76
```
77
78
## Usage Examples
79
80
### Choosing Serialization Format
81
82
```python
83
import vcr
84
85
# Default YAML serialization (human-readable)
86
yaml_vcr = vcr.VCR(serializer='yaml')
87
88
@yaml_vcr.use_cassette('interactions.yaml')
89
def test_with_yaml():
90
response = requests.get('https://api.example.com/data')
91
# Creates interactions.yaml file
92
93
# JSON serialization (more compact)
94
json_vcr = vcr.VCR(serializer='json')
95
96
@json_vcr.use_cassette('interactions.json')
97
def test_with_json():
98
response = requests.get('https://api.example.com/data')
99
# Creates interactions.json file
100
```
101
102
### Per-Test Serializer Override
103
104
```python
105
base_vcr = vcr.VCR(serializer='yaml') # Default to YAML
106
107
# Override for specific test
108
@base_vcr.use_cassette('special.json', serializer='json')
109
def test_with_json_override():
110
response = requests.get('https://api.example.com/data')
111
# Uses JSON despite base VCR using YAML
112
```
113
114
### Custom Serializer Implementation
115
116
```python
117
import json
118
import gzip
119
import vcr
120
121
class CompressedJSONSerializer:
122
"""Custom serializer that compresses JSON data."""
123
124
@staticmethod
125
def serialize(cassette_dict):
126
"""Serialize and compress cassette data."""
127
json_data = json.dumps(cassette_dict, indent=2)
128
compressed_data = gzip.compress(json_data.encode('utf-8'))
129
return compressed_data
130
131
@staticmethod
132
def deserialize(cassette_string):
133
"""Decompress and deserialize cassette data."""
134
if isinstance(cassette_string, bytes):
135
decompressed_data = gzip.decompress(cassette_string)
136
json_data = decompressed_data.decode('utf-8')
137
else:
138
json_data = cassette_string
139
return json.loads(json_data)
140
141
# Register custom serializer
142
my_vcr = vcr.VCR()
143
my_vcr.register_serializer('compressed_json', CompressedJSONSerializer)
144
145
@my_vcr.use_cassette('compressed.cjson', serializer='compressed_json')
146
def test_with_compressed_json():
147
response = requests.get('https://api.example.com/data')
148
```
149
150
### Pretty-Printed JSON Serializer
151
152
```python
153
import json
154
155
class PrettyJSONSerializer:
156
"""JSON serializer with human-readable formatting."""
157
158
@staticmethod
159
def serialize(cassette_dict):
160
return json.dumps(
161
cassette_dict,
162
indent=2,
163
sort_keys=True,
164
separators=(',', ': ')
165
)
166
167
@staticmethod
168
def deserialize(cassette_string):
169
return json.loads(cassette_string)
170
171
my_vcr = vcr.VCR()
172
my_vcr.register_serializer('pretty_json', PrettyJSONSerializer)
173
```
174
175
### Binary-Safe Serializer
176
177
```python
178
import json
179
import base64
180
181
class BinarySafeJSONSerializer:
182
"""JSON serializer that handles binary data in responses."""
183
184
@staticmethod
185
def serialize(cassette_dict):
186
"""Serialize with binary data encoded as base64."""
187
# Deep copy to avoid modifying original
188
import copy
189
safe_dict = copy.deepcopy(cassette_dict)
190
191
# Encode binary response bodies
192
for interaction in safe_dict.get('interactions', []):
193
response = interaction.get('response', {})
194
body = response.get('body', {})
195
196
if isinstance(body.get('string'), bytes):
197
# Encode binary data as base64
198
body['string'] = base64.b64encode(body['string']).decode('ascii')
199
body['encoding'] = 'base64'
200
201
return json.dumps(safe_dict, indent=2)
202
203
@staticmethod
204
def deserialize(cassette_string):
205
"""Deserialize with base64 decoding of binary data."""
206
cassette_dict = json.loads(cassette_string)
207
208
# Decode binary response bodies
209
for interaction in cassette_dict.get('interactions', []):
210
response = interaction.get('response', {})
211
body = response.get('body', {})
212
213
if body.get('encoding') == 'base64':
214
# Decode base64 data back to bytes
215
body['string'] = base64.b64decode(body['string'].encode('ascii'))
216
del body['encoding']
217
218
return cassette_dict
219
220
my_vcr = vcr.VCR()
221
my_vcr.register_serializer('binary_safe_json', BinarySafeJSONSerializer)
222
```
223
224
## Cassette File Format
225
226
### YAML Cassette Structure
227
228
```yaml
229
interactions:
230
- request:
231
body: null
232
headers:
233
Accept:
234
- '*/*'
235
User-Agent:
236
- python-requests/2.28.1
237
method: GET
238
uri: https://api.example.com/data
239
response:
240
body:
241
string: '{"message": "Hello, World!"}'
242
headers:
243
Content-Type:
244
- application/json
245
Content-Length:
246
- '26'
247
status:
248
code: 200
249
message: OK
250
version: 1
251
```
252
253
### JSON Cassette Structure
254
255
```json
256
{
257
"interactions": [
258
{
259
"request": {
260
"body": null,
261
"headers": {
262
"Accept": ["*/*"],
263
"User-Agent": ["python-requests/2.28.1"]
264
},
265
"method": "GET",
266
"uri": "https://api.example.com/data"
267
},
268
"response": {
269
"body": {
270
"string": "{\"message\": \"Hello, World!\"}"
271
},
272
"headers": {
273
"Content-Type": ["application/json"],
274
"Content-Length": ["26"]
275
},
276
"status": {
277
"code": 200,
278
"message": "OK"
279
}
280
}
281
}
282
],
283
"version": 1
284
}
285
```
286
287
## Advanced Serialization Patterns
288
289
### Environment-Based Serializer Selection
290
291
```python
292
import os
293
import vcr
294
295
def get_serializer():
296
"""Choose serializer based on environment."""
297
serializer = os.getenv('VCR_SERIALIZER', 'yaml')
298
return serializer
299
300
my_vcr = vcr.VCR(serializer=get_serializer())
301
302
# Usage: VCR_SERIALIZER=json python test.py
303
```
304
305
### Conditional Serializer Features
306
307
```python
308
import json
309
import os
310
311
class SmartJSONSerializer:
312
"""JSON serializer with environment-based features."""
313
314
@staticmethod
315
def serialize(cassette_dict):
316
# Compact format in production, pretty format in development
317
if os.getenv('ENV') == 'production':
318
return json.dumps(cassette_dict, separators=(',', ':'))
319
else:
320
return json.dumps(cassette_dict, indent=2, sort_keys=True)
321
322
@staticmethod
323
def deserialize(cassette_string):
324
return json.loads(cassette_string)
325
```
326
327
### Versioned Serializer
328
329
```python
330
import json
331
332
class VersionedJSONSerializer:
333
"""JSON serializer that includes format version information."""
334
335
CURRENT_VERSION = 2
336
337
@staticmethod
338
def serialize(cassette_dict):
339
# Add version information
340
versioned_dict = {
341
'format_version': VersionedJSONSerializer.CURRENT_VERSION,
342
'data': cassette_dict
343
}
344
return json.dumps(versioned_dict, indent=2)
345
346
@staticmethod
347
def deserialize(cassette_string):
348
versioned_dict = json.loads(cassette_string)
349
350
# Handle different format versions
351
format_version = versioned_dict.get('format_version', 1)
352
353
if format_version == 1:
354
# Legacy format - data is at root level
355
return versioned_dict
356
elif format_version == 2:
357
# Current format - data is nested
358
return versioned_dict['data']
359
else:
360
raise ValueError(f"Unsupported cassette format version: {format_version}")
361
```
362
363
### Encrypted Serializer
364
365
```python
366
import json
367
import base64
368
from cryptography.fernet import Fernet
369
370
class EncryptedJSONSerializer:
371
"""JSON serializer with encryption for sensitive data."""
372
373
def __init__(self, encryption_key=None):
374
if encryption_key is None:
375
encryption_key = Fernet.generate_key()
376
self.cipher = Fernet(encryption_key)
377
378
def serialize(self, cassette_dict):
379
"""Encrypt and serialize cassette data."""
380
json_data = json.dumps(cassette_dict)
381
encrypted_data = self.cipher.encrypt(json_data.encode())
382
return base64.b64encode(encrypted_data).decode()
383
384
def deserialize(self, cassette_string):
385
"""Decrypt and deserialize cassette data."""
386
encrypted_data = base64.b64decode(cassette_string.encode())
387
decrypted_data = self.cipher.decrypt(encrypted_data)
388
return json.loads(decrypted_data.decode())
389
390
# Usage with encryption key from environment
391
import os
392
key = os.getenv('VCR_ENCRYPTION_KEY')
393
if key:
394
encrypted_serializer = EncryptedJSONSerializer(key.encode())
395
my_vcr = vcr.VCR()
396
my_vcr.register_serializer('encrypted', encrypted_serializer)
397
```
398
399
### Streaming Serializer for Large Cassettes
400
401
```python
402
import json
403
import ijson # For streaming JSON parsing
404
405
class StreamingJSONSerializer:
406
"""Serializer optimized for large cassette files."""
407
408
@staticmethod
409
def serialize(cassette_dict):
410
"""Standard JSON serialization."""
411
return json.dumps(cassette_dict)
412
413
@staticmethod
414
def deserialize(cassette_string):
415
"""Stream parsing for large JSON files."""
416
if len(cassette_string) > 10 * 1024 * 1024: # > 10MB
417
# Use streaming parser for large files
418
import io
419
stream = io.StringIO(cassette_string)
420
return ijson.parse(stream)
421
else:
422
# Use standard parser for smaller files
423
return json.loads(cassette_string)
424
```