mirror of
https://github.com/kevin1024/vcrpy.git
synced 2025-12-08 16:53:23 +00:00
Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b7cbd181f4 | ||
|
|
4a4b04e5a6 | ||
|
|
6d0a8d8ed9 | ||
|
|
985e573303 | ||
|
|
73666bcb49 | ||
|
|
f5db99f079 | ||
|
|
dedca0f6e7 | ||
|
|
0d77d2dcc6 | ||
|
|
56a9a53522 | ||
|
|
34e7760d47 | ||
|
|
32b99f0719 | ||
|
|
d187d910b9 | ||
|
|
a73da71159 | ||
|
|
2385176084 | ||
|
|
14590ae3c7 | ||
|
|
d16b20a780 | ||
|
|
46a2c25f6a | ||
|
|
6bb67567f9 | ||
|
|
e84cd6f059 | ||
|
|
c0b88c2201 | ||
|
|
f003e3e4ab | ||
|
|
d0e6f9c047 | ||
|
|
1298d6f5c7 | ||
|
|
df67dd1728 | ||
|
|
559cd902e1 | ||
|
|
c44fee1f16 |
35
.travis.yml
35
.travis.yml
@@ -1,14 +1,29 @@
|
||||
language: python
|
||||
before_install: openssl version
|
||||
env:
|
||||
- WITH_REQUESTS="2.x"
|
||||
- WITH_REQUESTS="1.x"
|
||||
- WITH_REQUESTS="False"
|
||||
global:
|
||||
- secure: AifoKzwhjV94cmcQZrdQmqRu/9rkZZvWpwBv1daeAQpLOKFPGsOm3D+x2cSw9+iCfkgDZDfqQVv1kCaFVxTll8v8jTq5SJdqEY0NmGWbj/UkNtShh609oRDsuzLxAEwtVKYjf/h8K2BRea+bl1tGkwZ2vtmYS6dxNlAijjWOfds=
|
||||
- secure: LBSEg/gMj4u4Hrpo3zs6Y/1mTpd2RtcN49mZIFgTdbJ9IhpiNPqcEt647Lz94F9Eses2x2WbNuKqZKZZReY7QLbEzU1m0nN5jlaKrjcG5NR5clNABfFFyhgc0jBikyS4abAG8jc2efeaTrFuQwdoF4sE8YiVrkiVj2X5Xoi6sBk=
|
||||
matrix:
|
||||
- WITH_LIB="requests2.x"
|
||||
- WITH_LIB="requests1.x"
|
||||
- WITH_LIB="httplib2"
|
||||
- WITH_LIB="boto"
|
||||
matrix:
|
||||
allow_failures:
|
||||
- env: WITH_LIB="boto"
|
||||
exclude:
|
||||
- env: WITH_LIB="boto"
|
||||
python: 3.3
|
||||
python:
|
||||
- 2.6
|
||||
- 2.7
|
||||
install:
|
||||
- pip install PyYAML pytest --use-mirrors
|
||||
- if [ $WITH_REQUESTS = "1.x" ] ; then pip install requests==1.2.3; fi
|
||||
- if [ $WITH_REQUESTS = "2.x" ] ; then pip install requests; fi
|
||||
script: python setup.py test
|
||||
- 2.6
|
||||
- 2.7
|
||||
- 3.3
|
||||
- pypy
|
||||
install:
|
||||
- pip install PyYAML pytest --use-mirrors
|
||||
- if [ $WITH_LIB = "requests1.x" ] ; then pip install requests==1.2.3; fi
|
||||
- if [ $WITH_LIB = "requests2.x" ] ; then pip install requests; fi
|
||||
- if [ $WITH_LIB = "httplib2" ] ; then pip install httplib2; fi
|
||||
- if [ $WITH_LIB = "boto" ] ; then pip install boto; fi
|
||||
script: python setup.py test
|
||||
|
||||
67
README.md
67
README.md
@@ -1,4 +1,4 @@
|
||||
#VCR.py
|
||||
# VCR.py
|
||||
|
||||

|
||||
|
||||
@@ -6,7 +6,7 @@ This is a Python version of [Ruby's VCR library](https://github.com/myronmarston
|
||||
|
||||
[](http://travis-ci.org/kevin1024/vcrpy)
|
||||
|
||||
##What it does
|
||||
## What it does
|
||||
Simplify and speed up testing HTTP by recording all HTTP interactions and saving them to
|
||||
"cassette" files, which are yaml files containing the contents of your
|
||||
requests and responses. Then when you run your tests again, they all
|
||||
@@ -17,12 +17,18 @@ If the server you are testing against ever changes its API, all you need
|
||||
to do is delete your existing cassette files, and run your tests again.
|
||||
All of the mocked responses will be updated with the new API.
|
||||
|
||||
##Compatibility Notes
|
||||
This should work with Python 2.6 and 2.7, and [pypy](http://pypy.org).
|
||||
## Compatibility Notes
|
||||
VCR.py supports Python 2.6 and 2.7, 3.3, and [pypy](http://pypy.org).
|
||||
|
||||
Currently I've only tested this with urllib2, urllib3, and requests. It's known to *NOT WORK* with urllib.
|
||||
The following http libraries are supported:
|
||||
|
||||
##Usage
|
||||
* urllib2
|
||||
* http.client (python3)
|
||||
* requests (both 1.x and 2.x versions)
|
||||
* httplib2
|
||||
* boto
|
||||
|
||||
## Usage
|
||||
```python
|
||||
import vcr
|
||||
import urllib2
|
||||
@@ -32,7 +38,7 @@ with vcr.use_cassette('fixtures/vcr_cassettes/synopsis.yaml'):
|
||||
assert 'Example domains' in response
|
||||
```
|
||||
|
||||
Run this test once, and VCR.py will record the http request to
|
||||
Run this test once, and VCR.py will record the HTTP request to
|
||||
`fixtures/vcr_cassettes/synopsis.yml`. Run it again, and VCR.py will replay the
|
||||
response from iana.org when the http request is made. This test is now fast (no
|
||||
real HTTP requests are made anymore), deterministic (the test will continue to
|
||||
@@ -42,7 +48,7 @@ real request).
|
||||
|
||||
You can also use VCR.py as a decorator. The same request above would look like this:
|
||||
|
||||
```
|
||||
```python
|
||||
@vcr.use_cassette('fixtures/vcr_cassettes/synopsis.yaml'):
|
||||
def test_iana():
|
||||
response = urllib2.urlopen('http://www.iana.org/domains/reserved').read()
|
||||
@@ -54,7 +60,7 @@ All of the parameters and configuration works the same for the decorator version
|
||||
## Configuration
|
||||
|
||||
If you don't like VCR's defaults, you can set options by instantiating a
|
||||
VCR class and setting the options on it.
|
||||
`VCR` class and setting the options on it.
|
||||
|
||||
```python
|
||||
|
||||
@@ -164,7 +170,7 @@ with vcr.use_cassette('fixtures/vcr_cassettes/synopsis.yaml') as cass:
|
||||
assert cass.requests[0].url == 'http://www.zombo.com/'
|
||||
```
|
||||
|
||||
The Cassette object exposes the following properties which I consider
|
||||
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
|
||||
@@ -175,7 +181,7 @@ part of the API. The fields are as follows:
|
||||
played back
|
||||
* `responses_of(request)`: Access the responses that match a given request
|
||||
|
||||
The Request object has the following properties
|
||||
The `Request` object has the following properties
|
||||
|
||||
* `URL`: The full url of the request, including the protocol. Example: "http://www.google.com/"
|
||||
* `path`: The path of the request. For example "/" or "/home.html"
|
||||
@@ -228,8 +234,8 @@ Create your own method with the following signature
|
||||
def my_matcher(r1, r2):
|
||||
```
|
||||
|
||||
Your method receives the two requests and must return True if they
|
||||
match, False if they don't.
|
||||
Your method receives the two requests and must return `True` if they
|
||||
match, `False` if they don't.
|
||||
|
||||
Finally, register your method with VCR to use your
|
||||
new request matcher.
|
||||
@@ -255,19 +261,27 @@ with my_vcr.use_cassette('test.yml'):
|
||||
|
||||
```
|
||||
|
||||
##Installation
|
||||
## Installation
|
||||
|
||||
VCR.py is a package on PyPI, so you can `pip install vcrpy` (first you may need to `brew install libyaml` [[Homebrew](http://mxcl.github.com/homebrew/)])
|
||||
|
||||
##Ruby VCR compatibility
|
||||
## Ruby VCR compatibility
|
||||
I'm not trying to match the format of the Ruby VCR YAML files. Cassettes generated by
|
||||
Ruby's VCR are not compatible with VCR.py.
|
||||
|
||||
##Known Issues
|
||||
This library is a work in progress, so the API might change on you.
|
||||
There are probably some [bugs](https://github.com/kevin1024/vcrpy/issues?labels=bug&page=1&state=open) floating around too.
|
||||
## Running VCR's test suite
|
||||
|
||||
##Changelog
|
||||
The tests are all run automatically on [Travis CI](https://travis-ci.org/kevin1024/vcrpy), but you can also run them yourself using [py.test](http://pytest.org/) and [Tox](http://tox.testrun.org/). Tox will automatically run them in all environments VCR.py supports. The test suite is pretty big and slow, but you can tell tox to only run specific tests like this:
|
||||
|
||||
`tox -e py27requests -- -v -k "'test_status_code or test_gzip'"`
|
||||
|
||||
This will run only tests that look like `test_status_code` or `test_gzip` in the test suite, and only in the python 2.7 environment that has `requests` 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 don't have to worry about them failing if you submit a pull request.
|
||||
|
||||
|
||||
## Changelog
|
||||
* 0.7.0: VCR.py now supports Python 3! (thanks @asundg) Also I refactored the stub connections quite a bit to add support for the putrequest and putheader calls. This version also adds support for httplib2 (thanks @nilp0inter). I have added a couple tests for bobo since it is an http client in its own right. Finally, this version includes a fix for a bug where requests wasn't being patched properly (thanks @msabramo).
|
||||
* 0.6.0: Store response headers as a list since a HTTP response can have the same header twice (happens with set-cookie sometimes). This has the added benefit of preserving the order of headers. Thanks @smallcode for the bug report leading to this change. I have made an effort to ensure backwards compatibility with the old cassettes' header storage mechanism, but if you want to upgrade to the new header storage, you should delete your cassettes and re-record them. Also this release adds better error messages (thanks @msabramo) and adds support for using VCR as a decorator (thanks @smallcode for the motivation)
|
||||
* 0.5.0: Change the `response_of` method to `responses_of` since cassettes can now contain more than one response for a request. Since this changes the API, I'm bumping the version. Also includes 2 bugfixes: a better error message when attempting to overwrite a cassette file, and a fix for a bug with requests sessions (thanks @msabramo)
|
||||
* 0.4.0: Change default request recording behavior for multiple requests. If you make the same request multiple times to the same URL, the response might be different each time (maybe the response has a timestamp in it or something), so this will make the same request multiple times and save them all. Then, when you are replaying the cassette, the responses will be played back in the same order in which they were received. If you were making multiple requests to the same URL in a cassette before version 0.4.0, you might need to regenerate your cassette files. Also, removes support for the cassette.play_count counter API, since individual requests aren't unique anymore. A cassette might contain the same request several times. Also removes secure overwrite feature since that was breaking overwriting files in Windows, and fixes a bug preventing request's automatic body decompression from working.
|
||||
@@ -306,18 +320,5 @@ There are probably some [bugs](https://github.com/kevin1024/vcrpy/issues?labels=
|
||||
* 0.0.3: Add support for requests 1.2.3. Support for older versions of requests dropped (thanks @vitormazzi and @bryanhelmig)
|
||||
* 0.0.2: Add support for requests / urllib3
|
||||
* 0.0.1: Initial Release
|
||||
|
||||
##Similar libraries in Python
|
||||
Neither of these really implement the API I want, but I have cribbed some code
|
||||
from them.
|
||||
* https://github.com/bbangert/Dalton
|
||||
* https://github.com/storborg/replaylib
|
||||
|
||||
These were created after I created VCR.py but do something similar:
|
||||
|
||||
* https://github.com/gabrielfalcao/HTTPretty
|
||||
* https://github.com/kanzure/python-requestions
|
||||
* https://github.com/uber/cassette
|
||||
|
||||
#License
|
||||
# License
|
||||
This library uses the MIT license. See [LICENSE.txt](LICENSE.txt) for more details
|
||||
|
||||
5
setup.py
5
setup.py
@@ -19,7 +19,7 @@ class PyTest(TestCommand):
|
||||
sys.exit(errno)
|
||||
|
||||
setup(name='vcrpy',
|
||||
version='0.6.0',
|
||||
version='0.7.0',
|
||||
description="Automatically mock your HTTP interactions to simplify and speed up testing",
|
||||
author='Kevin McCarthy',
|
||||
author_email='me@kevinmccarthy.org',
|
||||
@@ -37,7 +37,7 @@ setup(name='vcrpy',
|
||||
'vcr.compat': 'vcr/compat',
|
||||
'vcr.persisters': 'vcr/persisters',
|
||||
},
|
||||
install_requires=['PyYAML','contextdecorator'],
|
||||
install_requires=['PyYAML','contextdecorator','six'],
|
||||
license='MIT',
|
||||
tests_require=['pytest','mock'],
|
||||
cmdclass={'test': PyTest},
|
||||
@@ -46,6 +46,7 @@ setup(name='vcrpy',
|
||||
'Environment :: Console',
|
||||
'Intended Audience :: Developers',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Topic :: Software Development :: Testing',
|
||||
'Topic :: Internet :: WWW/HTTP',
|
||||
'License :: OSI Approved :: MIT License',
|
||||
|
||||
@@ -13,7 +13,7 @@ def assert_cassette_has_one_response(cass):
|
||||
|
||||
def assert_is_json(a_string):
|
||||
try:
|
||||
json.loads(a_string)
|
||||
json.loads(a_string.decode('utf-8'))
|
||||
except Exception:
|
||||
assert False
|
||||
assert True
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
# External imports
|
||||
import os
|
||||
import urllib2
|
||||
from six.moves.urllib.request import urlopen
|
||||
|
||||
# Internal imports
|
||||
import vcr
|
||||
@@ -16,7 +16,7 @@ def test_nonexistent_directory(tmpdir):
|
||||
|
||||
# Run VCR to create dir and cassette file
|
||||
with vcr.use_cassette(str(tmpdir.join('nonexistent', 'cassette.yml'))):
|
||||
urllib2.urlopen('http://httpbin.org/').read()
|
||||
urlopen('http://httpbin.org/').read()
|
||||
|
||||
# This should have made the file and the directory
|
||||
assert os.path.exists(str(tmpdir.join('nonexistent', 'cassette.yml')))
|
||||
@@ -25,11 +25,11 @@ def test_nonexistent_directory(tmpdir):
|
||||
def test_unpatch(tmpdir):
|
||||
'''Ensure that our cassette gets unpatched when we're done'''
|
||||
with vcr.use_cassette(str(tmpdir.join('unpatch.yaml'))) as cass:
|
||||
urllib2.urlopen('http://httpbin.org/').read()
|
||||
urlopen('http://httpbin.org/').read()
|
||||
|
||||
# Make the same request, and assert that we haven't served any more
|
||||
# requests out of cache
|
||||
urllib2.urlopen('http://httpbin.org/').read()
|
||||
urlopen('http://httpbin.org/').read()
|
||||
assert cass.play_count == 0
|
||||
|
||||
|
||||
@@ -38,10 +38,10 @@ def test_basic_use(tmpdir):
|
||||
Copied from the docs
|
||||
'''
|
||||
with vcr.use_cassette('fixtures/vcr_cassettes/synopsis.yaml'):
|
||||
response = urllib2.urlopen(
|
||||
response = urlopen(
|
||||
'http://www.iana.org/domains/reserved'
|
||||
).read()
|
||||
assert 'Example domains' in response
|
||||
assert b'Example domains' in response
|
||||
|
||||
|
||||
def test_basic_json_use(tmpdir):
|
||||
@@ -50,8 +50,8 @@ def test_basic_json_use(tmpdir):
|
||||
'''
|
||||
test_fixture = 'fixtures/vcr_cassettes/synopsis.json'
|
||||
with vcr.use_cassette(test_fixture, serializer='json'):
|
||||
response = urllib2.urlopen('http://httpbin.org/').read()
|
||||
assert 'difficult sometimes' in response
|
||||
response = urlopen('http://httpbin.org/').read()
|
||||
assert b'difficult sometimes' in response
|
||||
|
||||
|
||||
def test_patched_content(tmpdir):
|
||||
@@ -60,16 +60,16 @@ def test_patched_content(tmpdir):
|
||||
request
|
||||
'''
|
||||
with vcr.use_cassette(str(tmpdir.join('synopsis.yaml'))) as cass:
|
||||
response = urllib2.urlopen('http://httpbin.org/').read()
|
||||
response = urlopen('http://httpbin.org/').read()
|
||||
assert cass.play_count == 0
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('synopsis.yaml'))) as cass:
|
||||
response2 = urllib2.urlopen('http://httpbin.org/').read()
|
||||
response2 = urlopen('http://httpbin.org/').read()
|
||||
assert cass.play_count == 1
|
||||
cass._save(force=True)
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('synopsis.yaml'))) as cass:
|
||||
response3 = urllib2.urlopen('http://httpbin.org/').read()
|
||||
response3 = urlopen('http://httpbin.org/').read()
|
||||
assert cass.play_count == 1
|
||||
|
||||
assert response == response2
|
||||
@@ -85,16 +85,16 @@ def test_patched_content_json(tmpdir):
|
||||
testfile = str(tmpdir.join('synopsis.json'))
|
||||
|
||||
with vcr.use_cassette(testfile) as cass:
|
||||
response = urllib2.urlopen('http://httpbin.org/').read()
|
||||
response = urlopen('http://httpbin.org/').read()
|
||||
assert cass.play_count == 0
|
||||
|
||||
with vcr.use_cassette(testfile) as cass:
|
||||
response2 = urllib2.urlopen('http://httpbin.org/').read()
|
||||
response2 = urlopen('http://httpbin.org/').read()
|
||||
assert cass.play_count == 1
|
||||
cass._save(force=True)
|
||||
|
||||
with vcr.use_cassette(testfile) as cass:
|
||||
response3 = urllib2.urlopen('http://httpbin.org/').read()
|
||||
response3 = urlopen('http://httpbin.org/').read()
|
||||
assert cass.play_count == 1
|
||||
|
||||
assert response == response2
|
||||
|
||||
41
tests/integration/test_boto.py
Normal file
41
tests/integration/test_boto.py
Normal file
@@ -0,0 +1,41 @@
|
||||
import pytest
|
||||
boto = pytest.importorskip("boto")
|
||||
from boto.s3.connection import S3Connection
|
||||
from boto.s3.key import Key
|
||||
import vcr
|
||||
|
||||
def test_boto_without_vcr():
|
||||
s3_conn = S3Connection()
|
||||
s3_bucket = s3_conn.get_bucket('boto-demo-1394171994') # a bucket you can access
|
||||
k = Key(s3_bucket)
|
||||
k.key = 'test.txt'
|
||||
k.set_contents_from_string('hello world i am a string')
|
||||
|
||||
def test_boto_medium_difficulty(tmpdir):
|
||||
s3_conn = S3Connection()
|
||||
s3_bucket = s3_conn.get_bucket('boto-demo-1394171994') # a bucket you can access
|
||||
with vcr.use_cassette(str(tmpdir.join('boto-medium.yml'))) as cass:
|
||||
k = Key(s3_bucket)
|
||||
k.key = 'test.txt'
|
||||
k.set_contents_from_string('hello world i am a string')
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('boto-medium.yml'))) as cass:
|
||||
k = Key(s3_bucket)
|
||||
k.key = 'test.txt'
|
||||
k.set_contents_from_string('hello world i am a string')
|
||||
|
||||
|
||||
def test_boto_hardcore_mode(tmpdir):
|
||||
with vcr.use_cassette(str(tmpdir.join('boto-hardcore.yml'))) as cass:
|
||||
s3_conn = S3Connection()
|
||||
s3_bucket = s3_conn.get_bucket('boto-demo-1394171994') # a bucket you can access
|
||||
k = Key(s3_bucket)
|
||||
k.key = 'test.txt'
|
||||
k.set_contents_from_string('hello world i am a string')
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('boto-hardcore.yml'))) as cass:
|
||||
s3_conn = S3Connection()
|
||||
s3_bucket = s3_conn.get_bucket('boto-demo-1394171994') # a bucket you can access
|
||||
k = Key(s3_bucket)
|
||||
k.key = 'test.txt'
|
||||
k.set_contents_from_string('hello world i am a string')
|
||||
@@ -1,8 +1,8 @@
|
||||
import os
|
||||
import json
|
||||
import urllib2
|
||||
import pytest
|
||||
import vcr
|
||||
from six.moves.urllib.request import urlopen
|
||||
|
||||
|
||||
def test_set_serializer_default_config(tmpdir):
|
||||
@@ -10,7 +10,7 @@ def test_set_serializer_default_config(tmpdir):
|
||||
|
||||
with my_vcr.use_cassette(str(tmpdir.join('test.json'))):
|
||||
assert my_vcr.serializer == 'json'
|
||||
urllib2.urlopen('http://httpbin.org/get')
|
||||
urlopen('http://httpbin.org/get')
|
||||
|
||||
with open(str(tmpdir.join('test.json'))) as f:
|
||||
assert json.loads(f.read())
|
||||
@@ -20,7 +20,7 @@ def test_default_set_cassette_library_dir(tmpdir):
|
||||
my_vcr = vcr.VCR(cassette_library_dir=str(tmpdir.join('subdir')))
|
||||
|
||||
with my_vcr.use_cassette('test.json'):
|
||||
urllib2.urlopen('http://httpbin.org/get')
|
||||
urlopen('http://httpbin.org/get')
|
||||
|
||||
assert os.path.exists(str(tmpdir.join('subdir').join('test.json')))
|
||||
|
||||
@@ -31,7 +31,7 @@ def test_override_set_cassette_library_dir(tmpdir):
|
||||
cld = str(tmpdir.join('subdir2'))
|
||||
|
||||
with my_vcr.use_cassette('test.json', cassette_library_dir=cld):
|
||||
urllib2.urlopen('http://httpbin.org/get')
|
||||
urlopen('http://httpbin.org/get')
|
||||
|
||||
assert os.path.exists(str(tmpdir.join('subdir2').join('test.json')))
|
||||
assert not os.path.exists(str(tmpdir.join('subdir').join('test.json')))
|
||||
@@ -41,10 +41,10 @@ def test_override_match_on(tmpdir):
|
||||
my_vcr = vcr.VCR(match_on=['method'])
|
||||
|
||||
with my_vcr.use_cassette(str(tmpdir.join('test.json'))):
|
||||
urllib2.urlopen('http://httpbin.org/')
|
||||
urlopen('http://httpbin.org/')
|
||||
|
||||
with my_vcr.use_cassette(str(tmpdir.join('test.json'))) as cass:
|
||||
urllib2.urlopen('http://httpbin.org/get')
|
||||
urlopen('http://httpbin.org/get')
|
||||
|
||||
assert len(cass) == 1
|
||||
assert cass.play_count == 1
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
|
||||
# External imports
|
||||
import os
|
||||
import urllib2
|
||||
import time
|
||||
from six.moves.urllib.request import urlopen
|
||||
|
||||
# Internal imports
|
||||
import vcr
|
||||
@@ -17,12 +17,12 @@ def test_disk_saver_nowrite(tmpdir):
|
||||
'''
|
||||
fname = str(tmpdir.join('synopsis.yaml'))
|
||||
with vcr.use_cassette(fname) as cass:
|
||||
urllib2.urlopen('http://www.iana.org/domains/reserved').read()
|
||||
urlopen('http://www.iana.org/domains/reserved').read()
|
||||
assert cass.play_count == 0
|
||||
last_mod = os.path.getmtime(fname)
|
||||
|
||||
with vcr.use_cassette(fname) as cass:
|
||||
urllib2.urlopen('http://www.iana.org/domains/reserved').read()
|
||||
urlopen('http://www.iana.org/domains/reserved').read()
|
||||
assert cass.play_count == 1
|
||||
assert cass.dirty is False
|
||||
last_mod2 = os.path.getmtime(fname)
|
||||
@@ -37,7 +37,7 @@ def test_disk_saver_write(tmpdir):
|
||||
'''
|
||||
fname = str(tmpdir.join('synopsis.yaml'))
|
||||
with vcr.use_cassette(fname) as cass:
|
||||
urllib2.urlopen('http://www.iana.org/domains/reserved').read()
|
||||
urlopen('http://www.iana.org/domains/reserved').read()
|
||||
assert cass.play_count == 0
|
||||
last_mod = os.path.getmtime(fname)
|
||||
|
||||
@@ -46,8 +46,8 @@ def test_disk_saver_write(tmpdir):
|
||||
time.sleep(1)
|
||||
|
||||
with vcr.use_cassette(fname, record_mode='any') as cass:
|
||||
urllib2.urlopen('http://www.iana.org/domains/reserved').read()
|
||||
urllib2.urlopen('http://httpbin.org/').read()
|
||||
urlopen('http://www.iana.org/domains/reserved').read()
|
||||
urlopen('http://httpbin.org/').read()
|
||||
assert cass.play_count == 1
|
||||
assert cass.dirty
|
||||
last_mod2 = os.path.getmtime(fname)
|
||||
|
||||
140
tests/integration/test_httplib2.py
Normal file
140
tests/integration/test_httplib2.py
Normal file
@@ -0,0 +1,140 @@
|
||||
'''Integration tests with httplib2'''
|
||||
# coding=utf-8
|
||||
|
||||
# External imports
|
||||
from six.moves.urllib_parse import urlencode
|
||||
import pytest
|
||||
|
||||
# Internal imports
|
||||
import vcr
|
||||
|
||||
from assertions import assert_cassette_has_one_response
|
||||
|
||||
httplib2 = pytest.importorskip("httplib2")
|
||||
|
||||
|
||||
@pytest.fixture(params=["https", "http"])
|
||||
def scheme(request):
|
||||
"""
|
||||
Fixture that returns both http and https
|
||||
"""
|
||||
return request.param
|
||||
|
||||
|
||||
def test_response_code(scheme, tmpdir):
|
||||
'''Ensure we can read a response code from a fetch'''
|
||||
url = scheme + '://httpbin.org/'
|
||||
with vcr.use_cassette(str(tmpdir.join('atts.yaml'))) as cass:
|
||||
resp, _ = httplib2.Http().request(url)
|
||||
code = resp.status
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('atts.yaml'))) as cass:
|
||||
resp, _ = httplib2.Http().request(url)
|
||||
assert code == resp.status
|
||||
|
||||
|
||||
def test_random_body(scheme, tmpdir):
|
||||
'''Ensure we can read the content, and that it's served from cache'''
|
||||
url = scheme + '://httpbin.org/bytes/1024'
|
||||
with vcr.use_cassette(str(tmpdir.join('body.yaml'))) as cass:
|
||||
_, content = httplib2.Http().request(url)
|
||||
body = content
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('body.yaml'))) as cass:
|
||||
_, content = httplib2.Http().request(url)
|
||||
assert body == content
|
||||
|
||||
|
||||
def test_response_headers(scheme, tmpdir):
|
||||
'''Ensure we can get information from the response'''
|
||||
url = scheme + '://httpbin.org/'
|
||||
with vcr.use_cassette(str(tmpdir.join('headers.yaml'))) as cass:
|
||||
resp, _ = httplib2.Http().request(url)
|
||||
headers = resp.items()
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('headers.yaml'))) as cass:
|
||||
resp, _ = httplib2.Http().request(url)
|
||||
assert headers == resp.items()
|
||||
|
||||
|
||||
def test_multiple_requests(scheme, tmpdir):
|
||||
'''Ensure that we can cache multiple requests'''
|
||||
urls = [
|
||||
scheme + '://httpbin.org/',
|
||||
scheme + '://httpbin.org/',
|
||||
scheme + '://httpbin.org/get',
|
||||
scheme + '://httpbin.org/bytes/1024'
|
||||
]
|
||||
with vcr.use_cassette(str(tmpdir.join('multiple.yaml'))) as cass:
|
||||
[httplib2.Http().request(url) for url in urls]
|
||||
assert len(cass) == len(urls)
|
||||
|
||||
|
||||
def test_get_data(scheme, tmpdir):
|
||||
'''Ensure that it works with query data'''
|
||||
data = urlencode({'some': 1, 'data': 'here'})
|
||||
url = scheme + '://httpbin.org/get?' + data
|
||||
with vcr.use_cassette(str(tmpdir.join('get_data.yaml'))) as cass:
|
||||
_, res1 = httplib2.Http().request(url)
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('get_data.yaml'))) as cass:
|
||||
_, res2 = httplib2.Http().request(url)
|
||||
|
||||
assert res1 == res2
|
||||
|
||||
|
||||
def test_post_data(scheme, tmpdir):
|
||||
'''Ensure that it works when posting data'''
|
||||
data = urlencode({'some': 1, 'data': 'here'})
|
||||
url = scheme + '://httpbin.org/post'
|
||||
with vcr.use_cassette(str(tmpdir.join('post_data.yaml'))) as cass:
|
||||
_, res1 = httplib2.Http().request(url, "POST", data)
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('post_data.yaml'))) as cass:
|
||||
_, res2 = httplib2.Http().request(url, "POST", data)
|
||||
|
||||
assert res1 == res2
|
||||
assert_cassette_has_one_response(cass)
|
||||
|
||||
|
||||
def test_post_unicode_data(scheme, tmpdir):
|
||||
'''Ensure that it works when posting unicode data'''
|
||||
data = urlencode({'snowman': u'☃'.encode('utf-8')})
|
||||
url = scheme + '://httpbin.org/post'
|
||||
with vcr.use_cassette(str(tmpdir.join('post_data.yaml'))) as cass:
|
||||
_, res1 = httplib2.Http().request(url, "POST", data)
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('post_data.yaml'))) as cass:
|
||||
_, res2 = httplib2.Http().request(url, "POST", data)
|
||||
|
||||
assert res1 == res2
|
||||
assert_cassette_has_one_response(cass)
|
||||
|
||||
|
||||
def test_cross_scheme(tmpdir):
|
||||
'''Ensure that requests between schemes are treated separately'''
|
||||
# First fetch a url under https, and then again under https and then
|
||||
# ensure that we haven't served anything out of cache, and we have two
|
||||
# requests / response pairs in the cassette
|
||||
with vcr.use_cassette(str(tmpdir.join('cross_scheme.yaml'))) as cass:
|
||||
httplib2.Http().request('https://httpbin.org/')
|
||||
httplib2.Http().request('http://httpbin.org/')
|
||||
assert len(cass) == 2
|
||||
assert cass.play_count == 0
|
||||
|
||||
|
||||
def test_decorator(scheme, tmpdir):
|
||||
'''Test the decorator version of VCR.py'''
|
||||
url = scheme + '://httpbin.org/'
|
||||
|
||||
@vcr.use_cassette(str(tmpdir.join('atts.yaml')))
|
||||
def inner1():
|
||||
resp, _ = httplib2.Http().request(url)
|
||||
return resp['status']
|
||||
|
||||
@vcr.use_cassette(str(tmpdir.join('atts.yaml')))
|
||||
def inner2():
|
||||
resp, _ = httplib2.Http().request(url)
|
||||
return resp['status']
|
||||
|
||||
assert inner1() == inner2()
|
||||
@@ -1,6 +1,6 @@
|
||||
import pytest
|
||||
from urllib2 import urlopen
|
||||
import vcr
|
||||
from six.moves.urllib.request import urlopen
|
||||
|
||||
|
||||
def test_making_extra_request_raises_exception(tmpdir):
|
||||
|
||||
@@ -1,46 +1,46 @@
|
||||
import os
|
||||
import urllib2
|
||||
import pytest
|
||||
import vcr
|
||||
from six.moves.urllib.request import urlopen
|
||||
|
||||
|
||||
def test_once_record_mode(tmpdir):
|
||||
testfile = str(tmpdir.join('recordmode.yml'))
|
||||
with vcr.use_cassette(testfile, record_mode="once"):
|
||||
# cassette file doesn't exist, so create.
|
||||
response = urllib2.urlopen('http://httpbin.org/').read()
|
||||
response = urlopen('http://httpbin.org/').read()
|
||||
|
||||
with vcr.use_cassette(testfile, record_mode="once") as cass:
|
||||
# make the same request again
|
||||
response = urllib2.urlopen('http://httpbin.org/').read()
|
||||
response = urlopen('http://httpbin.org/').read()
|
||||
|
||||
# the first time, it's played from the cassette.
|
||||
# but, try to access something else from the same cassette, and an
|
||||
# exception is raised.
|
||||
with pytest.raises(Exception):
|
||||
response = urllib2.urlopen('http://httpbin.org/get').read()
|
||||
response = urlopen('http://httpbin.org/get').read()
|
||||
|
||||
|
||||
def test_once_record_mode_two_times(tmpdir):
|
||||
testfile = str(tmpdir.join('recordmode.yml'))
|
||||
with vcr.use_cassette(testfile, record_mode="once"):
|
||||
# get two of the same file
|
||||
response1 = urllib2.urlopen('http://httpbin.org/').read()
|
||||
response2 = urllib2.urlopen('http://httpbin.org/').read()
|
||||
response1 = urlopen('http://httpbin.org/').read()
|
||||
response2 = urlopen('http://httpbin.org/').read()
|
||||
|
||||
with vcr.use_cassette(testfile, record_mode="once") as cass:
|
||||
# do it again
|
||||
response = urllib2.urlopen('http://httpbin.org/').read()
|
||||
response = urllib2.urlopen('http://httpbin.org/').read()
|
||||
response = urlopen('http://httpbin.org/').read()
|
||||
response = urlopen('http://httpbin.org/').read()
|
||||
|
||||
|
||||
def test_once_mode_three_times(tmpdir):
|
||||
testfile = str(tmpdir.join('recordmode.yml'))
|
||||
with vcr.use_cassette(testfile, record_mode="once"):
|
||||
# get three of the same file
|
||||
response1 = urllib2.urlopen('http://httpbin.org/').read()
|
||||
response2 = urllib2.urlopen('http://httpbin.org/').read()
|
||||
response2 = urllib2.urlopen('http://httpbin.org/').read()
|
||||
response1 = urlopen('http://httpbin.org/').read()
|
||||
response2 = urlopen('http://httpbin.org/').read()
|
||||
response2 = urlopen('http://httpbin.org/').read()
|
||||
|
||||
|
||||
def test_new_episodes_record_mode(tmpdir):
|
||||
@@ -48,15 +48,15 @@ def test_new_episodes_record_mode(tmpdir):
|
||||
|
||||
with vcr.use_cassette(testfile, record_mode="new_episodes"):
|
||||
# cassette file doesn't exist, so create.
|
||||
response = urllib2.urlopen('http://httpbin.org/').read()
|
||||
response = urlopen('http://httpbin.org/').read()
|
||||
|
||||
with vcr.use_cassette(testfile, record_mode="new_episodes") as cass:
|
||||
# make the same request again
|
||||
response = urllib2.urlopen('http://httpbin.org/').read()
|
||||
response = urlopen('http://httpbin.org/').read()
|
||||
|
||||
# in the "new_episodes" record mode, we can add more requests to
|
||||
# a cassette without repurcussions.
|
||||
response = urllib2.urlopen('http://httpbin.org/get').read()
|
||||
response = urlopen('http://httpbin.org/get').read()
|
||||
|
||||
# the first interaction was not re-recorded, but the second was added
|
||||
assert cass.play_count == 1
|
||||
@@ -67,15 +67,15 @@ def test_all_record_mode(tmpdir):
|
||||
|
||||
with vcr.use_cassette(testfile, record_mode="all"):
|
||||
# cassette file doesn't exist, so create.
|
||||
response = urllib2.urlopen('http://httpbin.org/').read()
|
||||
response = urlopen('http://httpbin.org/').read()
|
||||
|
||||
with vcr.use_cassette(testfile, record_mode="all") as cass:
|
||||
# make the same request again
|
||||
response = urllib2.urlopen('http://httpbin.org/').read()
|
||||
response = urlopen('http://httpbin.org/').read()
|
||||
|
||||
# in the "all" record mode, we can add more requests to
|
||||
# a cassette without repurcussions.
|
||||
response = urllib2.urlopen('http://httpbin.org/get').read()
|
||||
response = urlopen('http://httpbin.org/get').read()
|
||||
|
||||
# The cassette was never actually played, even though it existed.
|
||||
# that's because, in "all" mode, the requests all go directly to
|
||||
@@ -89,7 +89,7 @@ def test_none_record_mode(tmpdir):
|
||||
testfile = str(tmpdir.join('recordmode.yml'))
|
||||
with vcr.use_cassette(testfile, record_mode="none"):
|
||||
with pytest.raises(Exception):
|
||||
response = urllib2.urlopen('http://httpbin.org/').read()
|
||||
response = urlopen('http://httpbin.org/').read()
|
||||
|
||||
|
||||
def test_none_record_mode_with_existing_cassette(tmpdir):
|
||||
@@ -97,12 +97,12 @@ def test_none_record_mode_with_existing_cassette(tmpdir):
|
||||
testfile = str(tmpdir.join('recordmode.yml'))
|
||||
|
||||
with vcr.use_cassette(testfile, record_mode="all"):
|
||||
response = urllib2.urlopen('http://httpbin.org/').read()
|
||||
response = urlopen('http://httpbin.org/').read()
|
||||
|
||||
# play from cassette file
|
||||
with vcr.use_cassette(testfile, record_mode="none") as cass:
|
||||
response = urllib2.urlopen('http://httpbin.org/').read()
|
||||
response = urlopen('http://httpbin.org/').read()
|
||||
assert cass.play_count == 1
|
||||
# but if I try to hit the net, raise an exception.
|
||||
with pytest.raises(Exception):
|
||||
response = urllib2.urlopen('http://httpbin.org/get').read()
|
||||
response = urlopen('http://httpbin.org/get').read()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import urllib2
|
||||
import vcr
|
||||
from six.moves.urllib.request import urlopen
|
||||
|
||||
|
||||
def true_matcher(r1, r2):
|
||||
@@ -16,13 +16,13 @@ def test_registered_serializer_true_matcher(tmpdir):
|
||||
testfile = str(tmpdir.join('test.yml'))
|
||||
with my_vcr.use_cassette(testfile, match_on=['true']) as cass:
|
||||
# These 2 different urls are stored as the same request
|
||||
urllib2.urlopen('http://httpbin.org/')
|
||||
urllib2.urlopen('https://httpbin.org/get')
|
||||
urlopen('http://httpbin.org/')
|
||||
urlopen('https://httpbin.org/get')
|
||||
|
||||
with my_vcr.use_cassette(testfile, match_on=['true']) as cass:
|
||||
# I can get the response twice even though I only asked for it once
|
||||
urllib2.urlopen('http://httpbin.org/get')
|
||||
urllib2.urlopen('https://httpbin.org/get')
|
||||
urlopen('http://httpbin.org/get')
|
||||
urlopen('https://httpbin.org/get')
|
||||
|
||||
|
||||
def test_registered_serializer_false_matcher(tmpdir):
|
||||
@@ -31,6 +31,6 @@ def test_registered_serializer_false_matcher(tmpdir):
|
||||
testfile = str(tmpdir.join('test.yml'))
|
||||
with my_vcr.use_cassette(testfile, match_on=['false']) as cass:
|
||||
# These 2 different urls are stored as different requests
|
||||
urllib2.urlopen('http://httpbin.org/')
|
||||
urllib2.urlopen('https://httpbin.org/get')
|
||||
urlopen('http://httpbin.org/')
|
||||
urlopen('https://httpbin.org/get')
|
||||
assert len(cass) == 2
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import urllib2
|
||||
import vcr
|
||||
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import urllib2
|
||||
import vcr
|
||||
from six.moves.urllib.request import urlopen
|
||||
|
||||
|
||||
def test_recorded_request_url_with_redirected_request(tmpdir):
|
||||
with vcr.use_cassette(str(tmpdir.join('test.yml'))) as cass:
|
||||
assert len(cass) == 0
|
||||
urllib2.urlopen('http://httpbin.org/redirect/3')
|
||||
urlopen('http://httpbin.org/redirect/3')
|
||||
assert cass.requests[0].url == 'http://httpbin.org/redirect/3'
|
||||
assert cass.requests[3].url == 'http://httpbin.org/get'
|
||||
assert len(cass) == 4
|
||||
|
||||
@@ -3,9 +3,10 @@
|
||||
|
||||
# External imports
|
||||
import os
|
||||
import urllib2
|
||||
from urllib import urlencode
|
||||
|
||||
import pytest
|
||||
from six.moves.urllib.request import urlopen
|
||||
from six.moves.urllib_parse import urlencode
|
||||
|
||||
# Internal imports
|
||||
import vcr
|
||||
@@ -25,30 +26,30 @@ def test_response_code(scheme, tmpdir):
|
||||
'''Ensure we can read a response code from a fetch'''
|
||||
url = scheme + '://httpbin.org/'
|
||||
with vcr.use_cassette(str(tmpdir.join('atts.yaml'))) as cass:
|
||||
code = urllib2.urlopen(url).getcode()
|
||||
code = urlopen(url).getcode()
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('atts.yaml'))) as cass:
|
||||
assert code == urllib2.urlopen(url).getcode()
|
||||
assert code == urlopen(url).getcode()
|
||||
|
||||
|
||||
def test_random_body(scheme, tmpdir):
|
||||
'''Ensure we can read the content, and that it's served from cache'''
|
||||
url = scheme + '://httpbin.org/bytes/1024'
|
||||
with vcr.use_cassette(str(tmpdir.join('body.yaml'))) as cass:
|
||||
body = urllib2.urlopen(url).read()
|
||||
body = urlopen(url).read()
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('body.yaml'))) as cass:
|
||||
assert body == urllib2.urlopen(url).read()
|
||||
assert body == urlopen(url).read()
|
||||
|
||||
|
||||
def test_response_headers(scheme, tmpdir):
|
||||
'''Ensure we can get information from the response'''
|
||||
url = scheme + '://httpbin.org/'
|
||||
with vcr.use_cassette(str(tmpdir.join('headers.yaml'))) as cass:
|
||||
open1 = urllib2.urlopen(url).info().items()
|
||||
open1 = urlopen(url).info().items()
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('headers.yaml'))) as cass:
|
||||
open2 = urllib2.urlopen(url).info().items()
|
||||
open2 = urlopen(url).info().items()
|
||||
assert open1 == open2
|
||||
|
||||
|
||||
@@ -61,7 +62,7 @@ def test_multiple_requests(scheme, tmpdir):
|
||||
scheme + '://httpbin.org/bytes/1024'
|
||||
]
|
||||
with vcr.use_cassette(str(tmpdir.join('multiple.yaml'))) as cass:
|
||||
map(urllib2.urlopen, urls)
|
||||
[urlopen(url) for url in urls]
|
||||
assert len(cass) == len(urls)
|
||||
|
||||
|
||||
@@ -70,23 +71,23 @@ def test_get_data(scheme, tmpdir):
|
||||
data = urlencode({'some': 1, 'data': 'here'})
|
||||
url = scheme + '://httpbin.org/get?' + data
|
||||
with vcr.use_cassette(str(tmpdir.join('get_data.yaml'))) as cass:
|
||||
res1 = urllib2.urlopen(url).read()
|
||||
res1 = urlopen(url).read()
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('get_data.yaml'))) as cass:
|
||||
res2 = urllib2.urlopen(url).read()
|
||||
res2 = urlopen(url).read()
|
||||
|
||||
assert res1 == res2
|
||||
|
||||
|
||||
def test_post_data(scheme, tmpdir):
|
||||
'''Ensure that it works when posting data'''
|
||||
data = urlencode({'some': 1, 'data': 'here'})
|
||||
data = urlencode({'some': 1, 'data': 'here'}).encode('utf-8')
|
||||
url = scheme + '://httpbin.org/post'
|
||||
with vcr.use_cassette(str(tmpdir.join('post_data.yaml'))) as cass:
|
||||
res1 = urllib2.urlopen(url, data).read()
|
||||
res1 = urlopen(url, data).read()
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('post_data.yaml'))) as cass:
|
||||
res2 = urllib2.urlopen(url, data).read()
|
||||
res2 = urlopen(url, data).read()
|
||||
|
||||
assert res1 == res2
|
||||
assert_cassette_has_one_response(cass)
|
||||
@@ -94,12 +95,12 @@ def test_post_data(scheme, tmpdir):
|
||||
|
||||
def test_post_unicode_data(scheme, tmpdir):
|
||||
'''Ensure that it works when posting unicode data'''
|
||||
data = urlencode({'snowman': u'☃'.encode('utf-8')})
|
||||
data = urlencode({'snowman': u'☃'.encode('utf-8')}).encode('utf-8')
|
||||
url = scheme + '://httpbin.org/post'
|
||||
with vcr.use_cassette(str(tmpdir.join('post_data.yaml'))) as cass:
|
||||
res1 = urllib2.urlopen(url, data).read()
|
||||
res1 = urlopen(url, data).read()
|
||||
with vcr.use_cassette(str(tmpdir.join('post_data.yaml'))) as cass:
|
||||
res2 = urllib2.urlopen(url, data).read()
|
||||
res2 = urlopen(url, data).read()
|
||||
assert res1 == res2
|
||||
assert_cassette_has_one_response(cass)
|
||||
|
||||
@@ -110,8 +111,8 @@ def test_cross_scheme(tmpdir):
|
||||
# ensure that we haven't served anything out of cache, and we have two
|
||||
# requests / response pairs in the cassette
|
||||
with vcr.use_cassette(str(tmpdir.join('cross_scheme.yaml'))) as cass:
|
||||
urllib2.urlopen('https://httpbin.org/')
|
||||
urllib2.urlopen('http://httpbin.org/')
|
||||
urlopen('https://httpbin.org/')
|
||||
urlopen('http://httpbin.org/')
|
||||
assert len(cass) == 2
|
||||
assert cass.play_count == 0
|
||||
|
||||
@@ -121,10 +122,10 @@ def test_decorator(scheme, tmpdir):
|
||||
|
||||
@vcr.use_cassette(str(tmpdir.join('atts.yaml')))
|
||||
def inner1():
|
||||
return urllib2.urlopen(url).getcode()
|
||||
return urlopen(url).getcode()
|
||||
|
||||
@vcr.use_cassette(str(tmpdir.join('atts.yaml')))
|
||||
def inner2():
|
||||
return urllib2.urlopen(url).getcode()
|
||||
return urlopen(url).getcode()
|
||||
|
||||
assert inner1() == inner2()
|
||||
|
||||
@@ -3,7 +3,10 @@ requests = pytest.importorskip("requests")
|
||||
|
||||
import vcr
|
||||
|
||||
import httplib
|
||||
try:
|
||||
import httplib
|
||||
except ImportError:
|
||||
import http.client as httplib
|
||||
|
||||
|
||||
def test_domain_redirect():
|
||||
|
||||
75
tox.ini
75
tox.ini
@@ -4,18 +4,36 @@
|
||||
# and then run "tox" from this directory.
|
||||
|
||||
[tox]
|
||||
envlist = py26, py27, pypy, py26requests, py27requests, pypyrequests, py26oldrequests, py27oldrequests, pypyoldrequests
|
||||
envlist =
|
||||
py26
|
||||
py27
|
||||
py33
|
||||
pypy
|
||||
py26requests
|
||||
py27requests
|
||||
py33requests
|
||||
pypyrequests
|
||||
py26oldrequests
|
||||
py27oldrequests
|
||||
py33oldrequests
|
||||
pypyoldrequests
|
||||
py26httplib2
|
||||
py27httplib2
|
||||
py33httplib2
|
||||
pypyhttplib2
|
||||
|
||||
[testenv]
|
||||
commands =
|
||||
python setup.py test
|
||||
py.test {posargs}
|
||||
deps =
|
||||
mock
|
||||
pytest
|
||||
PyYAML
|
||||
|
||||
[testenv:py26oldrequests]
|
||||
basepython = python2.6
|
||||
deps =
|
||||
mock
|
||||
pytest
|
||||
PyYAML
|
||||
requests==1.2.3
|
||||
@@ -23,6 +41,15 @@ deps =
|
||||
[testenv:py27oldrequests]
|
||||
basepython = python2.7
|
||||
deps =
|
||||
mock
|
||||
pytest
|
||||
PyYAML
|
||||
requests==1.2.3
|
||||
|
||||
[testenv:py33oldrequests]
|
||||
basepython = python3.3
|
||||
deps =
|
||||
mock
|
||||
pytest
|
||||
PyYAML
|
||||
requests==1.2.3
|
||||
@@ -30,6 +57,7 @@ deps =
|
||||
[testenv:pypyoldrequests]
|
||||
basepython = pypy
|
||||
deps =
|
||||
mock
|
||||
pytest
|
||||
PyYAML
|
||||
requests==1.2.3
|
||||
@@ -37,6 +65,7 @@ deps =
|
||||
[testenv:py26requests]
|
||||
basepython = python2.6
|
||||
deps =
|
||||
mock
|
||||
pytest
|
||||
PyYAML
|
||||
requests
|
||||
@@ -44,6 +73,15 @@ deps =
|
||||
[testenv:py27requests]
|
||||
basepython = python2.7
|
||||
deps =
|
||||
mock
|
||||
pytest
|
||||
PyYAML
|
||||
requests
|
||||
|
||||
[testenv:py33requests]
|
||||
basepython = python3.3
|
||||
deps =
|
||||
mock
|
||||
pytest
|
||||
PyYAML
|
||||
requests
|
||||
@@ -51,6 +89,39 @@ deps =
|
||||
[testenv:pypyrequests]
|
||||
basepython = pypy
|
||||
deps =
|
||||
mock
|
||||
pytest
|
||||
PyYAML
|
||||
requests
|
||||
|
||||
[testenv:py26httplib2]
|
||||
basepython = python2.6
|
||||
deps =
|
||||
mock
|
||||
pytest
|
||||
PyYAML
|
||||
httplib2
|
||||
|
||||
[testenv:py27httplib2]
|
||||
basepython = python2.7
|
||||
deps =
|
||||
mock
|
||||
pytest
|
||||
PyYAML
|
||||
httplib2
|
||||
|
||||
[testenv:py33httplib2]
|
||||
basepython = python3.3
|
||||
deps =
|
||||
mock
|
||||
pytest
|
||||
PyYAML
|
||||
httplib2
|
||||
|
||||
[testenv:pypyhttplib2]
|
||||
basepython = pypy
|
||||
deps =
|
||||
mock
|
||||
pytest
|
||||
PyYAML
|
||||
httplib2
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from config import VCR
|
||||
from .config import VCR
|
||||
|
||||
default_vcr = VCR()
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ class Cassette(ContextDecorator):
|
||||
def play_response(self, request):
|
||||
'''
|
||||
Get the response corresponding to a request, but only if it
|
||||
hasn't been played back before, and mark it as playe.d
|
||||
hasn't been played back before, and mark it as played
|
||||
'''
|
||||
for index, (stored_request, response) in enumerate(self.data):
|
||||
if requests_match(request, stored_request, self._match_on):
|
||||
|
||||
@@ -32,18 +32,21 @@ class VCR(object):
|
||||
try:
|
||||
serializer = self.serializers[serializer_name]
|
||||
except KeyError:
|
||||
print "Serializer {0} doesn't exist or isn't registered".format(
|
||||
print("Serializer {0} doesn't exist or isn't registered".format(
|
||||
serializer_name
|
||||
)
|
||||
))
|
||||
raise KeyError
|
||||
return serializer
|
||||
|
||||
def _get_matchers(self, matcher_names):
|
||||
matchers = []
|
||||
try:
|
||||
matchers = [self.matchers[m] for m in matcher_names]
|
||||
for m in matcher_names:
|
||||
matchers.append(self.matchers[m])
|
||||
except KeyError:
|
||||
raise KeyError(
|
||||
"Matcher {0} doesn't exist or isn't registered".format(m)
|
||||
"Matcher {0} doesn't exist or isn't registered".format(
|
||||
m)
|
||||
)
|
||||
return matchers
|
||||
|
||||
|
||||
56
vcr/patch.py
56
vcr/patch.py
@@ -1,7 +1,7 @@
|
||||
'''Utilities for patching in cassettes'''
|
||||
|
||||
import httplib
|
||||
from .stubs import VCRHTTPConnection, VCRHTTPSConnection
|
||||
from six.moves import http_client as httplib
|
||||
|
||||
|
||||
# Save some of the original types for the purposes of unpatching
|
||||
@@ -12,7 +12,8 @@ try:
|
||||
# Try to save the original types for requests
|
||||
import requests.packages.urllib3.connectionpool as cpool
|
||||
_VerifiedHTTPSConnection = cpool.VerifiedHTTPSConnection
|
||||
_HTTPConnection = cpool.HTTPConnection
|
||||
_cpoolHTTPConnection = cpool.HTTPConnection
|
||||
_cpoolHTTPSConnection = cpool.HTTPSConnection
|
||||
except ImportError: # pragma: no cover
|
||||
pass
|
||||
|
||||
@@ -23,6 +24,15 @@ try:
|
||||
except ImportError: # pragma: no cover
|
||||
pass
|
||||
|
||||
try:
|
||||
# Try to save the original types for httplib2
|
||||
import httplib2
|
||||
_HTTPConnectionWithTimeout = httplib2.HTTPConnectionWithTimeout
|
||||
_HTTPSConnectionWithTimeout = httplib2.HTTPSConnectionWithTimeout
|
||||
_SCHEME_TO_CONNECTION = httplib2.SCHEME_TO_CONNECTION
|
||||
except ImportError: # pragma: no cover
|
||||
pass
|
||||
|
||||
|
||||
def install(cassette):
|
||||
"""
|
||||
@@ -30,9 +40,8 @@ def install(cassette):
|
||||
This replaces the actual HTTPConnection with a VCRHTTPConnection
|
||||
object which knows how to save to / read from cassettes
|
||||
"""
|
||||
httplib.HTTPConnection = httplib.HTTP._connection_class = VCRHTTPConnection
|
||||
httplib.HTTPSConnection = httplib.HTTPS._connection_class = (
|
||||
VCRHTTPSConnection)
|
||||
httplib.HTTPConnection = VCRHTTPConnection
|
||||
httplib.HTTPSConnection = VCRHTTPSConnection
|
||||
httplib.HTTPConnection.cassette = cassette
|
||||
httplib.HTTPSConnection.cassette = cassette
|
||||
|
||||
@@ -63,18 +72,34 @@ def install(cassette):
|
||||
except ImportError: # pragma: no cover
|
||||
pass
|
||||
|
||||
# patch httplib2
|
||||
try:
|
||||
import httplib2 as cpool
|
||||
from .stubs.httplib2_stubs import VCRHTTPConnectionWithTimeout
|
||||
from .stubs.httplib2_stubs import VCRHTTPSConnectionWithTimeout
|
||||
cpool.HTTPConnectionWithTimeout = VCRHTTPConnectionWithTimeout
|
||||
cpool.HTTPSConnectionWithTimeout = VCRHTTPSConnectionWithTimeout
|
||||
cpool.SCHEME_TO_CONNECTION = {
|
||||
'http': VCRHTTPConnectionWithTimeout,
|
||||
'https': VCRHTTPSConnectionWithTimeout
|
||||
}
|
||||
except ImportError: # pragma: no cover
|
||||
pass
|
||||
|
||||
|
||||
def reset():
|
||||
'''Undo all the patching'''
|
||||
httplib.HTTPConnection = httplib.HTTP._connection_class = _HTTPConnection
|
||||
httplib.HTTPSConnection = httplib.HTTPS._connection_class = \
|
||||
_HTTPSConnection
|
||||
httplib.HTTPConnection = _HTTPConnection
|
||||
httplib.HTTPSConnection = _HTTPSConnection
|
||||
try:
|
||||
import requests.packages.urllib3.connectionpool as cpool
|
||||
# unpatch requests v1.x
|
||||
cpool.VerifiedHTTPSConnection = _VerifiedHTTPSConnection
|
||||
cpool.HTTPConnection = _HTTPConnection
|
||||
cpool.HTTPConnectionPool.ConnectionCls = _HTTPConnection
|
||||
cpool.HTTPSConnectionPool.ConnectionCls = _HTTPSConnection
|
||||
cpool.HTTPConnection = _cpoolHTTPConnection
|
||||
# unpatch requests v2.x
|
||||
cpool.HTTPConnectionPool.ConnectionCls = _cpoolHTTPConnection
|
||||
cpool.HTTPSConnection = _cpoolHTTPSConnection
|
||||
cpool.HTTPSConnectionPool.ConnectionCls = _cpoolHTTPSConnection
|
||||
except ImportError: # pragma: no cover
|
||||
pass
|
||||
|
||||
@@ -82,7 +107,16 @@ def reset():
|
||||
import urllib3.connectionpool as cpool
|
||||
cpool.VerifiedHTTPSConnection = _VerifiedHTTPSConnection
|
||||
cpool.HTTPConnection = _HTTPConnection
|
||||
cpool.HTTPSConnection = _HTTPSConnection
|
||||
cpool.HTTPConnectionPool.ConnectionCls = _HTTPConnection
|
||||
cpool.HTTPSConnectionPool.ConnectionCls = _HTTPSConnection
|
||||
except ImportError: # pragma: no cover
|
||||
pass
|
||||
|
||||
try:
|
||||
import httplib2 as cpool
|
||||
cpool.HTTPConnectionWithTimeout = _HTTPConnectionWithTimeout
|
||||
cpool.HTTPSConnectionWithTimeout = _HTTPSConnectionWithTimeout
|
||||
cpool.SCHEME_TO_CONNECTION = _SCHEME_TO_CONNECTION
|
||||
except ImportError: # pragma: no cover
|
||||
pass
|
||||
|
||||
@@ -10,6 +10,11 @@ class Request(object):
|
||||
# make headers a frozenset so it will be hashable
|
||||
self.headers = frozenset(headers.items())
|
||||
|
||||
def add_header(self, key, value):
|
||||
tmp = dict(self.headers)
|
||||
tmp[key] = value
|
||||
self.headers = frozenset(tmp.iteritems())
|
||||
|
||||
@property
|
||||
def url(self):
|
||||
return "{0}://{1}{2}".format(self.protocol, self.host, self.path)
|
||||
|
||||
73
vcr/serializers/compat.py
Normal file
73
vcr/serializers/compat.py
Normal file
@@ -0,0 +1,73 @@
|
||||
import six
|
||||
|
||||
|
||||
def convert_to_bytes(resp):
|
||||
resp = convert_headers_to_bytes(resp)
|
||||
resp = convert_body_to_bytes(resp)
|
||||
return resp
|
||||
|
||||
|
||||
def convert_to_unicode(resp):
|
||||
resp = convert_headers_to_unicode(resp)
|
||||
resp = convert_body_to_unicode(resp)
|
||||
return resp
|
||||
|
||||
|
||||
def convert_headers_to_bytes(resp):
|
||||
try:
|
||||
resp['headers'] = [h.encode('utf-8') for h in resp['headers']]
|
||||
except (KeyError, TypeError):
|
||||
pass
|
||||
return resp
|
||||
|
||||
|
||||
def convert_headers_to_unicode(resp):
|
||||
try:
|
||||
resp['headers'] = [h.decode('utf-8') for h in resp['headers']]
|
||||
except (KeyError, TypeError):
|
||||
pass
|
||||
return resp
|
||||
|
||||
|
||||
def convert_body_to_bytes(resp):
|
||||
"""
|
||||
If the request body is a string, encode it to bytes (for python3 support)
|
||||
|
||||
By default yaml serializes to utf-8 encoded bytestrings.
|
||||
When this cassette is loaded by python3, it's automatically decoded
|
||||
into unicode strings. This makes sure that it stays a bytestring, since
|
||||
that's what all the internal httplib machinery is expecting.
|
||||
|
||||
For more info on py3 yaml:
|
||||
http://pyyaml.org/wiki/PyYAMLDocumentation#Python3support
|
||||
"""
|
||||
try:
|
||||
if not isinstance(resp['body']['string'], six.binary_type):
|
||||
resp['body']['string'] = resp['body']['string'].encode('utf-8')
|
||||
except (KeyError, TypeError, UnicodeEncodeError):
|
||||
# The thing we were converting either wasn't a dictionary or didn't
|
||||
# have the keys we were expecting. Some of the tests just serialize
|
||||
# and deserialize a string.
|
||||
|
||||
# Also, sometimes the thing actually is binary, so if you can't encode
|
||||
# it, just give up.
|
||||
pass
|
||||
return resp
|
||||
|
||||
|
||||
def convert_body_to_unicode(resp):
|
||||
"""
|
||||
If the request body is bytes, decode it to a string (for python3 support)
|
||||
"""
|
||||
try:
|
||||
if not isinstance(resp['body']['string'], six.text_type):
|
||||
resp['body']['string'] = resp['body']['string'].decode('utf-8')
|
||||
except (KeyError, TypeError, UnicodeDecodeError):
|
||||
# The thing we were converting either wasn't a dictionary or didn't
|
||||
# have the keys we were expecting. Some of the tests just serialize
|
||||
# and deserialize a string.
|
||||
|
||||
# Also, sometimes the thing actually is binary, so if you can't decode
|
||||
# it, just give up.
|
||||
pass
|
||||
return resp
|
||||
@@ -1,4 +1,5 @@
|
||||
from vcr.request import Request
|
||||
from . import compat
|
||||
try:
|
||||
import simplejson as json
|
||||
except ImportError:
|
||||
@@ -11,22 +12,17 @@ def _json_default(obj):
|
||||
return obj
|
||||
|
||||
|
||||
def _fix_response_unicode(d):
|
||||
d['body']['string'] = d['body']['string'].encode('utf-8')
|
||||
return d
|
||||
|
||||
|
||||
def deserialize(cassette_string):
|
||||
data = json.loads(cassette_string)
|
||||
requests = [Request._from_dict(r['request']) for r in data]
|
||||
responses = [_fix_response_unicode(r['response']) for r in data]
|
||||
responses = [compat.convert_to_bytes(r['response']) for r in data]
|
||||
return requests, responses
|
||||
|
||||
|
||||
def serialize(cassette_dict):
|
||||
data = ([{
|
||||
'request': request._to_dict(),
|
||||
'response': response,
|
||||
'response': compat.convert_to_unicode(response),
|
||||
} for request, response in zip(
|
||||
cassette_dict['requests'],
|
||||
cassette_dict['responses']
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import sys
|
||||
import yaml
|
||||
from . import compat
|
||||
|
||||
# Use the libYAML versions if possible
|
||||
try:
|
||||
@@ -6,11 +8,40 @@ try:
|
||||
except ImportError:
|
||||
from yaml import Loader, Dumper
|
||||
|
||||
"""
|
||||
Just a general note on the serialization philosophy here:
|
||||
I prefer cassettes to be human-readable if possible. Yaml serializes
|
||||
bytestrings to !!binary, which isn't readable, so I would like to serialize to
|
||||
strings and from strings, which yaml will encode as utf-8 automatically.
|
||||
All the internal HTTP stuff expects bytestrings, so this whole serialization
|
||||
process feels backwards.
|
||||
|
||||
Serializing: bytestring -> string (yaml persists to utf-8)
|
||||
Deserializing: string (yaml converts from utf-8) -> bytestring
|
||||
"""
|
||||
|
||||
|
||||
def _restore_frozenset():
|
||||
"""
|
||||
Restore __builtin__.frozenset for cassettes serialized in python2 but
|
||||
deserialized in python3 and builtins.frozenset for cassettes serialized
|
||||
in python3 and deserialized in python2
|
||||
"""
|
||||
|
||||
if '__builtin__' not in sys.modules:
|
||||
import builtins
|
||||
sys.modules['__builtin__'] = builtins
|
||||
|
||||
if 'builtins' not in sys.modules:
|
||||
sys.modules['builtins'] = sys.modules['__builtin__']
|
||||
|
||||
|
||||
def deserialize(cassette_string):
|
||||
_restore_frozenset()
|
||||
data = yaml.load(cassette_string, Loader=Loader)
|
||||
requests = [r['request'] for r in data]
|
||||
responses = [r['response'] for r in data]
|
||||
responses = [compat.convert_to_bytes(r['response']) for r in data]
|
||||
return requests, responses
|
||||
|
||||
|
||||
@@ -20,6 +51,6 @@ def serialize(cassette_dict):
|
||||
'response': response,
|
||||
} for request, response in zip(
|
||||
cassette_dict['requests'],
|
||||
cassette_dict['responses']
|
||||
[compat.convert_to_unicode(r) for r in cassette_dict['responses']],
|
||||
)])
|
||||
return yaml.dump(data, Dumper=Dumper)
|
||||
|
||||
@@ -1,10 +1,20 @@
|
||||
'''Stubs for patching HTTP and HTTPS requests'''
|
||||
|
||||
from httplib import HTTPConnection, HTTPSConnection, HTTPMessage
|
||||
from cStringIO import StringIO
|
||||
|
||||
try:
|
||||
import http.client
|
||||
except ImportError:
|
||||
pass
|
||||
import six
|
||||
from six.moves.http_client import (
|
||||
HTTPConnection,
|
||||
HTTPSConnection,
|
||||
HTTPMessage,
|
||||
HTTPResponse,
|
||||
)
|
||||
from six import BytesIO
|
||||
from vcr.request import Request
|
||||
from vcr.errors import CannotOverwriteExistingCassetteException
|
||||
from . import compat
|
||||
|
||||
|
||||
def parse_headers_backwards_compat(header_dict):
|
||||
@@ -14,8 +24,8 @@ def parse_headers_backwards_compat(header_dict):
|
||||
parses the old dictionary-style headers for
|
||||
backwards-compatability reasons.
|
||||
"""
|
||||
msg = HTTPMessage(StringIO(""))
|
||||
for key, val in header_dict.iteritems():
|
||||
msg = HTTPMessage(BytesIO(""))
|
||||
for key, val in header_dict.items():
|
||||
msg.addheader(key, val)
|
||||
msg.headers.append("{0}:{1}".format(key, val))
|
||||
return msg
|
||||
@@ -24,63 +34,73 @@ def parse_headers_backwards_compat(header_dict):
|
||||
def parse_headers(header_list):
|
||||
if isinstance(header_list, dict):
|
||||
return parse_headers_backwards_compat(header_list)
|
||||
headers = "".join(header_list) + "\r\n"
|
||||
msg = HTTPMessage(StringIO(headers))
|
||||
msg.fp.seek(0)
|
||||
msg.readheaders()
|
||||
return msg
|
||||
headers = b"".join(header_list) + b"\r\n"
|
||||
return compat.get_httpmessage(headers)
|
||||
|
||||
|
||||
class VCRHTTPResponse(object):
|
||||
class VCRHTTPResponse(HTTPResponse):
|
||||
"""
|
||||
Stub reponse class that gets returned instead of a HTTPResponse
|
||||
"""
|
||||
def __init__(self, recorded_response):
|
||||
self.recorded_response = recorded_response
|
||||
self.reason = recorded_response['status']['message']
|
||||
self.status = recorded_response['status']['code']
|
||||
self.status = self.code = recorded_response['status']['code']
|
||||
self.version = None
|
||||
self._content = StringIO(self.recorded_response['body']['string'])
|
||||
self.closed = False
|
||||
self._content = BytesIO(self.recorded_response['body']['string'])
|
||||
self._closed = False
|
||||
|
||||
headers = self.recorded_response['headers']
|
||||
self.msg = parse_headers(headers)
|
||||
|
||||
self.length = self.msg.getheader('content-length') or None
|
||||
self.length = compat.get_header(self.msg, 'content-length') or None
|
||||
|
||||
@property
|
||||
def closed(self):
|
||||
# in python3, I can't change the value of self.closed. So I'
|
||||
# twiddling self._closed and using this property to shadow the real
|
||||
# self.closed from the superclas
|
||||
return self._closed
|
||||
|
||||
def read(self, *args, **kwargs):
|
||||
# Note: I'm pretty much ignoring any chunking stuff because
|
||||
# I don't really understand what it is or how it works.
|
||||
return self._content.read(*args, **kwargs)
|
||||
|
||||
def readline(self, *args, **kwargs):
|
||||
return self._content.readline(*args, **kwargs)
|
||||
|
||||
def close(self):
|
||||
self.closed = True
|
||||
self._closed = True
|
||||
return True
|
||||
|
||||
def getcode(self):
|
||||
return self.status
|
||||
|
||||
def isclosed(self):
|
||||
# Urllib3 seems to call this because it actually uses
|
||||
# the weird chunking support in httplib
|
||||
return self.closed
|
||||
|
||||
def info(self):
|
||||
return parse_headers(self.recorded_response['headers'])
|
||||
|
||||
def getheaders(self):
|
||||
headers = parse_headers(self.recorded_response['headers'])
|
||||
return headers.dict.iteritems()
|
||||
message = parse_headers(self.recorded_response['headers'])
|
||||
return compat.get_header_items(message)
|
||||
|
||||
def getheader(self, header, default=None):
|
||||
headers = dict(((k, v) for k, v in self.getheaders()))
|
||||
return headers.get(header, default)
|
||||
|
||||
|
||||
class VCRConnectionMixin:
|
||||
class VCRConnection:
|
||||
# A reference to the cassette that's currently being patched in
|
||||
cassette = None
|
||||
|
||||
def request(self, method, url, body=None, headers=None):
|
||||
'''Persist the request metadata in self._vcr_request'''
|
||||
# see VCRConnectionMixin._restore_socket for the motivation here
|
||||
if hasattr(self, 'sock'):
|
||||
del self.sock
|
||||
|
||||
self._vcr_request = Request(
|
||||
protocol=self._protocol,
|
||||
host=self.host,
|
||||
port=self.port,
|
||||
host=self.real_connection.host,
|
||||
port=self.real_connection.port,
|
||||
method=method,
|
||||
path=url,
|
||||
body=body,
|
||||
@@ -92,6 +112,26 @@ class VCRConnectionMixin:
|
||||
# allows me to compare the entire length of the response to see if it
|
||||
# exists in the cassette.
|
||||
|
||||
def putrequest(self, method, url, *args, **kwargs):
|
||||
"""
|
||||
httplib gives you more than one way to do it. This is a way
|
||||
to start building up a request. Usually followed by a bunch
|
||||
of putheader() calls.
|
||||
"""
|
||||
self._vcr_request = Request(
|
||||
protocol=self._protocol,
|
||||
host=self.real_connection.host,
|
||||
port=self.real_connection.port,
|
||||
method=method,
|
||||
path=url,
|
||||
body="",
|
||||
headers={}
|
||||
)
|
||||
|
||||
def putheader(self, header, *values):
|
||||
for value in values:
|
||||
self._vcr_request.add_header(header, value)
|
||||
|
||||
def send(self, data):
|
||||
'''
|
||||
This method is called after request(), to add additional data to the
|
||||
@@ -101,82 +141,17 @@ class VCRConnectionMixin:
|
||||
self._vcr_request.body = (self._vcr_request.body or '') + data
|
||||
|
||||
def close(self):
|
||||
self._restore_socket()
|
||||
self._baseclass.close(self)
|
||||
# Note: the real connection will only close if it's open, so
|
||||
# no need to check that here.
|
||||
self.real_connection.close()
|
||||
|
||||
def _restore_socket(self):
|
||||
def endheaders(self, *args, **kwargs):
|
||||
"""
|
||||
Since some libraries (REQUESTS!!) decide to set options on
|
||||
connection.socket, I need to delete the socket attribute
|
||||
(which makes requests think this is a AppEngine connection)
|
||||
and then restore it when I want to make the actual request.
|
||||
This function restores it to its standard initial value
|
||||
(which is None)
|
||||
Normally, this would atually send the request to the server.
|
||||
We are not sending the request until getting the response,
|
||||
so bypass this method for now.
|
||||
"""
|
||||
if not hasattr(self, 'sock'):
|
||||
self.sock = None
|
||||
|
||||
def _send_request(self, method, url, body, headers):
|
||||
"""
|
||||
Copy+pasted from python stdlib 2.6 source because it
|
||||
has a call to self.send() which I have overridden
|
||||
#stdlibproblems #fml
|
||||
"""
|
||||
header_names = dict.fromkeys([k.lower() for k in headers])
|
||||
skips = {}
|
||||
if 'host' in header_names:
|
||||
skips['skip_host'] = 1
|
||||
if 'accept-encoding' in header_names:
|
||||
skips['skip_accept_encoding'] = 1
|
||||
|
||||
self.putrequest(method, url, **skips)
|
||||
|
||||
if body and ('content-length' not in header_names):
|
||||
thelen = None
|
||||
try:
|
||||
thelen = str(len(body))
|
||||
except TypeError, te:
|
||||
# If this is a file-like object, try to
|
||||
# fstat its file descriptor
|
||||
import os
|
||||
try:
|
||||
thelen = str(os.fstat(body.fileno()).st_size)
|
||||
except (AttributeError, OSError):
|
||||
# Don't send a length if this failed
|
||||
if self.debuglevel > 0:
|
||||
print "Cannot stat!!"
|
||||
|
||||
if thelen is not None:
|
||||
self.putheader('Content-Length', thelen)
|
||||
for hdr, value in headers.iteritems():
|
||||
self.putheader(hdr, value)
|
||||
self.endheaders()
|
||||
|
||||
if body:
|
||||
self._baseclass.send(self, body)
|
||||
|
||||
def _send_output(self, message_body=None):
|
||||
"""
|
||||
Copy-and-pasted from httplib, just so I can modify the self.send()
|
||||
calls to call the superclass's send(), since I had to override the
|
||||
send() behavior, since send() is both an external and internal
|
||||
httplib API.
|
||||
"""
|
||||
self._buffer.extend(("", ""))
|
||||
msg = "\r\n".join(self._buffer)
|
||||
del self._buffer[:]
|
||||
# If msg and message_body are sent in a single send() call,
|
||||
# it will avoid performance problems caused by the interaction
|
||||
# between delayed ack and the Nagle algorithm.
|
||||
if isinstance(message_body, str):
|
||||
msg += message_body
|
||||
message_body = None
|
||||
self._restore_socket()
|
||||
self._baseclass.send(self, msg)
|
||||
if message_body is not None:
|
||||
#message_body was not a string (i.e. it is a file) and
|
||||
#we must run the risk of Nagle
|
||||
self._baseclass.send(self, message_body)
|
||||
pass
|
||||
|
||||
def getresponse(self, _=False):
|
||||
'''Retrieve a the response'''
|
||||
@@ -198,12 +173,7 @@ class VCRConnectionMixin:
|
||||
# Otherwise, we should send the request, then get the response
|
||||
# and return it.
|
||||
|
||||
# restore sock's value to None, since we need a real socket
|
||||
self._restore_socket()
|
||||
|
||||
#make the actual request
|
||||
self._baseclass.request(
|
||||
self,
|
||||
self.real_connection.request(
|
||||
method=self._vcr_request.method,
|
||||
url=self._vcr_request.path,
|
||||
body=self._vcr_request.body,
|
||||
@@ -211,7 +181,7 @@ class VCRConnectionMixin:
|
||||
)
|
||||
|
||||
# get the response
|
||||
response = self._baseclass.getresponse(self)
|
||||
response = self.real_connection.getresponse()
|
||||
|
||||
# put the response into the cassette
|
||||
response = {
|
||||
@@ -219,29 +189,54 @@ class VCRConnectionMixin:
|
||||
'code': response.status,
|
||||
'message': response.reason
|
||||
},
|
||||
'headers': response.msg.headers,
|
||||
'headers': compat.get_headers(response),
|
||||
'body': {'string': response.read()},
|
||||
}
|
||||
self.cassette.append(self._vcr_request, response)
|
||||
return VCRHTTPResponse(response)
|
||||
|
||||
def set_debuglevel(self, *args, **kwargs):
|
||||
self.real_connection.set_debuglevel(*args, **kwargs)
|
||||
|
||||
class VCRHTTPConnection(VCRConnectionMixin, HTTPConnection):
|
||||
def connect(self, *args, **kwargs):
|
||||
"""
|
||||
httplib2 uses this. Connects to the server I'm assuming.
|
||||
|
||||
Only pass to the baseclass if we don't have a recorded response
|
||||
and are not write-protected.
|
||||
"""
|
||||
|
||||
if hasattr(self, '_vcr_request') and \
|
||||
self._vcr_request in self.cassette and \
|
||||
self.cassette.record_mode != "all" and \
|
||||
self.cassette.rewound:
|
||||
# We already have a response we are going to play, don't
|
||||
# actually connect
|
||||
return
|
||||
|
||||
if self.cassette.write_protected:
|
||||
# Cassette is write-protected, don't actually connect
|
||||
return
|
||||
|
||||
return self.real_connection.connect(*args, **kwargs)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
# need to temporarily reset here because the real connection
|
||||
# inherits from the thing that we are mocking out. Take out
|
||||
# the reset if you want to see what I mean :)
|
||||
from vcr.patch import install, reset
|
||||
reset()
|
||||
self.real_connection = self._baseclass(*args, **kwargs)
|
||||
install(self.cassette)
|
||||
|
||||
|
||||
class VCRHTTPConnection(VCRConnection):
|
||||
'''A Mocked class for HTTP requests'''
|
||||
# Can't use super since this is an old-style class
|
||||
_baseclass = HTTPConnection
|
||||
_protocol = 'http'
|
||||
|
||||
|
||||
class VCRHTTPSConnection(VCRConnectionMixin, HTTPSConnection):
|
||||
class VCRHTTPSConnection(VCRConnection):
|
||||
'''A Mocked class for HTTPS requests'''
|
||||
_baseclass = HTTPSConnection
|
||||
_protocol = 'https'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
'''I overrode the init and copied a lot of the code from the parent
|
||||
class because HTTPConnection when this happens has been replaced by
|
||||
VCRHTTPConnection, but doing it here lets us use the original one.'''
|
||||
HTTPConnection.__init__(self, *args, **kwargs)
|
||||
self.key_file = kwargs.pop('key_file', None)
|
||||
self.cert_file = kwargs.pop('cert_file', None)
|
||||
|
||||
45
vcr/stubs/compat.py
Normal file
45
vcr/stubs/compat.py
Normal file
@@ -0,0 +1,45 @@
|
||||
import six
|
||||
from six import BytesIO
|
||||
from six.moves.http_client import HTTPMessage
|
||||
try:
|
||||
import http.client
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
"""
|
||||
The python3 http.client api moved some stuff around, so this is an abstraction
|
||||
layer that tries to cope with this move.
|
||||
"""
|
||||
|
||||
|
||||
def get_header(message, name):
|
||||
if six.PY3:
|
||||
return message.getallmatchingheaders(name)
|
||||
else:
|
||||
return message.getheader(name)
|
||||
|
||||
|
||||
def get_header_items(message):
|
||||
if six.PY3:
|
||||
return dict(message._headers).items()
|
||||
else:
|
||||
return message.dict.items()
|
||||
|
||||
|
||||
def get_headers(response):
|
||||
if six.PY3:
|
||||
header_list = response.msg._headers
|
||||
return [b': '.join((k.encode('utf-8'), v.encode('utf-8'))) + b'\r\n'
|
||||
for k, v in header_list]
|
||||
else:
|
||||
return response.msg.headers
|
||||
|
||||
|
||||
def get_httpmessage(headers):
|
||||
if six.PY3:
|
||||
return http.client.parse_headers(BytesIO(headers))
|
||||
msg = HTTPMessage(BytesIO(headers))
|
||||
msg.fp.seek(0)
|
||||
msg.readheaders()
|
||||
return msg
|
||||
62
vcr/stubs/httplib2_stubs.py
Normal file
62
vcr/stubs/httplib2_stubs.py
Normal file
@@ -0,0 +1,62 @@
|
||||
'''Stubs for httplib2'''
|
||||
|
||||
from httplib2 import HTTPConnectionWithTimeout, HTTPSConnectionWithTimeout
|
||||
from ..stubs import VCRHTTPConnection, VCRHTTPSConnection
|
||||
|
||||
|
||||
class VCRHTTPConnectionWithTimeout(VCRHTTPConnection,
|
||||
HTTPConnectionWithTimeout):
|
||||
_baseclass = HTTPConnectionWithTimeout
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
'''I overrode the init because I need to clean kwargs before calling
|
||||
HTTPConnection.__init__.'''
|
||||
|
||||
# Delete the keyword arguments that HTTPConnection would not recognize
|
||||
safe_keys = set(
|
||||
('host', 'port', 'strict', 'timeout', 'source_address')
|
||||
)
|
||||
unknown_keys = set(kwargs.keys()) - safe_keys
|
||||
safe_kwargs = kwargs.copy()
|
||||
for kw in unknown_keys:
|
||||
del safe_kwargs[kw]
|
||||
|
||||
self.proxy_info = kwargs.pop('proxy_info', None)
|
||||
VCRHTTPConnection.__init__(self, *args, **safe_kwargs)
|
||||
self.sock = self.real_connection.sock
|
||||
|
||||
|
||||
class VCRHTTPSConnectionWithTimeout(VCRHTTPSConnection,
|
||||
HTTPSConnectionWithTimeout):
|
||||
_baseclass = HTTPSConnectionWithTimeout
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
# Delete the keyword arguments that HTTPSConnection would not recognize
|
||||
safe_keys = set((
|
||||
'host',
|
||||
'port',
|
||||
'key_file',
|
||||
'cert_file',
|
||||
'strict',
|
||||
'timeout',
|
||||
'source_address',
|
||||
))
|
||||
unknown_keys = set(kwargs.keys()) - safe_keys
|
||||
safe_kwargs = kwargs.copy()
|
||||
for kw in unknown_keys:
|
||||
del safe_kwargs[kw]
|
||||
self.proxy_info = kwargs.pop('proxy_info', None)
|
||||
if not 'ca_certs' in kwargs or kwargs['ca_certs'] is None:
|
||||
try:
|
||||
import httplib2
|
||||
self.ca_certs = httplib2.CA_CERTS
|
||||
except ImportError:
|
||||
self.ca_certs = None
|
||||
else:
|
||||
self.ca_certs = kwargs['ca_certs']
|
||||
|
||||
self.disable_ssl_certificate_validation = kwargs.pop(
|
||||
'disable_ssl_certificate_validation', None)
|
||||
VCRHTTPSConnection.__init__(self, *args, **safe_kwargs)
|
||||
self.sock = self.real_connection.sock
|
||||
Reference in New Issue
Block a user