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