File: advanced.rst

package info (click to toggle)
vcr.py 7.0.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,060 kB
  • sloc: python: 6,264; makefile: 188; sh: 1
file content (429 lines) | stat: -rw-r--r-- 14,186 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
Advanced Features
=================

If you want, VCR.py can return information about the cassette it is
using to record your requests and responses. This will let you record
your requests and responses and make assertions on them, to make sure
that your code under test is generating the expected requests and
responses. This feature is not present in Ruby's VCR, but I think it is
a nice addition. Here's an example:

.. code:: python

    import vcr
    import urllib2

    with vcr.use_cassette('fixtures/vcr_cassettes/synopsis.yaml') as cass:
        response = urllib2.urlopen('http://www.zombo.com/').read()
        # cass should have 1 request inside it
        assert len(cass) == 1
        # the request uri should have been http://www.zombo.com/
        assert cass.requests[0].uri == 'http://www.zombo.com/'

The ``Cassette`` object exposes the following properties which I
consider part of the API. The fields are as follows:

-  ``requests``: A list of vcr.Request objects corresponding to the http
   requests that were made during the recording of the cassette. The
   requests appear in the order that they were originally processed.
-  ``responses``: A list of the responses made.
-  ``play_count``: The number of times this cassette has played back a
   response.
-  ``all_played``: A boolean indicating whether all the responses have
   been played back.
-  ``responses_of(request)``: Access the responses that match a given
   request
-  ``allow_playback_repeats``: A boolean indicating whether responses
   can be played back more than once.

The ``Request`` object has the following properties:

-  ``uri``: The full uri of the request. Example:
   "https://google.com/?q=vcrpy"
-  ``scheme``: The scheme used to make the request (http or https)
-  ``host``: The host of the request, for example "www.google.com"
-  ``port``: The port the request was made on
-  ``path``: The path of the request. For example "/" or "/home.html"
-  ``query``: The parsed query string of the request. Sorted list of
   name, value pairs.
-  ``method`` : The method used to make the request, for example "GET"
   or "POST"
-  ``body``: The body of the request, usually empty except for POST /
   PUT / etc

Backwards compatible properties:

-  ``url``: The ``uri`` alias
-  ``protocol``: The ``scheme`` alias

Register your own serializer
----------------------------

Don't like JSON or YAML? That's OK, VCR.py can serialize to any format
you would like. Create your own module or class instance with 2 methods:

-  ``def deserialize(cassette_string)``
-  ``def serialize(cassette_dict)``

Finally, register your class with VCR to use your new serializer.

.. code:: python

    import vcr

    class BogoSerializer:
        """
        Must implement serialize() and deserialize() methods
        """
        pass

    my_vcr = vcr.VCR()
    my_vcr.register_serializer('bogo', BogoSerializer())

    with my_vcr.use_cassette('test.bogo', serializer='bogo'):
        # your http here

    # After you register, you can set the default serializer to your new serializer

    my_vcr.serializer = 'bogo'

    with my_vcr.use_cassette('test.bogo'):
        # your http here

Register your own request matcher
---------------------------------

Create your own method with the following signature

.. code:: python

    def my_matcher(r1, r2):

Your method receives the two requests and can either:

- Use an ``assert`` statement: return None if they match and raise ``AssertionError`` if not.
- Return a boolean: ``True`` if they match, ``False`` if not.

Note: in order to have good feedback when a matcher fails, we recommend using an ``assert`` statement with a clear error message.

Finally, register your method with VCR to use your new request matcher.

.. code:: python

    import vcr

    def jurassic_matcher(r1, r2):
        assert r1.uri == r2.uri and 'JURASSIC PARK' in r1.body, \
            'required string (JURASSIC PARK) not found in request body'

    my_vcr = vcr.VCR()
    my_vcr.register_matcher('jurassic', jurassic_matcher)

    with my_vcr.use_cassette('test.yml', match_on=['jurassic']):
        # your http here

    # After you register, you can set the default match_on to use your new matcher

    my_vcr.match_on = ['jurassic']

    with my_vcr.use_cassette('test.yml'):
        # your http here

Register your own cassette persister
------------------------------------

Create your own persistence class, see the example below:

Your custom persister must implement both ``load_cassette`` and ``save_cassette``
methods.  The ``load_cassette`` method must return a deserialized cassette or raise
either ``CassetteNotFoundError`` if no cassette is found, or ``CassetteDecodeError``
if the cassette cannot be successfully deserialized.

Once the persister class is defined, register with VCR like so...

.. code:: python

    import vcr
    my_vcr = vcr.VCR()

    class CustomerPersister:
        # implement Persister methods...

    my_vcr.register_persister(CustomPersister)

Filter sensitive data from the request
--------------------------------------

If you are checking your cassettes into source control, and are using
some form of authentication in your tests, you can filter out that
information so it won't appear in your cassette files. There are a few
ways to do this:

Filter information from HTTP Headers
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Use the ``filter_headers`` configuration option with a list of headers
to filter.

.. code:: python

    with my_vcr.use_cassette('test.yml', filter_headers=['authorization']):
        # sensitive HTTP request goes here

Filter information from HTTP querystring
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Use the ``filter_query_parameters`` configuration option with a list of
query parameters to filter.

.. code:: python

    with my_vcr.use_cassette('test.yml', filter_query_parameters=['api_key']):
        requests.get('http://api.com/getdata?api_key=secretstring')

Filter information from HTTP post data
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Use the ``filter_post_data_parameters`` configuration option with a list
of post data parameters to filter.

.. code:: python

    with my_vcr.use_cassette('test.yml', filter_post_data_parameters=['api_key']):
        requests.post('http://api.com/postdata', data={'api_key': 'secretstring'})

Advanced use of filter_headers, filter_query_parameters and filter_post_data_parameters
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

In all of the above cases, it's also possible to pass a list of ``(key, value)``
tuples where the value can be any of the following:

* A new value to replace the original value.
* ``None`` to remove the key/value pair. (Same as passing a simple key string.)
* A callable that returns a new value or ``None``.

So these two calls are the same:

.. code:: python

    # original (still works)
    vcr = VCR(filter_headers=['authorization'])

    # new
    vcr = VCR(filter_headers=[('authorization', None)])

Here are two examples of the new functionality:

.. code:: python

    # replace with a static value (most common)
    vcr = VCR(filter_headers=[('authorization', 'XXXXXX')])

    # replace with a callable, for example when testing
    # lots of different kinds of authorization.
    def replace_auth(key, value, request):
        auth_type = value.split(' ', 1)[0]
        return '{} {}'.format(auth_type, 'XXXXXX')

Custom Request filtering
~~~~~~~~~~~~~~~~~~~~~~~~

If none of these covers your request filtering needs, you can register a
callback with the ``before_record_request`` configuration option to
manipulate the HTTP request before adding it to the cassette, or return
``None`` to ignore it entirely. Here is an example that will never record
requests to the ``'/login'`` path:

.. code:: python

    def before_record_cb(request):
        if request.path == '/login':
            return None
        return request

    my_vcr = vcr.VCR(
        before_record_request=before_record_cb,
    )
    with my_vcr.use_cassette('test.yml'):
        # your http code here

You can also mutate the request using this callback. For example, you
could remove all query parameters from any requests to the ``'/login'``
path.

.. code:: python

    def scrub_login_request(request):
        if request.path == '/login':
            request.uri, _ =  urllib.splitquery(request.uri)
        return request

    my_vcr = vcr.VCR(
        before_record_request=scrub_login_request,
    )
    with my_vcr.use_cassette('test.yml'):
        # your http code here

Custom Response Filtering
~~~~~~~~~~~~~~~~~~~~~~~~~

You can also do response filtering with the
``before_record_response`` configuration option. Its usage is
similar to the above ``before_record_request`` - you can
mutate the response, or return ``None`` to avoid recording
the request and response altogether. For example to hide
sensitive data from the response body:

.. code:: python

    def scrub_string(string, replacement=''):
        def before_record_response(response):
            response['body']['string'] = response['body']['string'].replace(string, replacement)
            return response
        return before_record_response

    my_vcr = vcr.VCR(
        before_record_response=scrub_string(settings.USERNAME, 'username'),
    )
    with my_vcr.use_cassette('test.yml'):
         # your http code here


Decode compressed response
---------------------------

When the ``decode_compressed_response`` keyword argument of a ``VCR`` object
is set to True, VCR will decompress "gzip" and "deflate" response bodies
before recording. This ensures that these interactions become readable and
editable after being serialized.

.. note::
    Decompression is done before any other specified `Custom Response Filtering`_.

This option should be avoided if the actual decompression of response bodies
is part of the functionality of the library or app being tested.

Ignore requests
---------------

If you would like to completely ignore certain requests, you can do it
in a few ways:

-  Set the ``ignore_localhost`` option equal to True. This will not
   record any requests sent to (or responses from) localhost, 127.0.0.1,
   or 0.0.0.0.
-  Set the ``ignore_hosts`` configuration option to a list of hosts to
   ignore
-  Add a ``before_record_request`` or ``before_record_response`` callback
   that returns ``None`` for requests you want to ignore (see above).

Requests that are ignored by VCR will not be saved in a cassette, nor
played back from a cassette. VCR will completely ignore those requests
as if it didn't notice them at all, and they will continue to hit the
server as if VCR were not there.

Custom Patches
--------------

If you use a custom ``HTTPConnection`` class, or otherwise make http
requests in a way that requires additional patching, you can use the
``custom_patches`` keyword argument of the ``VCR`` and ``Cassette``
objects to patch those objects whenever a cassette's context is entered.
To patch a custom version of ``HTTPConnection`` you can do something
like this:

::

    import where_the_custom_https_connection_lives
    from vcr.stubs import VCRHTTPSConnection
    my_vcr = config.VCR(custom_patches=((where_the_custom_https_connection_lives, 'CustomHTTPSConnection', VCRHTTPSConnection),))

    @my_vcr.use_cassette(...)

Automatic Cassette Naming
-------------------------

VCR.py now allows the omission of the path argument to the use\_cassette
function. Both of the following are now legal/should work

.. code:: python

    @my_vcr.use_cassette
    def my_test_function():
        ...

.. code:: python

    @my_vcr.use_cassette()
    def my_test_function():
        ...

In both cases, VCR.py will use a path that is generated from the
provided test function's name. If no ``cassette_library_dir`` has been
set, the cassette will be in a file with the name of the test function
in directory of the file in which the test function is declared. If a
``cassette_library_dir`` has been set, the cassette will appear in that
directory in a file with the name of the decorated function.

It is possible to control the path produced by the automatic naming
machinery by customizing the ``path_transformer`` and
``func_path_generator`` vcr variables. To add an extension to all
cassette names, use ``VCR.ensure_suffix`` as follows:

.. code:: python

    my_vcr = VCR(path_transformer=VCR.ensure_suffix('.yaml'))

    @my_vcr.use_cassette
    def my_test_function():

Rewind Cassette
---------------

VCR.py allows to rewind a cassette in order to replay it inside the same function/test.

.. code:: python

    with vcr.use_cassette('fixtures/vcr_cassettes/synopsis.yaml') as cass:
        response = urllib2.urlopen('http://www.zombo.com/').read()
        assert cass.all_played
        cass.rewind()
        assert not cass.all_played

Playback Repeats
----------------

By default, each response in a cassette can only be matched and played back
once while the cassette is in use, unless the cassette is rewound.

If you want to allow playback repeats without rewinding the cassette, use
the Cassette ``allow_playback_repeats`` option.

.. code:: python

    with vcr.use_cassette('fixtures/vcr_cassettes/synopsis.yaml', allow_playback_repeats=True) as cass:
        for x in range(10):
            response = urllib2.urlopen('http://www.zombo.com/').read()
        assert cass.all_played

Discards Cassette on Errors
---------------------------

By default VCR will save the cassette file even when there is any error inside
the enclosing context/test.

If you want to save the cassette only when the test succeeds, set the Cassette
``record_on_exception`` option to ``False``.

.. code:: python

    try:
        my_vcr = VCR(record_on_exception=False)
        with my_vcr.use_cassette('fixtures/vcr_cassettes/synopsis.yaml') as cass:
            response = urllib2.urlopen('http://www.zombo.com/').read()
            raise RuntimeError("Oops, something happened")
    except RuntimeError:
        pass

    # Since there was an exception, the cassette file hasn't been created.
    assert not os.path.exists('fixtures/vcr_cassettes/synopsis.yaml')