Getting Started¶
The first step is to make sure Betamax is right for you. Let’s start by answering the following questions
Are you using Requests?
If you’re not using Requests, Betamax is not for you. You should checkout VCRpy.
Are you using Sessions or are you using the functional API (e.g.,
requests.get
)?If you’re using the functional API, and aren’t willing to use Sessions, Betamax is not yet for you.
So if you’re using Requests and you’re using Sessions, you’re in the right place.
Betamax officially supports py.test and unittest but it should integrate well with nose as well.
Installation¶
$ pip install betamax
Configuration¶
When starting with Betamax, you need to tell it where to store the cassettes that it creates. There’s two ways to do this:
If you’re using
Betamax
oruse_cassette
you can pass thecassette_library_dir
option. For example,import betamax import requests session = requests.Session() recorder = betamax.Betamax(session, cassette_library_dir='cassettes') with recorder.use_cassette('introduction'): # ...
You can do it once, globally, for your test suite.
import betamax with betamax.Betamax.configure() as config: config.cassette_library_dir = 'cassettes'
Note
If you don’t set a cassette directory, Betamax won’t save cassettes to disk
There are other configuration options that can be provided, but this is the only one that is required.
Recording Your First Cassette¶
Let’s make a file named our_first_recorded_session.py
. Let’s add the
following to our file:
import betamax
import requests
CASSETTE_LIBRARY_DIR = 'examples/cassettes/'
def main():
session = requests.Session()
recorder = betamax.Betamax(
session, cassette_library_dir=CASSETTE_LIBRARY_DIR
)
with recorder.use_cassette('our-first-recorded-session'):
session.get('https://httpbin.org/get')
if __name__ == '__main__':
main()
If we then run our script, we’ll see that a new file is created in our specified cassette directory. It should look something like:
{"http_interactions": [{"request": {"body": {"string": "", "encoding": "utf-8"}, "headers": {"Connection": ["keep-alive"], "Accept-Encoding": ["gzip, deflate"], "Accept": ["*/*"], "User-Agent": ["python-requests/2.7.0 CPython/2.7.9 Darwin/14.1.0"]}, "method": "GET", "uri": "https://httpbin.org/get"}, "response": {"body": {"string": "{\n \"args\": {}, \n \"headers\": {\n \"Accept\": \"*/*\", \n \"Accept-Encoding\": \"gzip, deflate\", \n \"Host\": \"httpbin.org\", \n \"User-Agent\": \"python-requests/2.7.0 CPython/2.7.9 Darwin/14.1.0\"\n }, \n \"origin\": \"127.0.0.1\", \n \"url\": \"https://httpbin.org/get\"\n}\n", "encoding": null}, "headers": {"content-length": ["265"], "server": ["nginx"], "connection": ["keep-alive"], "access-control-allow-credentials": ["true"], "date": ["Fri, 19 Jun 2015 04:10:33 GMT"], "access-control-allow-origin": ["*"], "content-type": ["application/json"]}, "status": {"message": "OK", "code": 200}, "url": "https://httpbin.org/get"}, "recorded_at": "2015-06-19T04:10:33"}], "recorded_with": "betamax/0.4.1"}
Now, each subsequent time that we run that script, we will use the recorded interaction instead of talking to the internet over and over again.
Note
There is no need to write any other code to replay your cassettes. Each time you run that session with the cassette in place, Betamax does all the heavy lifting for you.
Recording More Complex Cassettes¶
Most times we cannot isolate our tests to a single request at a time, so we’ll have cassettes that make multiple requests. Betamax can handle these with ease, let’s take a look at an 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()
Before we run this example, we have to install a new package:
betamax-serializers
, e.g., pip install betamax-serializers
.
If we now run our new example, we’ll see a new file appear in our
examples/cassettes/
directory named
more-complicated-cassettes.json
. This cassette will be much larger as
a result of making 3 requests and receiving 3 responses. You’ll also notice
that we imported betamax_serializers.pretty_json
and called
register_serializer()
with
PrettyJSONSerializer
. Then we added
a keyword argument to our invocation of use_cassette()
,
serialize_with='prettyjson'
.
PrettyJSONSerializer
is a class
provided by the betamax-serializers
package on PyPI that can serialize and
deserialize cassette data into JSON while allowing it to be easily human
readable and pretty. Let’s see the results:
{
"http_interactions": [
{
"recorded_at": "2015-06-21T19:22:54",
"request": {
"body": {
"encoding": "utf-8",
"string": ""
},
"headers": {
"Accept": [
"*/*"
],
"Accept-Encoding": [
"gzip, deflate"
],
"Connection": [
"keep-alive"
],
"User-Agent": [
"python-requests/2.7.0 CPython/2.7.9 Darwin/14.1.0"
]
},
"method": "GET",
"uri": "https://httpbin.org/get"
},
"response": {
"body": {
"encoding": null,
"string": "{\n \"args\": {}, \n \"headers\": {\n \"Accept\": \"*/*\", \n \"Accept-Encoding\": \"gzip, deflate\", \n \"Host\": \"httpbin.org\", \n \"User-Agent\": \"python-requests/2.7.0 CPython/2.7.9 Darwin/14.1.0\"\n }, \n \"origin\": \"127.0.0.1\", \n \"url\": \"https://httpbin.org/get\"\n}\n"
},
"headers": {
"access-control-allow-credentials": [
"true"
],
"access-control-allow-origin": [
"*"
],
"connection": [
"keep-alive"
],
"content-length": [
"265"
],
"content-type": [
"application/json"
],
"date": [
"Sun, 21 Jun 2015 19:22:54 GMT"
],
"server": [
"nginx"
]
},
"status": {
"code": 200,
"message": "OK"
},
"url": "https://httpbin.org/get"
}
},
{
"recorded_at": "2015-06-21T19:22:54",
"request": {
"body": {
"encoding": "utf-8",
"string": "{\"some-attribute\": \"some-value\"}"
},
"headers": {
"Accept": [
"*/*"
],
"Accept-Encoding": [
"gzip, deflate"
],
"Connection": [
"keep-alive"
],
"Content-Length": [
"32"
],
"Content-Type": [
"application/json"
],
"User-Agent": [
"python-requests/2.7.0 CPython/2.7.9 Darwin/14.1.0"
]
},
"method": "POST",
"uri": "https://httpbin.org/post?id=20"
},
"response": {
"body": {
"encoding": null,
"string": "{\n \"args\": {\n \"id\": \"20\"\n }, \n \"data\": \"{\\\"some-attribute\\\": \\\"some-value\\\"}\", \n \"files\": {}, \n \"form\": {}, \n \"headers\": {\n \"Accept\": \"*/*\", \n \"Accept-Encoding\": \"gzip, deflate\", \n \"Content-Length\": \"32\", \n \"Content-Type\": \"application/json\", \n \"Host\": \"httpbin.org\", \n \"User-Agent\": \"python-requests/2.7.0 CPython/2.7.9 Darwin/14.1.0\"\n }, \n \"json\": {\n \"some-attribute\": \"some-value\"\n }, \n \"origin\": \"127.0.0.1\", \n \"url\": \"https://httpbin.org/post?id=20\"\n}\n"
},
"headers": {
"access-control-allow-credentials": [
"true"
],
"access-control-allow-origin": [
"*"
],
"connection": [
"keep-alive"
],
"content-length": [
"495"
],
"content-type": [
"application/json"
],
"date": [
"Sun, 21 Jun 2015 19:22:54 GMT"
],
"server": [
"nginx"
]
},
"status": {
"code": 200,
"message": "OK"
},
"url": "https://httpbin.org/post?id=20"
}
},
{
"recorded_at": "2015-06-21T19:22:54",
"request": {
"body": {
"encoding": "utf-8",
"string": ""
},
"headers": {
"Accept": [
"*/*"
],
"Accept-Encoding": [
"gzip, deflate"
],
"Connection": [
"keep-alive"
],
"User-Agent": [
"python-requests/2.7.0 CPython/2.7.9 Darwin/14.1.0"
]
},
"method": "GET",
"uri": "https://httpbin.org/get?id=20"
},
"response": {
"body": {
"encoding": null,
"string": "{\n \"args\": {\n \"id\": \"20\"\n }, \n \"headers\": {\n \"Accept\": \"*/*\", \n \"Accept-Encoding\": \"gzip, deflate\", \n \"Host\": \"httpbin.org\", \n \"User-Agent\": \"python-requests/2.7.0 CPython/2.7.9 Darwin/14.1.0\"\n }, \n \"origin\": \"127.0.0.1\", \n \"url\": \"https://httpbin.org/get?id=20\"\n}\n"
},
"headers": {
"access-control-allow-credentials": [
"true"
],
"access-control-allow-origin": [
"*"
],
"connection": [
"keep-alive"
],
"content-length": [
"289"
],
"content-type": [
"application/json"
],
"date": [
"Sun, 21 Jun 2015 19:22:54 GMT"
],
"server": [
"nginx"
]
},
"status": {
"code": 200,
"message": "OK"
},
"url": "https://httpbin.org/get?id=20"
}
}
],
"recorded_with": "betamax/0.4.2"
}
This makes the cassette easy to read and helps us recognize that requests and responses are paired together. We’ll explore cassettes more a bit later.