Skip to content

Commit 3dea853

Browse files
committed
Merge pull request #108 from IvanMalison/fix_CassetteContextDecorator_nesting_issues
Fix cassette context decorator nesting issues
2 parents 20057a6 + 113c95f commit 3dea853

File tree

11 files changed

+609
-255
lines changed

11 files changed

+609
-255
lines changed

Diff for: .gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,5 @@ dist/
66
.coverage
77
*.egg-info/
88
pytestdebug.log
9+
10+
fixtures/

Diff for: README.md

+51-15
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
![vcr.py](https://raw.github.com/kevin1024/vcrpy/master/vcr.png)
44

5-
This is a Python version of [Ruby's VCR library](https://github.com/myronmarston/vcr).
5+
This is a Python version of [Ruby's VCR library](https://github.com/vcr/vcr).
66

77
[![Build Status](https://secure.travis-ci.org/kevin1024/vcrpy.png?branch=master)](http://travis-ci.org/kevin1024/vcrpy)
88
[![Stories in Ready](https://badge.waffle.io/kevin1024/vcrpy.png?label=ready&title=Ready)](https://waffle.io/kevin1024/vcrpy)
@@ -176,13 +176,13 @@ with vcr.use_cassette('fixtures/vcr_cassettes/synopsis.yaml') as cass:
176176
The `Cassette` object exposes the following properties which I consider part of
177177
the API. The fields are as follows:
178178

179-
* `requests`: A list of vcr.Request objects containing the requests made while
180-
this cassette was being used, ordered by the order that the request was made.
179+
* `requests`: A list of vcr.Request objects corresponding to the http requests
180+
that were made during the recording of the cassette. The requests appear in the
181+
order that they were originally processed.
181182
* `responses`: A list of the responses made.
182-
* `play_count`: The number of times this cassette has had a response played
183-
back
184-
* `all_played`: A boolean indicates whether all the responses have been
185-
played back
183+
* `play_count`: The number of times this cassette has played back a response.
184+
* `all_played`: A boolean indicating whether all the responses have been
185+
played back.
186186
* `responses_of(request)`: Access the responses that match a given request
187187

188188
The `Request` object has the following properties:
@@ -215,7 +215,7 @@ Finally, register your class with VCR to use your new serializer.
215215
```python
216216
import vcr
217217

218-
BogoSerializer(object):
218+
class BogoSerializer(object):
219219
"""
220220
Must implement serialize() and deserialize() methods
221221
"""
@@ -293,12 +293,12 @@ with my_vcr.use_cassette('test.yml', filter_query_parameters=['api_key']):
293293
requests.get('http://api.com/getdata?api_key=secretstring')
294294
```
295295

296-
### Custom request filtering
296+
### Custom Request filtering
297297

298-
If neither of these covers your use case, you can register a callback that will
299-
manipulate the HTTP request before adding it to the cassette. Use the
300-
`before_record` configuration option to so this. Here is an
301-
example that will never record requests to the /login endpoint.
298+
If neither of these covers your request filtering needs, you can register a callback
299+
that will manipulate the HTTP request before adding it to the cassette. Use the
300+
`before_record` configuration option to so this. Here is an example that will
301+
never record requests to the /login endpoint.
302302

303303
```python
304304
def before_record_cb(request):
@@ -312,6 +312,40 @@ with my_vcr.use_cassette('test.yml'):
312312
# your http code here
313313
```
314314

315+
You can also mutate the response using this callback. For example, you could
316+
remove all query parameters from any requests to the `'/login'` path.
317+
318+
```python
319+
def scrub_login_request(request):
320+
if request.path == '/login':
321+
request.uri, _ = urllib.splitquery(response.uri)
322+
return request
323+
324+
my_vcr = vcr.VCR(
325+
before_record=scrub_login_request,
326+
)
327+
with my_vcr.use_cassette('test.yml'):
328+
# your http code here
329+
```
330+
331+
### Custom Response Filtering
332+
333+
VCR.py also suports response filtering with the `before_record_response` keyword
334+
argument. It's usage is similar to that of `before_record`:
335+
336+
```python
337+
def scrub_string(string, replacement=''):
338+
def before_record_reponse(response):
339+
return response['body']['string] = response['body']['string].replace(string, replacement)
340+
return scrub_string
341+
342+
my_vcr = vcr.VCR(
343+
before_record=scrub_string(settings.USERNAME, 'username'),
344+
)
345+
with my_vcr.use_cassette('test.yml'):
346+
# your http code here
347+
```
348+
315349
## Ignore requests
316350

317351
If you would like to completely ignore certain requests, you can do it in a
@@ -335,7 +369,7 @@ to `brew install libyaml` [[Homebrew](http://mxcl.github.com/homebrew/)])
335369

336370
## Ruby VCR compatibility
337371

338-
I'm not trying to match the format of the Ruby VCR YAML files. Cassettes
372+
VCR.py does not aim to match the format of the Ruby VCR YAML files. Cassettes
339373
generated by Ruby's VCR are not compatible with VCR.py.
340374

341375
## Running VCR's test suite
@@ -356,7 +390,7 @@ installed.
356390
Also, in order for the boto tests to run, you will need an AWS key. Refer to
357391
the [boto
358392
documentation](http://boto.readthedocs.org/en/latest/getting_started.html) for
359-
how to set this up. I have marked the boto tests as optional in Travis so you
393+
how to set this up. I have marked the boto tests as optional in Travis so you
360394
don't have to worry about them failing if you submit a pull request.
361395

362396

@@ -423,6 +457,8 @@ API in version 1.0.x
423457

424458

425459
## Changelog
460+
* 1.1.0 Add `before_record_response`. Fix several bugs related to the context
461+
management of cassettes.
426462
* 1.0.3: Fix an issue with requests 2.4 and make sure case sensitivity is
427463
consistent across python versions
428464
* 1.0.2: Fix an issue with requests 2.3

Diff for: setup.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ def run_tests(self):
2020

2121
setup(
2222
name='vcrpy',
23-
version='1.0.3',
23+
version='1.1.0',
2424
description=(
2525
"Automatically mock your HTTP interactions to simplify and "
2626
"speed up testing"
@@ -41,7 +41,7 @@ def run_tests(self):
4141
'vcr.compat': 'vcr/compat',
4242
'vcr.persisters': 'vcr/persisters',
4343
},
44-
install_requires=['PyYAML', 'contextdecorator', 'six'],
44+
install_requires=['PyYAML', 'mock', 'six', 'contextlib2'],
4545
license='MIT',
4646
tests_require=['pytest', 'mock', 'pytest-localserver'],
4747
cmdclass={'test': PyTest},

Diff for: tests/integration/test_requests.py

+71-15
Original file line numberDiff line numberDiff line change
@@ -24,41 +24,41 @@ def scheme(request):
2424
def test_status_code(scheme, tmpdir):
2525
'''Ensure that we can read the status code'''
2626
url = scheme + '://httpbin.org/'
27-
with vcr.use_cassette(str(tmpdir.join('atts.yaml'))) as cass:
27+
with vcr.use_cassette(str(tmpdir.join('atts.yaml'))):
2828
status_code = requests.get(url).status_code
2929

30-
with vcr.use_cassette(str(tmpdir.join('atts.yaml'))) as cass:
30+
with vcr.use_cassette(str(tmpdir.join('atts.yaml'))):
3131
assert status_code == requests.get(url).status_code
3232

3333

3434
def test_headers(scheme, tmpdir):
3535
'''Ensure that we can read the headers back'''
3636
url = scheme + '://httpbin.org/'
37-
with vcr.use_cassette(str(tmpdir.join('headers.yaml'))) as cass:
37+
with vcr.use_cassette(str(tmpdir.join('headers.yaml'))):
3838
headers = requests.get(url).headers
3939

40-
with vcr.use_cassette(str(tmpdir.join('headers.yaml'))) as cass:
40+
with vcr.use_cassette(str(tmpdir.join('headers.yaml'))):
4141
assert headers == requests.get(url).headers
4242

4343

4444
def test_body(tmpdir, scheme):
4545
'''Ensure the responses are all identical enough'''
4646
url = scheme + '://httpbin.org/bytes/1024'
47-
with vcr.use_cassette(str(tmpdir.join('body.yaml'))) as cass:
47+
with vcr.use_cassette(str(tmpdir.join('body.yaml'))):
4848
content = requests.get(url).content
4949

50-
with vcr.use_cassette(str(tmpdir.join('body.yaml'))) as cass:
50+
with vcr.use_cassette(str(tmpdir.join('body.yaml'))):
5151
assert content == requests.get(url).content
5252

5353

5454
def test_auth(tmpdir, scheme):
5555
'''Ensure that we can handle basic auth'''
5656
auth = ('user', 'passwd')
5757
url = scheme + '://httpbin.org/basic-auth/user/passwd'
58-
with vcr.use_cassette(str(tmpdir.join('auth.yaml'))) as cass:
58+
with vcr.use_cassette(str(tmpdir.join('auth.yaml'))):
5959
one = requests.get(url, auth=auth)
6060

61-
with vcr.use_cassette(str(tmpdir.join('auth.yaml'))) as cass:
61+
with vcr.use_cassette(str(tmpdir.join('auth.yaml'))):
6262
two = requests.get(url, auth=auth)
6363
assert one.content == two.content
6464
assert one.status_code == two.status_code
@@ -81,10 +81,10 @@ def test_post(tmpdir, scheme):
8181
'''Ensure that we can post and cache the results'''
8282
data = {'key1': 'value1', 'key2': 'value2'}
8383
url = scheme + '://httpbin.org/post'
84-
with vcr.use_cassette(str(tmpdir.join('requests.yaml'))) as cass:
84+
with vcr.use_cassette(str(tmpdir.join('requests.yaml'))):
8585
req1 = requests.post(url, data).content
8686

87-
with vcr.use_cassette(str(tmpdir.join('requests.yaml'))) as cass:
87+
with vcr.use_cassette(str(tmpdir.join('requests.yaml'))):
8888
req2 = requests.post(url, data).content
8989

9090
assert req1 == req2
@@ -93,7 +93,7 @@ def test_post(tmpdir, scheme):
9393
def test_redirects(tmpdir, scheme):
9494
'''Ensure that we can handle redirects'''
9595
url = scheme + '://httpbin.org/redirect-to?url=bytes/1024'
96-
with vcr.use_cassette(str(tmpdir.join('requests.yaml'))) as cass:
96+
with vcr.use_cassette(str(tmpdir.join('requests.yaml'))):
9797
content = requests.get(url).content
9898

9999
with vcr.use_cassette(str(tmpdir.join('requests.yaml'))) as cass:
@@ -124,11 +124,11 @@ def test_gzip(tmpdir, scheme):
124124
url = scheme + '://httpbin.org/gzip'
125125
response = requests.get(url)
126126

127-
with vcr.use_cassette(str(tmpdir.join('gzip.yaml'))) as cass:
127+
with vcr.use_cassette(str(tmpdir.join('gzip.yaml'))):
128128
response = requests.get(url)
129129
assert_is_json(response.content)
130130

131-
with vcr.use_cassette(str(tmpdir.join('gzip.yaml'))) as cass:
131+
with vcr.use_cassette(str(tmpdir.join('gzip.yaml'))):
132132
assert_is_json(response.content)
133133

134134

@@ -143,9 +143,65 @@ def test_session_and_connection_close(tmpdir, scheme):
143143
with vcr.use_cassette(str(tmpdir.join('session_connection_closed.yaml'))):
144144
session = requests.session()
145145

146-
resp = session.get('http://httpbin.org/get', headers={'Connection': 'close'})
147-
resp = session.get('http://httpbin.org/get', headers={'Connection': 'close'})
146+
session.get('http://httpbin.org/get', headers={'Connection': 'close'})
147+
session.get('http://httpbin.org/get', headers={'Connection': 'close'})
148+
148149

149150
def test_https_with_cert_validation_disabled(tmpdir):
150151
with vcr.use_cassette(str(tmpdir.join('cert_validation_disabled.yaml'))):
151152
requests.get('https://httpbin.org', verify=False)
153+
154+
155+
def test_session_can_make_requests_after_requests_unpatched(tmpdir):
156+
with vcr.use_cassette(str(tmpdir.join('test_session_after_unpatched.yaml'))):
157+
session = requests.session()
158+
session.get('http://httpbin.org/get')
159+
160+
with vcr.use_cassette(str(tmpdir.join('test_session_after_unpatched.yaml'))):
161+
session = requests.session()
162+
session.get('http://httpbin.org/get')
163+
164+
session.get('http://httpbin.org/status/200')
165+
166+
167+
def test_session_created_before_use_cassette_is_patched(tmpdir, scheme):
168+
url = scheme + '://httpbin.org/bytes/1024'
169+
# Record arbitrary, random data to the cassette
170+
with vcr.use_cassette(str(tmpdir.join('session_created_outside.yaml'))):
171+
session = requests.session()
172+
body = session.get(url).content
173+
174+
# Create a session outside of any cassette context manager
175+
session = requests.session()
176+
# Make a request to make sure that a connectionpool is instantiated
177+
session.get(scheme + '://httpbin.org/get')
178+
179+
with vcr.use_cassette(str(tmpdir.join('session_created_outside.yaml'))):
180+
# These should only be the same if the patching succeeded.
181+
assert session.get(url).content == body
182+
183+
184+
def test_nested_cassettes_with_session_created_before_nesting(scheme, tmpdir):
185+
'''
186+
This tests ensures that a session that was created while one cassette was
187+
active is patched to the use the responses of a second cassette when it
188+
is enabled.
189+
'''
190+
url = scheme + '://httpbin.org/bytes/1024'
191+
with vcr.use_cassette(str(tmpdir.join('first_nested.yaml'))):
192+
session = requests.session()
193+
first_body = session.get(url).content
194+
with vcr.use_cassette(str(tmpdir.join('second_nested.yaml'))):
195+
second_body = session.get(url).content
196+
third_body = requests.get(url).content
197+
198+
with vcr.use_cassette(str(tmpdir.join('second_nested.yaml'))):
199+
session = requests.session()
200+
assert session.get(url).content == second_body
201+
with vcr.use_cassette(str(tmpdir.join('first_nested.yaml'))):
202+
assert session.get(url).content == first_body
203+
assert session.get(url).content == third_body
204+
205+
# Make sure that the session can now get content normally.
206+
session.get('http://www.reddit.com')
207+

0 commit comments

Comments
 (0)