Skip to content

Commit

Permalink
Merge pull request #108 from IvanMalison/fix_CassetteContextDecorator…
Browse files Browse the repository at this point in the history
…_nesting_issues

Fix cassette context decorator nesting issues
  • Loading branch information
colonelpanic8 committed Sep 21, 2014
2 parents 20057a6 + 113c95f commit 3dea853
Show file tree
Hide file tree
Showing 11 changed files with 609 additions and 255 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ dist/
.coverage
*.egg-info/
pytestdebug.log

fixtures/
66 changes: 51 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

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

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

[![Build Status](https://secure.travis-ci.org/kevin1024/vcrpy.png?branch=master)](http://travis-ci.org/kevin1024/vcrpy)
[![Stories in Ready](https://badge.waffle.io/kevin1024/vcrpy.png?label=ready&title=Ready)](https://waffle.io/kevin1024/vcrpy)
Expand Down Expand Up @@ -176,13 +176,13 @@ with vcr.use_cassette('fixtures/vcr_cassettes/synopsis.yaml') as cass:
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 containing the requests made while
this cassette was being used, ordered by the order that the request was made.
* `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 had a response played
back
* `all_played`: A boolean indicates whether all the responses have been
played back
* `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

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

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

### Custom request filtering
### Custom Request filtering

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

```python
def before_record_cb(request):
Expand All @@ -312,6 +312,40 @@ with my_vcr.use_cassette('test.yml'):
# your http code here
```

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

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

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

### Custom Response Filtering

VCR.py also suports response filtering with the `before_record_response` keyword
argument. It's usage is similar to that of `before_record`:

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

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

## Ignore requests

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

## Ruby VCR compatibility

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

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


Expand Down Expand Up @@ -423,6 +457,8 @@ API in version 1.0.x


## Changelog
* 1.1.0 Add `before_record_response`. Fix several bugs related to the context
management of cassettes.
* 1.0.3: Fix an issue with requests 2.4 and make sure case sensitivity is
consistent across python versions
* 1.0.2: Fix an issue with requests 2.3
Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def run_tests(self):

setup(
name='vcrpy',
version='1.0.3',
version='1.1.0',
description=(
"Automatically mock your HTTP interactions to simplify and "
"speed up testing"
Expand All @@ -41,7 +41,7 @@ def run_tests(self):
'vcr.compat': 'vcr/compat',
'vcr.persisters': 'vcr/persisters',
},
install_requires=['PyYAML', 'contextdecorator', 'six'],
install_requires=['PyYAML', 'mock', 'six', 'contextlib2'],
license='MIT',
tests_require=['pytest', 'mock', 'pytest-localserver'],
cmdclass={'test': PyTest},
Expand Down
86 changes: 71 additions & 15 deletions tests/integration/test_requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,41 +24,41 @@ def scheme(request):
def test_status_code(scheme, tmpdir):
'''Ensure that we can read the status code'''
url = scheme + '://httpbin.org/'
with vcr.use_cassette(str(tmpdir.join('atts.yaml'))) as cass:
with vcr.use_cassette(str(tmpdir.join('atts.yaml'))):
status_code = requests.get(url).status_code

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


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

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


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

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


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

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

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

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

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

with vcr.use_cassette(str(tmpdir.join('gzip.yaml'))) as cass:
with vcr.use_cassette(str(tmpdir.join('gzip.yaml'))):
response = requests.get(url)
assert_is_json(response.content)

with vcr.use_cassette(str(tmpdir.join('gzip.yaml'))) as cass:
with vcr.use_cassette(str(tmpdir.join('gzip.yaml'))):
assert_is_json(response.content)


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

resp = session.get('http://httpbin.org/get', headers={'Connection': 'close'})
resp = session.get('http://httpbin.org/get', headers={'Connection': 'close'})
session.get('http://httpbin.org/get', headers={'Connection': 'close'})
session.get('http://httpbin.org/get', headers={'Connection': 'close'})


def test_https_with_cert_validation_disabled(tmpdir):
with vcr.use_cassette(str(tmpdir.join('cert_validation_disabled.yaml'))):
requests.get('https://httpbin.org', verify=False)


def test_session_can_make_requests_after_requests_unpatched(tmpdir):
with vcr.use_cassette(str(tmpdir.join('test_session_after_unpatched.yaml'))):
session = requests.session()
session.get('http://httpbin.org/get')

with vcr.use_cassette(str(tmpdir.join('test_session_after_unpatched.yaml'))):
session = requests.session()
session.get('http://httpbin.org/get')

session.get('http://httpbin.org/status/200')


def test_session_created_before_use_cassette_is_patched(tmpdir, scheme):
url = scheme + '://httpbin.org/bytes/1024'
# Record arbitrary, random data to the cassette
with vcr.use_cassette(str(tmpdir.join('session_created_outside.yaml'))):
session = requests.session()
body = session.get(url).content

# Create a session outside of any cassette context manager
session = requests.session()
# Make a request to make sure that a connectionpool is instantiated
session.get(scheme + '://httpbin.org/get')

with vcr.use_cassette(str(tmpdir.join('session_created_outside.yaml'))):
# These should only be the same if the patching succeeded.
assert session.get(url).content == body


def test_nested_cassettes_with_session_created_before_nesting(scheme, tmpdir):
'''
This tests ensures that a session that was created while one cassette was
active is patched to the use the responses of a second cassette when it
is enabled.
'''
url = scheme + '://httpbin.org/bytes/1024'
with vcr.use_cassette(str(tmpdir.join('first_nested.yaml'))):
session = requests.session()
first_body = session.get(url).content
with vcr.use_cassette(str(tmpdir.join('second_nested.yaml'))):
second_body = session.get(url).content
third_body = requests.get(url).content

with vcr.use_cassette(str(tmpdir.join('second_nested.yaml'))):
session = requests.session()
assert session.get(url).content == second_body
with vcr.use_cassette(str(tmpdir.join('first_nested.yaml'))):
assert session.get(url).content == first_body
assert session.get(url).content == third_body

# Make sure that the session can now get content normally.
session.get('http://www.reddit.com')

Loading

0 comments on commit 3dea853

Please sign in to comment.