Automatically mock your HTTP interactions to simplify and speed up testing
HTTP request and response representation with case-insensitive headers, body processing, and URI parsing capabilities. VCR.py provides internal models for capturing and manipulating HTTP interactions.
VCR's representation of an HTTP request with automatic body and header processing.
class Request:
"""
VCR's representation of an HTTP request.
Args:
method (str): HTTP method (GET, POST, etc.)
uri (str): Full request URI
body: Request body (str, bytes, file-like object, or iterable)
headers: Request headers (dict or HeadersDict)
"""
def __init__(self, method: str, uri: str, body, headers): ...Properties providing parsed access to request components.
@property
def headers(self) -> HeadersDict:
"""Case-insensitive dictionary of request headers."""
@property
def body(self):
"""
Request body, returned in appropriate format:
- File-like objects: Returns BytesIO
- Iterables: Returns iterator
- Other types: Returns as-is
"""
@property
def method(self) -> str:
"""HTTP method (GET, POST, PUT, DELETE, etc.)"""
@property
def uri(self) -> str:
"""Complete request URI"""
@property
def scheme(self) -> str:
"""URI scheme (http, https)"""
@property
def host(self) -> str:
"""Request host/domain"""
@property
def port(self) -> int:
"""Request port number"""
@property
def path(self) -> str:
"""URI path component"""
@property
def query(self) -> str:
"""Query string component"""Case-insensitive dictionary implementation for HTTP headers.
class HeadersDict(dict):
"""
Case-insensitive dictionary for HTTP headers.
Allows access to headers regardless of case:
headers['Content-Type'] == headers['content-type']
"""
def __init__(self, data=None): ...from vcr.request import Request, HeadersDict
# Basic request creation
request = Request(
method='GET',
uri='https://api.example.com/users?page=1',
body=None,
headers={'User-Agent': 'MyApp/1.0', 'Accept': 'application/json'}
)
# POST request with body
post_request = Request(
method='POST',
uri='https://api.example.com/users',
body='{"name": "John", "email": "john@example.com"}',
headers={
'Content-Type': 'application/json',
'Authorization': 'Bearer token123'
}
)# URI components
print(request.scheme) # 'https'
print(request.host) # 'api.example.com'
print(request.port) # 443 (default for https)
print(request.path) # '/users'
print(request.query) # 'page=1'
# Headers (case-insensitive access)
print(request.headers['User-Agent']) # 'MyApp/1.0'
print(request.headers['user-agent']) # 'MyApp/1.0' (same value)
print(request.headers.get('Accept')) # 'application/json'import io
# String body
request = Request(
method='POST',
uri='https://api.example.com/data',
body='{"key": "value"}',
headers={}
)
print(request.body) # '{"key": "value"}'
# File-like body
file_obj = io.BytesIO(b'file content')
request = Request(
method='PUT',
uri='https://api.example.com/upload',
body=file_obj,
headers={}
)
# Request automatically reads and stores file content
# request.body returns BytesIO with the content
# Iterable body
request = Request(
method='POST',
uri='https://api.example.com/stream',
body=[b'chunk1', b'chunk2', b'chunk3'],
headers={}
)
# Request converts iterable to list
# request.body returns iterator over the chunksfrom vcr.request import HeadersDict
# Create headers dict
headers = HeadersDict({
'Content-Type': 'application/json',
'Authorization': 'Bearer token123',
'X-Custom-Header': 'custom-value'
})
# Case-insensitive access
print(headers['content-type']) # 'application/json'
print(headers['AUTHORIZATION']) # 'Bearer token123'
print(headers['x-custom-header']) # 'custom-value'
# Standard dict operations work
headers['New-Header'] = 'new-value'
del headers['X-Custom-Header']
# Iteration preserves original case
for key, value in headers.items():
print(f"{key}: {value}")# Modifying request components
request = Request('GET', 'https://api.example.com/data', None, {})
# Update headers (creates new HeadersDict if needed)
request.headers['Authorization'] = 'Bearer new-token'
request.headers['Accept'] = 'application/xml'
# Body modification
request.body = '{"updated": "data"}'
# Note: URI components are read-only properties
# To change URI, create a new Request objectdef sanitize_request(request):
"""Example filter function that modifies requests before recording"""
# Remove sensitive headers
if 'authorization' in request.headers:
request.headers['authorization'] = 'REDACTED'
# Modify query parameters by reconstructing URI
if 'api_key' in request.uri:
# Custom logic to remove api_key from URI
pass
return request
# Use with VCR configuration
my_vcr = vcr.VCR(before_record_request=sanitize_request)from vcr.request import HeadersDict
def normalize_headers(headers_dict):
"""Normalize header values for consistent recording"""
normalized = HeadersDict()
for key, value in headers_dict.items():
if key.lower() == 'user-agent':
# Normalize user agent strings
normalized[key] = 'Normalized-User-Agent/1.0'
elif key.lower() == 'date':
# Remove timestamp headers for deterministic recording
continue
else:
normalized[key] = value
return normalized
# Apply to requests during processing
request.headers = normalize_headers(request.headers)import json
from urllib.parse import parse_qs
def analyze_request_body(request):
"""Analyze and potentially modify request body"""
if request.headers.get('content-type', '').startswith('application/json'):
try:
# Parse and potentially modify JSON body
data = json.loads(request.body)
# Remove sensitive fields
data.pop('password', None)
data.pop('api_secret', None)
request.body = json.dumps(data)
except (json.JSONDecodeError, TypeError):
pass
elif request.headers.get('content-type') == 'application/x-www-form-urlencoded':
# Handle form data
try:
data = parse_qs(request.body)
# Process form fields
if 'password' in data:
data['password'] = ['REDACTED']
request.body = '&'.join(f"{k}={v[0]}" for k, v in data.items())
except (ValueError, TypeError):
pass
return requestInstall with Tessl CLI
npx tessl i tessl/pypi-vcrpy