Automatically mock your HTTP interactions to simplify and speed up testing
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.
Core matcher functions that compare specific aspects of HTTP requests.
def method(r1: Request, r2: Request) -> None:
"""
Match HTTP method (GET, POST, etc.).
Args:
r1, r2: Request objects to compare
Raises:
AssertionError: If methods don't match
"""
def uri(r1: Request, r2: Request) -> None:
"""
Match complete URI.
Args:
r1, r2: Request objects to compare
Raises:
AssertionError: If URIs don't match
"""
def host(r1: Request, r2: Request) -> None:
"""
Match request host/domain.
Args:
r1, r2: Request objects to compare
Raises:
AssertionError: If hosts don't match
"""
def scheme(r1: Request, r2: Request) -> None:
"""
Match URI scheme (http/https).
Args:
r1, r2: Request objects to compare
Raises:
AssertionError: If schemes don't match
"""
def port(r1: Request, r2: Request) -> None:
"""
Match port numbers.
Args:
r1, r2: Request objects to compare
Raises:
AssertionError: If ports don't match
"""
def path(r1: Request, r2: Request) -> None:
"""
Match URI path component.
Args:
r1, r2: Request objects to compare
Raises:
AssertionError: If paths don't match
"""
def query(r1: Request, r2: Request) -> None:
"""
Match query string parameters.
Args:
r1, r2: Request objects to compare
Raises:
AssertionError: If query strings don't match
"""Advanced matcher functions for header and body content comparison.
def headers(r1: Request, r2: Request) -> None:
"""
Match request headers.
Args:
r1, r2: Request objects to compare
Raises:
AssertionError: If headers don't match
"""
def raw_body(r1: Request, r2: Request) -> None:
"""
Match raw request body content.
Args:
r1, r2: Request objects to compare
Raises:
AssertionError: If raw bodies don't match
"""
def body(r1: Request, r2: Request) -> None:
"""
Match processed request body content.
Args:
r1, r2: Request objects to compare
Raises:
AssertionError: If processed bodies don't match
"""Functions for executing and analyzing matching results.
def requests_match(r1: Request, r2: Request, matchers: list) -> bool:
"""
Check if two requests match using specified matchers.
Args:
r1, r2: Request objects to compare
matchers: List of matcher functions to apply
Returns:
bool: True if all matchers pass, False otherwise
"""
def get_matchers_results(r1: Request, r2: Request, matchers: list) -> tuple:
"""
Get detailed results of matcher comparison.
Args:
r1, r2: Request objects to compare
matchers: List of matcher functions to apply
Returns:
tuple: (succeeded_matchers, failed_matchers_with_errors)
"""import vcr
# Default VCR matching (most common)
my_vcr = vcr.VCR(
match_on=['method', 'scheme', 'host', 'port', 'path', 'query']
)
@my_vcr.use_cassette('test.yaml')
def test_default_matching():
# Requests matched on all default criteria
# Headers and body content ignored by default
pass# Include headers in matching criteria
strict_vcr = vcr.VCR(
match_on=['method', 'uri', 'headers']
)
@strict_vcr.use_cassette('strict.yaml')
def test_strict_matching():
# Requests must have identical headers to match
# Useful for APIs that vary responses based on headers
pass# Match based on request body content
body_vcr = vcr.VCR(
match_on=['method', 'uri', 'body']
)
@body_vcr.use_cassette('body_match.yaml')
def test_body_matching():
# POST/PUT requests matched on body content
# Useful for APIs where request body affects response
pass# Match only on essential criteria
loose_vcr = vcr.VCR(
match_on=['method', 'host', 'path']
)
@loose_vcr.use_cassette('loose.yaml')
def test_loose_matching():
# Ignores query parameters, ports, schemes
# Useful when minor URL variations don't affect response
passdef custom_matcher(r1, r2):
"""Custom matcher that ignores timestamp parameters."""
from urllib.parse import urlparse, parse_qs
# Parse query parameters
q1 = parse_qs(urlparse(r1.uri).query)
q2 = parse_qs(urlparse(r2.uri).query)
# Remove timestamp parameters
for params in [q1, q2]:
params.pop('timestamp', None)
params.pop('_t', None)
# Compare remaining parameters
if q1 != q2:
raise AssertionError(f"Query parameters don't match: {q1} != {q2}")
# Register and use custom matcher
my_vcr = vcr.VCR(
match_on=['method', 'host', 'path', 'custom_query']
)
my_vcr.register_matcher('custom_query', custom_matcher)import json
def json_body_matcher(r1, r2):
"""Match JSON bodies ignoring key order."""
try:
json1 = json.loads(r1.body) if r1.body else None
json2 = json.loads(r2.body) if r2.body else None
if json1 != json2:
raise AssertionError(f"JSON bodies don't match: {json1} != {json2}")
except (json.JSONDecodeError, TypeError):
# Fall back to string comparison for non-JSON bodies
if r1.body != r2.body:
raise AssertionError(f"Bodies don't match: {r1.body} != {r2.body}")
my_vcr = vcr.VCR(
match_on=['method', 'uri', 'json_body']
)
my_vcr.register_matcher('json_body', json_body_matcher)def auth_header_matcher(r1, r2):
"""Match only authentication-related headers."""
auth_headers = ['authorization', 'x-api-key', 'x-auth-token']
for header in auth_headers:
h1 = r1.headers.get(header)
h2 = r2.headers.get(header)
if h1 != h2:
raise AssertionError(f"Auth header {header} doesn't match: {h1} != {h2}")
my_vcr = vcr.VCR(
match_on=['method', 'uri', 'auth_headers']
)
my_vcr.register_matcher('auth_headers', auth_header_matcher)base_vcr = vcr.VCR(match_on=['method', 'uri'])
# Override matching for specific test
@base_vcr.use_cassette('test.yaml', match_on=['method', 'host', 'path'])
def test_with_different_matching():
# Uses different matching criteria for this test only
passdef smart_matcher(r1, r2):
"""Apply different matching logic based on request type."""
if r1.method in ['GET', 'HEAD']:
# For read operations, match on URI only
if r1.uri != r2.uri:
raise AssertionError("URIs don't match for read operation")
else:
# For write operations, also match on body
if r1.uri != r2.uri or r1.body != r2.body:
raise AssertionError("URI or body doesn't match for write operation")
my_vcr = vcr.VCR(match_on=['smart_match'])
my_vcr.register_matcher('smart_match', smart_matcher)from vcr.matchers import get_matchers_results
def debug_matching():
"""Helper to debug why requests aren't matching."""
# This would typically be used within VCR internals or custom debugging
recorded_request = Request('GET', 'https://api.example.com/data?v=1', None, {})
incoming_request = Request('GET', 'https://api.example.com/data?v=2', None, {})
matchers = [method, scheme, host, port, path, query]
succeeded, failed = get_matchers_results(recorded_request, incoming_request, matchers)
print(f"Succeeded matchers: {succeeded}")
print(f"Failed matchers: {failed}")
# Output shows which specific matchers failed and whydef example_matcher(r1, r2):
"""
Template for custom matcher functions.
Args:
r1, r2: Request objects to compare
Raises:
AssertionError: If requests don't match (include descriptive message)
"""
if some_comparison(r1, r2):
# Requests match - return normally
return
else:
# Requests don't match - raise AssertionError with details
raise AssertionError(f"Custom match failed: {r1.some_attr} != {r2.some_attr}")def efficient_matcher(r1, r2):
"""Example of performance-conscious matcher."""
# Check quick comparisons first
if r1.method != r2.method:
raise AssertionError("Methods don't match")
# More expensive operations last
if expensive_comparison(r1.body, r2.body):
raise AssertionError("Expensive comparison failed")Install with Tessl CLI
npx tessl i tessl/pypi-vcrpy