1Third-Party Packages
2====================
3
4Betamax was created to be a very close imitation of `VCR`_. As such, it has
5the default set of request matchers and a subset of the supported cassette
6serializers for VCR.
7
8As part of my own usage of Betamax, and supporting other people's usage of
9Betamax, I've created (and maintain) two third party packages that provide
10extra request matchers and cassette serializers.
11
12- `betamax-matchers`_
13- `betamax-serializers`_
14
15For simplicity, those modules will be documented here instead of on their own
16documentation sites.
17
18Request Matchers
19----------------
20
21There are three third-party request matchers provided by the
22`betamax-matchers`_ package:
23
24- :class:`~betamax_matchers.form_urlencoded.URLEncodedBodyMatcher`,
25  ``'form-urlencoded-body'``
26- :class:`~betamax_matchers.json_body.JSONBodyMatcher`, ``'json-body'``
27- :class:`~betamax_matchers.multipart.MultipartFormDataBodyMatcher`,
28  ``'multipart-form-data-body'``
29
30In order to use any of these we have to register them with Betamax. Below we
31will register all three but you do not need to do that if you only need to use
32one:
33
34.. code-block:: python
35
36    import betamax
37    from betamax_matchers import form_urlencoded
38    from betamax_matchers import json_body
39    from betamax_matchers import multipart
40
41    betamax.Betamax.register_request_matcher(
42        form_urlencoded.URLEncodedBodyMatcher
43        )
44    betamax.Betamax.register_request_matcher(
45        json_body.JSONBodyMatcher
46        )
47    betamax.Betamax.register_request_matcher(
48        multipart.MultipartFormDataBodyMatcher
49        )
50
51All of these classes inherit from :class:`betamax.BaseMatcher` which means
52that each needs a name that will be used when specifying what matchers to use
53with Betamax. I have noted those next to the class name for each matcher
54above. Let's use the JSON body matcher in an example though:
55
56.. code-block:: python
57
58    import betamax
59    from betamax_matchers import json_body
60    # This example requires at least requests 2.5.0
61    import requests
62
63    betamax.Betamax.register_request_matcher(
64        json_body.JSONBodyMatcher
65        )
66
67
68    def main():
69        session = requests.Session()
70        recorder = betamax.Betamax(session, cassette_library_dir='.')
71        url = 'https://httpbin.org/post'
72        json_data = {'key': 'value',
73                     'other-key': 'other-value',
74                     'yet-another-key': 'yet-another-value'}
75        matchers = ['method', 'uri', 'json-body']
76
77        with recorder.use_cassette('json-body-example', match_requests_on=matchers):
78            r = session.post(url, json=json_data)
79
80
81    if __name__ == '__main__':
82        main()
83
84If we ran that request without those matcher with hash seed randomization,
85then we would occasionally receive exceptions that a request could not be
86matched. That is because dictionaries are not inherently ordered so the body
87string of the request can change and be any of the following:
88
89.. code-block:: js
90
91    {"key": "value", "other-key": "other-value", "yet-another-key":
92    "yet-another-value"}
93
94.. code-block:: js
95
96    {"key": "value", "yet-another-key": "yet-another-value", "other-key":
97    "other-value"}
98
99.. code-block:: js
100
101    {"other-key": "other-value", "yet-another-key": "yet-another-value",
102    "key": "value"}
103
104
105.. code-block:: js
106
107    {"yet-another-key": "yet-another-value", "key": "value", "other-key":
108    "other-value"}
109
110.. code-block:: js
111
112    {"yet-another-key": "yet-another-value", "other-key": "other-value",
113    "key": "value"}
114
115.. code-block:: js
116
117    {"other-key": "other-value", "key": "value", "yet-another-key":
118    "yet-another-value"}
119
120But using the ``'json-body'`` matcher, the matcher will parse the request and
121compare python dictionaries instead of python strings. That will completely
122bypass the issues introduced by hash randomization. I use this matcher
123extensively in `github3.py`_\ 's tests.
124
125Cassette Serializers
126--------------------
127
128By default, Betamax only comes with the JSON serializer.
129`betamax-serializers`_ provides extra serializer classes that users have
130contributed.
131
132For example, as we've seen elsewhere in our documentation, the default JSON
133serializer does not create beautiful or easy to read cassettes. As a
134substitute for that, we have the
135:class:`~betamax_serializers.pretty_json.PrettyJSONSerializer` that does that
136for you.
137
138.. code-block:: python
139
140    from betamax import Betamax
141    from betamax_serializers import pretty_json
142
143    import requests
144
145    Betamax.register_serializer(pretty_json.PrettyJSONSerializer)
146
147    session = requests.Session()
148    recorder = Betamax(session)
149    with recorder.use_cassette('testpretty', serialize_with='prettyjson'):
150        session.request(method=method, url=url, ...)
151
152
153This will give us a pretty-printed cassette like:
154
155.. literalinclude:: ../examples/cassettes/more-complicated-cassettes.json
156    :language: js
157
158.. links
159
160.. _VCR:
161    https://relishapp.com/vcr/vcr
162.. _betamax-matchers:
163    https://pypi.python.org/pypi/betamax-matchers
164.. _betamax-serializers:
165    https://pypi.python.org/pypi/betamax-serializers
166.. _github3.py:
167    https://github.com/sigmavirus24/github3.py
168