Long Term Usage Patterns

Now that we’ve covered the basics in Getting Started, let’s look at some patterns and problems we might encounter when using Betamax over a period of months instead of minutes.

Adding New Requests to a Cassette

Let’s reuse an example. Specifically let’s reuse our examples/more_complicated_cassettes.py example.

import betamax
from betamax_serializers import pretty_json
import requests

CASSETTE_LIBRARY_DIR = 'examples/cassettes/'


def main():
    session = requests.Session()
    betamax.Betamax.register_serializer(pretty_json.PrettyJSONSerializer)
    recorder = betamax.Betamax(
        session, cassette_library_dir=CASSETTE_LIBRARY_DIR
    )

    with recorder.use_cassette('more-complicated-cassettes',
                               serialize_with='prettyjson'):
        session.get('https://httpbin.org/get')
        session.post('https://httpbin.org/post',
                     params={'id': '20'},
                     json={'some-attribute': 'some-value'})
        session.get('https://httpbin.org/get', params={'id': '20'})


if __name__ == '__main__':
    main()

Let’s add a new POST request in there:

session.post('https://httpbin.org/post',
             params={'id': '20'},
             json={'some-other-attribute': 'some-other-value'})

If we run this cassette now, we should expect to see that there was an exception because Betamax couldn’t find a matching request for it. We expect this because the post requests have two completely different bodies, right? Right. The problem you’ll find is that by default Betamax only matches on the URI and the Method. So Betamax will find a matching request/response pair for ("POST", "https://httpbin.org/post?id=20") and reuse it. So now we need to update how we use Betamax so it will match using the body as well:

import betamax
from betamax_serializers import pretty_json
import requests

CASSETTE_LIBRARY_DIR = 'examples/cassettes/'


def main():
    session = requests.Session()
    betamax.Betamax.register_serializer(pretty_json.PrettyJSONSerializer)
    recorder = betamax.Betamax(
        session, cassette_library_dir=CASSETTE_LIBRARY_DIR
    )
    matchers = ['method', 'uri', 'body']

    with recorder.use_cassette('more-complicated-cassettes',
                               serialize_with='prettyjson',
                               match_requests_on=matchers):
        session.get('https://httpbin.org/get')
        session.post('https://httpbin.org/post',
                     params={'id': '20'},
                     json={'some-attribute': 'some-value'})
        session.get('https://httpbin.org/get', params={'id': '20'})
        session.post('https://httpbin.org/post',
                     params={'id': '20'},
                     json={'some-other-attribute': 'some-other-value'})


if __name__ == '__main__':
    main()

Now when we run that we should see something like this:

Traceback (most recent call last):
  File "examples/more_complicated_cassettes_2.py", line 30, in <module>
    main()
  File "examples/more_complicated_cassettes_2.py", line 26, in main
    json={'some-other-attribute': 'some-other-value'})
  File ".../lib/python2.7/site-packages/requests/sessions.py", line 508, in post
    return self.request('POST', url, data=data, json=json, **kwargs)
  File ".../lib/python2.7/site-packages/requests/sessions.py", line 465, in request
    resp = self.send(prep, **send_kwargs)
  File ".../lib/python2.7/site-packages/requests/sessions.py", line 573, in send
    r = adapter.send(request, **kwargs)
  File ".../lib/python2.7/site-packages/betamax/adapter.py", line 91, in send
    self.cassette))
betamax.exceptions.BetamaxError: A request was made that could not be handled.

A request was made to https://httpbin.org/post?id=20 that could not be found in more-complicated-cassettes.

The settings on the cassette are:

    - record_mode: once
    - match_options ['method', 'uri', 'body'].

This is what we do expect to see. So, how do we fix it?

We have a few options to fix it.

Option 1: Re-recording the Cassette

One of the easiest ways to fix this situation is to simply remove the cassette that was recorded and run the script again. This will recreate the cassette and subsequent runs will work just fine.

To be clear, we’re advocating for this option that the user do:

$ rm examples/cassettes/{{ cassette-name }}

This is the favorable option if you don’t foresee yourself needing to add new interactions often.

Option 2: Changing the Record Mode

A different way would be to update the recording mode used by Betamax. We would update the line in our file that currently reads:

with recorder.use_cassette('more-complicated-cassettes',
                           serialize_with='prettyjson',
                           match_requests_on=matchers):

to add one more parameter to the call to use_cassette(). We want to use the record parameter to tell Betamax to use either the new_episodes or all modes. Which you choose depends on your use case.

new_episodes will only record new request/response interactions that Betamax sees. all will just re-record every interaction every time. In our example, we’ll use new_episodes so our code now looks like:

with recorder.use_cassette('more-complicated-cassettes',
                           serialize_with='prettyjson',
                           match_requests_on=matchers,
                           record='new_episodes'):

Known Issues

Tests Periodically Slow Down

Description:

Requests checks if it should use or bypass proxies using the standard library function proxy_bypass. This has been known to cause slow downs when using Requests and can cause your recorded requests to slow down as well.

Betamax presently has no way to prevent this from being called as it operates at a lower level in Requests than is necessary.

Workarounds:

  • Mock gethostbyname method from socket library, to force a localhost setting, e.g.,

    import socket
    socket.gethostbyname = lambda x: '127.0.0.1'
    
  • Set trust_env to False on the session used with Betamax. This will prevent Requests from checking for proxies and whether it needs bypass them.

Related bugs: