Matchers

You can specify how you would like Betamax to match requests you are making with the recorded requests. You have the following options for default (built-in) matchers:

Matcher

Behaviour

body

This matches by checking the equality of the request bodies.

headers

This matches by checking the equality of all of the request headers

host

This matches based on the host of the URI

method

This matches based on the method, e.g., GET, POST, etc.

path

This matches on the path of the URI

query

This matches on the query part of the URI

uri

This matches on the entirety of the URI

Default Matchers

By default, Betamax matches on uri and method.

Specifying Matchers

You can specify the matchers to be used in the entire library by configuring Betamax like so:

import betamax

with betamax.Betamax.configure() as config:
    config.default_cassette_options['match_requests_on'].extend([
        'headers', 'body'
    ])

Instead of configuring global state, though, you can set it per cassette. For example:

import betamax
import requests


session = requests.Session()
recorder = betamax.Betamax(session)
match_on = ['uri', 'method', 'headers', 'body']
with recorder.use_cassette('example', match_requests_on=match_on):
    # ...

Making Your Own Matcher

So long as you are matching requests, you can define your own way of matching. Each request matcher has to inherit from betamax.BaseMatcher and implement match.

class betamax.BaseMatcher

Base class that ensures sub-classes that implement custom matchers can be registered and have the only method that is required.

Usage:

from betamax import Betamax, BaseMatcher

class MyMatcher(BaseMatcher):
    name = 'my'

    def match(self, request, recorded_request):
        # My fancy matching algorithm

Betamax.register_request_matcher(MyMatcher)

The last line is absolutely necessary.

The match method will be given a requests.PreparedRequest object and a dictionary. The dictionary always has the following keys:

  • url

  • method

  • body

  • headers

match(request, recorded_request)

A method that must be implemented by the user.

Parameters
  • request (PreparedRequest) – A requests PreparedRequest object

  • recorded_request (dict) – A dictionary containing the serialized request in the cassette

Returns bool

True if they match else False

on_init()

Method to implement if you wish something to happen in __init__.

The return value is not checked and this is called at the end of __init__. It is meant to provide the matcher author a way to perform things during initialization of the instance that would otherwise require them to override BaseMatcher.__init__.

Some examples of matchers are in the source reproduced here:

# -*- coding: utf-8 -*-
from .base import BaseMatcher


class HeadersMatcher(BaseMatcher):
    # Matches based on the headers of the request
    name = 'headers'

    def match(self, request, recorded_request):
        return dict(request.headers) == self.flatten_headers(recorded_request)

    def flatten_headers(self, request):
        from betamax.util import from_list
        headers = request['headers'].items()
        return dict((k, from_list(v)) for (k, v) in headers)
# -*- coding: utf-8 -*-
from .base import BaseMatcher
from requests.compat import urlparse


class HostMatcher(BaseMatcher):
    # Matches based on the host of the request
    name = 'host'

    def match(self, request, recorded_request):
        request_host = urlparse(request.url).netloc
        recorded_host = urlparse(recorded_request['uri']).netloc
        return request_host == recorded_host
# -*- coding: utf-8 -*-
from .base import BaseMatcher


class MethodMatcher(BaseMatcher):
    # Matches based on the method of the request
    name = 'method'

    def match(self, request, recorded_request):
        return request.method == recorded_request['method']
# -*- coding: utf-8 -*-
from .base import BaseMatcher
from requests.compat import urlparse


class PathMatcher(BaseMatcher):
    # Matches based on the path of the request
    name = 'path'

    def match(self, request, recorded_request):
        request_path = urlparse(request.url).path
        recorded_path = urlparse(recorded_request['uri']).path
        return request_path == recorded_path
# -*- coding: utf-8 -*-
from .base import BaseMatcher
from requests.compat import urlparse


class PathMatcher(BaseMatcher):
    # Matches based on the path of the request
    name = 'path'

    def match(self, request, recorded_request):
        request_path = urlparse(request.url).path
        recorded_path = urlparse(recorded_request['uri']).path
        return request_path == recorded_path
# -*- coding: utf-8 -*-
from .base import BaseMatcher
from .query import QueryMatcher
from requests.compat import urlparse


class URIMatcher(BaseMatcher):
    # Matches based on the uri of the request
    name = 'uri'

    def on_init(self):
        # Get something we can use to match query strings with
        self.query_matcher = QueryMatcher().match

    def match(self, request, recorded_request):
        queries_match = self.query_matcher(request, recorded_request)
        request_url, recorded_url = request.url, recorded_request['uri']
        return self.all_equal(request_url, recorded_url) and queries_match

    def parse(self, uri):
        parsed = urlparse(uri)
        return {
            'scheme': parsed.scheme,
            'netloc': parsed.netloc,
            'path': parsed.path,
            'fragment': parsed.fragment
            }

    def all_equal(self, new_uri, recorded_uri):
        new_parsed = self.parse(new_uri)
        recorded_parsed = self.parse(recorded_uri)
        return (new_parsed == recorded_parsed)

When you have finished writing your own matcher, you can instruct betamax to use it like so:

import betamax

class MyMatcher(betamax.BaseMatcher):
    name = 'my'

    def match(self, request, recorded_request):
        return True

betamax.Betamax.register_request_matcher(MyMatcher)

To use it, you simply use the name you set like you use the name of the default matchers, e.g.:

with Betamax(s).use_cassette('example', match_requests_on=['uri', 'my']):
    # ...

on_init

As you can see in the code for URIMatcher, we use on_init to initialize an attribute on the URIMatcher instance. This method serves to provide the matcher author with a different way of initializing the object outside of the match method. This also means that the author does not have to override the base class’ __init__ method.