CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-vcrpy

Automatically mock your HTTP interactions to simplify and speed up testing

Overview
Eval results
Files

request-matching.mddocs/

Request Matching

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.

Capabilities

Basic Matcher Functions

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
    """

Header and Body Matchers

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
    """

Matching Utility Functions

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)
    """

Usage Examples

Default Matching Configuration

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

Strict Matching with Headers

# 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

Body-Based Matching

# 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

Loose Matching

# 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
    pass

Custom Matcher Registration

def 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)

JSON Body 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)

Header Subset 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)

Advanced Matching Scenarios

Per-Test Matching Override

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
    pass

Conditional Matching

def 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)

Debugging Matcher Failures

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 why

Matcher Implementation Guidelines

Creating Custom Matchers

def 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}")

Performance Considerations

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

docs

core-configuration.md

data-filtering.md

error-handling.md

index.md

record-modes.md

request-matching.md

request-response.md

serialization.md

test-integration.md

tile.json