mirror of
https://github.com/kevin1024/vcrpy.git
synced 2025-12-08 16:53:23 +00:00
Substantial refactoring
This refactoring includes some PEP-8 compliance changes, as well as some more complete testing to ensure that we're in fact serving everything out of cassettes when we thing we are. Incidentally, it also includes fixes for #3 and #4
This commit is contained in:
committed by
Kevin McCarthy
parent
3742e2fdc0
commit
b488ca67fe
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,2 +1,7 @@
|
||||
*.pyc
|
||||
.tox
|
||||
build/
|
||||
dist/
|
||||
*.egg/
|
||||
.coverage
|
||||
*.egg-info/
|
||||
@@ -10,4 +10,4 @@ python:
|
||||
install:
|
||||
- pip install PyYAML pytest --use-mirrors
|
||||
- if [ $WITH_REQUESTS = "True" ] ; then pip install requests; fi
|
||||
script: py.test
|
||||
script: pwd; py.test --basetemp=.
|
||||
|
||||
9
setup.py
9
setup.py
@@ -19,12 +19,17 @@ class PyTest(TestCommand):
|
||||
sys.exit(errno)
|
||||
|
||||
setup(name='vcrpy',
|
||||
version='0.0.4',
|
||||
version='0.0.5',
|
||||
description="A Python port of Ruby's VCR to make mocking HTTP easier",
|
||||
author='Kevin McCarthy',
|
||||
author_email='me@kevinmccarthy.org',
|
||||
url='https://github.com/kevin1024/vcrpy',
|
||||
packages=['vcr'],
|
||||
packages=[
|
||||
'vcr',
|
||||
'vcr.stubs'],
|
||||
package_dir={
|
||||
'vcr': 'vcr',
|
||||
'vcr.stubs': 'vcr/stubs'},
|
||||
install_requires=['PyYAML'],
|
||||
license='MIT',
|
||||
tests_require=['pytest'],
|
||||
|
||||
30
tests/common.py
Normal file
30
tests/common.py
Normal file
@@ -0,0 +1,30 @@
|
||||
# coding=utf-8
|
||||
|
||||
import os
|
||||
import json
|
||||
import shutil
|
||||
import unittest
|
||||
|
||||
|
||||
class TestVCR(unittest.TestCase):
|
||||
fixtures = os.path.join('does', 'not', 'exist')
|
||||
|
||||
def tearDown(self):
|
||||
# Remove th urllib2 fixtures if they exist
|
||||
if os.path.exists(self.fixtures):
|
||||
shutil.rmtree(self.fixtures)
|
||||
|
||||
def fixture(self, *names):
|
||||
'''Return a path to the provided fixture'''
|
||||
return os.path.join(self.fixtures, *names)
|
||||
|
||||
def assertBodiesEqual(self, one, two):
|
||||
"""
|
||||
httpbin.org returns a different `origin` header
|
||||
each time, so strip this out since it makes testing
|
||||
difficult.
|
||||
"""
|
||||
one, two = json.loads(one), json.loads(two)
|
||||
del one['origin']
|
||||
del two['origin']
|
||||
self.assertEqual(one, two)
|
||||
37
tests/fixtures/wild/domain_redirect.yaml
vendored
Normal file
37
tests/fixtures/wild/domain_redirect.yaml
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
- request:
|
||||
body: null
|
||||
headers: !!python/object:requests.structures.CaseInsensitiveDict
|
||||
_store:
|
||||
accept: !!python/tuple [Accept, '*/*']
|
||||
accept-encoding: !!python/tuple [Accept-Encoding, 'gzip, deflate, compress']
|
||||
user-agent: !!python/tuple [User-Agent, vcrpy-test]
|
||||
host: seomoz.org
|
||||
method: GET
|
||||
port: 80
|
||||
url: /
|
||||
response:
|
||||
body: {string: "<html>\r\n<head><title>301 Moved Permanently</title></head>\r\n<body
|
||||
bgcolor=\"white\">\r\n<center><h1>301 Moved Permanently</h1></center>\r\n<hr><center>nginx</center>\r\n</body>\r\n</html>\r\n"}
|
||||
headers: {accept-ranges: bytes, age: '0', connection: keep-alive, content-length: '178',
|
||||
content-type: text/html, date: 'Mon, 05 Aug 2013 22:05:23 GMT', location: 'http://moz.com/',
|
||||
server: nginx, server-name: dalmozwww03.dal.moz.com, via: 1.1 varnish, x-varnish: '836826051'}
|
||||
status: {code: 301, message: Moved Permanently}
|
||||
- request:
|
||||
body: null
|
||||
headers: !!python/object:requests.structures.CaseInsensitiveDict
|
||||
_store:
|
||||
accept: !!python/tuple [Accept, '*/*']
|
||||
accept-encoding: !!python/tuple [Accept-Encoding, 'gzip, deflate, compress']
|
||||
user-agent: !!python/tuple [User-Agent, vcrpy-test]
|
||||
host: moz.com
|
||||
method: GET
|
||||
port: 80
|
||||
url: /
|
||||
response:
|
||||
body: {string: ''}
|
||||
headers: {accept-ranges: bytes, age: '2763', cache-control: 'no-cache, must-revalidate,
|
||||
s-maxage=3600', connection: keep-alive, content-encoding: gzip, content-length: '0',
|
||||
content-type: text/html, date: 'Mon, 05 Aug 2013 22:05:23 GMT', expires: 'Fri,
|
||||
15 Oct 2004 12:00:00 GMT', server: nginx, server-name: dalmozwww02.dal.moz.com,
|
||||
vary: Accept-Encoding, via: 1.1 varnish, x-varnish: 836826061 836743142}
|
||||
status: {code: 200, message: OK}
|
||||
@@ -1,107 +1,34 @@
|
||||
'''Basic tests about cassettes'''
|
||||
# coding=utf-8
|
||||
import os
|
||||
import unittest
|
||||
|
||||
# Internal imports
|
||||
import vcr
|
||||
from vcr.cassette import Cassette
|
||||
from .common import TestVCR
|
||||
|
||||
# External imports
|
||||
import os
|
||||
import urllib2
|
||||
from urllib import urlencode
|
||||
from utils import assert_httpbin_responses_equal
|
||||
|
||||
TEST_CASSETTE_FILE = 'cassettes/test_req.yaml'
|
||||
|
||||
class TestHttpRequest(unittest.TestCase):
|
||||
|
||||
def tearDown(self):
|
||||
try:
|
||||
os.remove(TEST_CASSETTE_FILE)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def test_response_code(self):
|
||||
code = urllib2.urlopen('http://httpbin.org/').getcode()
|
||||
with vcr.use_cassette(TEST_CASSETTE_FILE):
|
||||
self.assertEqual(code, urllib2.urlopen('http://httpbin.org/').getcode())
|
||||
self.assertEqual(code, urllib2.urlopen('http://httpbin.org/').getcode())
|
||||
|
||||
def test_response_body(self):
|
||||
body = urllib2.urlopen('http://httpbin.org/').read()
|
||||
with vcr.use_cassette(TEST_CASSETTE_FILE):
|
||||
self.assertEqual(body, urllib2.urlopen('http://httpbin.org/').read())
|
||||
self.assertEqual(body, urllib2.urlopen('http://httpbin.org/').read())
|
||||
|
||||
def test_response_headers(self):
|
||||
with vcr.use_cassette(TEST_CASSETTE_FILE):
|
||||
headers = urllib2.urlopen('http://httpbin.org/').info().items()
|
||||
self.assertEqual(headers, urllib2.urlopen('http://httpbin.org/').info().items())
|
||||
|
||||
def test_multiple_requests(self):
|
||||
body1 = urllib2.urlopen('http://httpbin.org/').read()
|
||||
body2 = urllib2.urlopen('http://httpbin.org/get').read()
|
||||
with vcr.use_cassette(TEST_CASSETTE_FILE):
|
||||
self.assertEqual(body1, urllib2.urlopen('http://httpbin.org/').read())
|
||||
new_body2 = urllib2.urlopen('http://httpbin.org/get').read()
|
||||
|
||||
assert_httpbin_responses_equal(body2, new_body2)
|
||||
|
||||
self.assertEqual(body1, urllib2.urlopen('http://httpbin.org/').read())
|
||||
new_body2 = urllib2.urlopen('http://httpbin.org/get').read()
|
||||
|
||||
assert_httpbin_responses_equal(body2, new_body2)
|
||||
|
||||
class TestHttps(unittest.TestCase):
|
||||
|
||||
def tearDown(self):
|
||||
try:
|
||||
os.remove(TEST_CASSETTE_FILE)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def test_response_code(self):
|
||||
code = urllib2.urlopen('https://httpbin.org/').getcode()
|
||||
with vcr.use_cassette(TEST_CASSETTE_FILE):
|
||||
self.assertEqual(code, urllib2.urlopen('https://httpbin.org/').getcode())
|
||||
self.assertEqual(code, urllib2.urlopen('https://httpbin.org/').getcode())
|
||||
|
||||
def test_response_body(self):
|
||||
body = urllib2.urlopen('https://httpbin.org/').read()
|
||||
with vcr.use_cassette(TEST_CASSETTE_FILE):
|
||||
self.assertEqual(body, urllib2.urlopen('https://httpbin.org/').read())
|
||||
self.assertEqual(body, urllib2.urlopen('https://httpbin.org/').read())
|
||||
|
||||
def test_response_headers(self):
|
||||
with vcr.use_cassette(TEST_CASSETTE_FILE):
|
||||
headers = urllib2.urlopen('https://httpbin.org/').info().items()
|
||||
self.assertEqual(headers, urllib2.urlopen('https://httpbin.org/').info().items())
|
||||
|
||||
def test_get_data(self):
|
||||
TEST_DATA = urlencode({'some': 1, 'data': 'here'})
|
||||
with vcr.use_cassette(TEST_CASSETTE_FILE):
|
||||
body = urllib2.urlopen('https://httpbin.org/get?' + TEST_DATA).read()
|
||||
self.assertEqual(body, urllib2.urlopen('https://httpbin.org/get?' + TEST_DATA).read())
|
||||
|
||||
def test_post_data(self):
|
||||
TEST_DATA = urlencode({'some': 1, 'data': 'here'})
|
||||
with vcr.use_cassette(TEST_CASSETTE_FILE):
|
||||
body = urllib2.urlopen('https://httpbin.org/post', TEST_DATA).read()
|
||||
self.assertEqual(body, urllib2.urlopen('https://httpbin.org/post', TEST_DATA).read())
|
||||
|
||||
def test_post_unicode(self):
|
||||
TEST_DATA = urlencode({'snowman': u'☃'.encode('utf-8')})
|
||||
with vcr.use_cassette(TEST_CASSETTE_FILE):
|
||||
body = urllib2.urlopen('https://httpbin.org/post', TEST_DATA).read()
|
||||
self.assertEqual(body, urllib2.urlopen('https://httpbin.org/post', TEST_DATA).read())
|
||||
|
||||
|
||||
class TestCassette(unittest.TestCase):
|
||||
def test_serialize_cassette(self):
|
||||
c1 = Cassette()
|
||||
c1.requests = ['a', 'b', 'c']
|
||||
c1.responses = ['d', 'e', 'f']
|
||||
ser = c1.serialize()
|
||||
c2 = Cassette(ser)
|
||||
self.assertEqual(c1.requests, c2.requests)
|
||||
self.assertEqual(c1.responses, c2.responses)
|
||||
class TestCassette(TestVCR):
|
||||
'''We should be able to save a cassette'''
|
||||
fixtures = os.path.join('tests', 'fixtures', 'basic')
|
||||
|
||||
def test_nonexistent_directory(self):
|
||||
'''If we load a cassette in a nonexistent directory, it can save ok'''
|
||||
self.assertFalse(os.path.exists(self.fixture('nonexistent')))
|
||||
with vcr.use_cassette(self.fixture('nonexistent', 'cass.yaml')):
|
||||
urllib2.urlopen('http://httpbin.org/').read()
|
||||
# This should have made the file and the directory
|
||||
self.assertTrue(
|
||||
os.path.exists(self.fixture('nonexistent', 'cass.yaml')))
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
def test_unpatch(self):
|
||||
'''Ensure that our cassette gets unpatched when we're done'''
|
||||
with vcr.use_cassette(self.fixture('unpatch.yaml')) as cass:
|
||||
urllib2.urlopen('http://httpbin.org/').read()
|
||||
|
||||
# Make the same requests, and assert that we haven't served any more
|
||||
# requests out of cache
|
||||
urllib2.urlopen('http://httpbin.org/').read()
|
||||
self.assertEqual(len(cass.cached()), 0)
|
||||
|
||||
@@ -1,110 +1,163 @@
|
||||
# coding=utf-8
|
||||
import os
|
||||
import unittest
|
||||
import vcr
|
||||
import pytest
|
||||
from utils import assert_httpbin_responses_equal
|
||||
'''Test requests' interaction with vcr'''
|
||||
|
||||
# coding=utf-8
|
||||
|
||||
# Internal imports
|
||||
import vcr
|
||||
from .common import TestVCR
|
||||
|
||||
import os
|
||||
import pytest
|
||||
requests = pytest.importorskip("requests")
|
||||
|
||||
TEST_CASSETTE_FILE = 'cassettes/test_req.yaml'
|
||||
|
||||
class TestRequestsGet(unittest.TestCase):
|
||||
class TestRequestsBase(TestVCR):
|
||||
'''Some utility for running Requests tests'''
|
||||
fixtures = os.path.join('tests', 'fixtures', 'requests')
|
||||
|
||||
def setUp(self):
|
||||
self.unmolested_response = requests.get('http://httpbin.org/get')
|
||||
with vcr.use_cassette(TEST_CASSETTE_FILE):
|
||||
self.initial_response = requests.get('http://httpbin.org/get')
|
||||
self.cached_response = requests.get('http://httpbin.org/get')
|
||||
|
||||
class TestHTTPRequests(TestRequestsBase):
|
||||
'''Some tests using requests and http'''
|
||||
scheme = 'http'
|
||||
|
||||
def test_status_code(self):
|
||||
'''Ensure that we can read the status code'''
|
||||
url = self.scheme + '://httpbin.org/'
|
||||
with vcr.use_cassette(self.fixture('atts.yaml')) as cass:
|
||||
# Ensure that this is empty to begin with
|
||||
self.assertEqual(len(cass), 0)
|
||||
self.assertEqual(len(cass.cached()), 0)
|
||||
self.assertEqual(
|
||||
requests.get(url).status_code,
|
||||
requests.get(url).status_code)
|
||||
# Ensure that we've now cached a single response
|
||||
self.assertEqual(len(cass), 1)
|
||||
self.assertEqual(len(cass.cached()), 1)
|
||||
|
||||
def test_headers(self):
|
||||
'''Ensure that we can read the headers back'''
|
||||
url = self.scheme + '://httpbin.org/'
|
||||
with vcr.use_cassette(self.fixture('headers.yaml')) as cass:
|
||||
# Ensure that this is empty to begin with
|
||||
self.assertEqual(len(cass), 0)
|
||||
self.assertEqual(len(cass.cached()), 0)
|
||||
self.assertEqual(
|
||||
requests.get(url).headers,
|
||||
requests.get(url).headers)
|
||||
# Ensure that we've now cached a single response
|
||||
self.assertEqual(len(cass), 1)
|
||||
self.assertEqual(len(cass.cached()), 1)
|
||||
|
||||
def test_body(self):
|
||||
'''Ensure the responses are all identical enough'''
|
||||
url = self.scheme + '://httpbin.org/bytes/1024'
|
||||
with vcr.use_cassette(self.fixture('body.yaml')) as cass:
|
||||
# Ensure that this is empty to begin with
|
||||
self.assertEqual(len(cass), 0)
|
||||
self.assertEqual(len(cass.cached()), 0)
|
||||
self.assertEqual(
|
||||
requests.get(url).content,
|
||||
requests.get(url).content)
|
||||
# Ensure that we've now cached a single response
|
||||
self.assertEqual(len(cass), 1)
|
||||
self.assertEqual(len(cass.cached()), 1)
|
||||
|
||||
def test_auth(self):
|
||||
'''Ensure that we can handle basic auth'''
|
||||
auth = ('user', 'passwd')
|
||||
url = self.scheme + '://httpbin.org/basic-auth/user/passwd'
|
||||
with vcr.use_cassette(self.fixture('auth.yaml')) as cass:
|
||||
# Ensure that this is empty to begin with
|
||||
self.assertEqual(len(cass), 0)
|
||||
self.assertEqual(len(cass.cached()), 0)
|
||||
one = requests.get(url, auth=auth)
|
||||
two = requests.get(url, auth=auth)
|
||||
self.assertEqual(one.content, two.content)
|
||||
self.assertEqual(one.status_code, two.status_code)
|
||||
# Ensure that we've now cached a single response
|
||||
self.assertEqual(len(cass), 1)
|
||||
self.assertEqual(len(cass.cached()), 1)
|
||||
|
||||
def test_auth_failed(self):
|
||||
'''Ensure that we can save failed auth statuses'''
|
||||
auth = ('user', 'wrongwrongwrong')
|
||||
url = self.scheme + '://httpbin.org/basic-auth/user/passwd'
|
||||
with vcr.use_cassette(self.fixture('auth-failed.yaml')) as cass:
|
||||
# Ensure that this is empty to begin with
|
||||
self.assertEqual(len(cass), 0)
|
||||
self.assertEqual(len(cass.cached()), 0)
|
||||
one = requests.get(url, auth=auth)
|
||||
two = requests.get(url, auth=auth)
|
||||
self.assertEqual(one.content, two.content)
|
||||
self.assertEqual(one.status_code, two.status_code)
|
||||
self.assertNotEqual(one.status_code, 200)
|
||||
# Ensure that we've now cached a single response
|
||||
self.assertEqual(len(cass), 1)
|
||||
self.assertEqual(len(cass.cached()), 1)
|
||||
|
||||
def test_post(self):
|
||||
'''Ensure that we can post and cache the results'''
|
||||
data = {'key1': 'value1', 'key2': 'value2'}
|
||||
url = self.scheme + '://httpbin.org/post'
|
||||
with vcr.use_cassette(self.fixture('redirect.yaml')) as cass:
|
||||
# Ensure that this is empty to begin with
|
||||
self.assertEqual(len(cass), 0)
|
||||
self.assertEqual(len(cass.cached()), 0)
|
||||
self.assertEqual(
|
||||
requests.post(url, data).content,
|
||||
requests.post(url, data).content)
|
||||
# Ensure that we've now cached a single response
|
||||
self.assertEqual(len(cass), 1)
|
||||
self.assertEqual(len(cass.cached()), 1)
|
||||
|
||||
def test_redirects(self):
|
||||
'''Ensure that we can handle redirects'''
|
||||
url = self.scheme + '://httpbin.org/redirect-to?url=bytes/1024'
|
||||
with vcr.use_cassette(self.fixture('redirect.yaml')) as cass:
|
||||
# Ensure that this is empty to begin with
|
||||
self.assertEqual(len(cass), 0)
|
||||
self.assertEqual(len(cass.cached()), 0)
|
||||
self.assertEqual(
|
||||
requests.get(url).content,
|
||||
requests.get(url).content)
|
||||
# Ensure that we've now cached /two/ responses. One for the redirect
|
||||
# and one for the final fetch
|
||||
self.assertEqual(len(cass), 2)
|
||||
self.assertEqual(len(cass.cached()), 2)
|
||||
|
||||
|
||||
class TestHTTPSRequests(TestHTTPRequests):
|
||||
'''Same as above, now in https'''
|
||||
scheme = 'https'
|
||||
|
||||
def test_cross_scheme(self):
|
||||
'''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(self.fixture('cross_scheme.yaml')) as cass:
|
||||
requests.get('https://httpbin.org/')
|
||||
requests.get('http://httpbin.org/')
|
||||
self.assertEqual(len(cass), 2)
|
||||
self.assertEqual(len(cass.cached()), 0)
|
||||
|
||||
|
||||
class TestWild(TestRequestsBase):
|
||||
'''Test some examples from the wild'''
|
||||
fixtures = os.path.join('tests', 'fixtures', 'wild')
|
||||
|
||||
def tearDown(self):
|
||||
try:
|
||||
os.remove(TEST_CASSETTE_FILE)
|
||||
except OSError:
|
||||
pass
|
||||
# No deleting our directory, and ensure that it exists
|
||||
self.assertTrue(os.path.exists(self.fixture()))
|
||||
|
||||
def test_initial_response_code(self):
|
||||
self.assertEqual(self.unmolested_response.status_code, self.initial_response.status_code)
|
||||
|
||||
def test_cached_response_code(self):
|
||||
self.assertEqual(self.unmolested_response.status_code, self.cached_response.status_code)
|
||||
|
||||
def test_initial_response_headers(self):
|
||||
self.assertEqual(self.unmolested_response.headers['content-type'], self.initial_response.headers['content-type'])
|
||||
|
||||
def test_cached_response_headers(self):
|
||||
self.assertEqual(self.unmolested_response.headers['content-type'], self.cached_response.headers['content-type'])
|
||||
|
||||
def test_initial_response_text(self):
|
||||
assert_httpbin_responses_equal(self.unmolested_response.text, self.initial_response.text)
|
||||
|
||||
def test_cached_response_text(self):
|
||||
assert_httpbin_responses_equal(self.unmolested_response.text, self.cached_response.text)
|
||||
|
||||
|
||||
class TestRequestsAuth(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.unmolested_response = requests.get('https://httpbin.org/basic-auth/user/passwd', auth=('user', 'passwd'))
|
||||
with vcr.use_cassette(TEST_CASSETTE_FILE):
|
||||
self.initial_response = requests.get('https://httpbin.org/basic-auth/user/passwd', auth=('user', 'passwd'))
|
||||
self.cached_response = requests.get('https://httpbin.org/basic-auth/user/passwd', auth=('user', 'passwd'))
|
||||
|
||||
def tearDown(self):
|
||||
try:
|
||||
os.remove(TEST_CASSETTE_FILE)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def test_initial_response_code(self):
|
||||
self.assertEqual(self.unmolested_response.status_code, self.initial_response.status_code)
|
||||
|
||||
def test_cached_response_code(self):
|
||||
self.assertEqual(self.unmolested_response.status_code, self.cached_response.status_code)
|
||||
|
||||
def test_cached_response_auth_can_fail(self):
|
||||
auth_fail_cached = requests.get('https://httpbin.org/basic-auth/user/passwd', auth=('user', 'passwdzzz'))
|
||||
self.assertNotEqual(self.unmolested_response.status_code, auth_fail_cached.status_code)
|
||||
|
||||
class TestRequestsPost(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
payload = {'key1': 'value1', 'key2': 'value2'}
|
||||
self.unmolested_response = requests.post('http://httpbin.org/post', payload)
|
||||
with vcr.use_cassette(TEST_CASSETTE_FILE):
|
||||
self.initial_response = requests.post('http://httpbin.org/post', payload)
|
||||
self.cached_response = requests.post('http://httpbin.org/post', payload)
|
||||
|
||||
def tearDown(self):
|
||||
try:
|
||||
os.remove(TEST_CASSETTE_FILE)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def test_initial_post_response_text(self):
|
||||
assert_httpbin_responses_equal(self.unmolested_response.text, self.initial_response.text)
|
||||
|
||||
def test_cached_post_response_text(self):
|
||||
assert_httpbin_responses_equal(self.unmolested_response.text, self.cached_response.text)
|
||||
|
||||
|
||||
class TestRequestsHTTPS(unittest.TestCase):
|
||||
maxDiff = None
|
||||
|
||||
def setUp(self):
|
||||
self.unmolested_response = requests.get('https://httpbin.org/get')
|
||||
with vcr.use_cassette(TEST_CASSETTE_FILE):
|
||||
self.initial_response = requests.get('https://httpbin.org/get')
|
||||
self.cached_response = requests.get('https://httpbin.org/get')
|
||||
|
||||
def tearDown(self):
|
||||
try:
|
||||
os.remove(TEST_CASSETTE_FILE)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def test_initial_https_response_text(self):
|
||||
assert_httpbin_responses_equal(self.unmolested_response.text, self.initial_response.text)
|
||||
|
||||
def test_cached_https_response_text(self):
|
||||
assert_httpbin_responses_equal(self.unmolested_response.text, self.cached_response.text)
|
||||
def test_domain_redirect(self):
|
||||
'''Ensure that redirects across domains are considered unique'''
|
||||
# In this example, seomoz.org redirects to moz.com, and if those
|
||||
# requests are considered identical, then we'll be stuck in a redirect
|
||||
# loop.
|
||||
url = 'http://seomoz.org/'
|
||||
with vcr.use_cassette(self.fixture('domain_redirect.yaml')) as cass:
|
||||
requests.get(url, headers={'User-Agent': 'vcrpy-test'})
|
||||
# Ensure that we've now served two responses. One for the original
|
||||
# redirect, and a second for the actual fetch
|
||||
self.assertEqual(len(cass.cached()), 2)
|
||||
|
||||
141
tests/test_urllib2.py
Normal file
141
tests/test_urllib2.py
Normal file
@@ -0,0 +1,141 @@
|
||||
'''Integration tests with urllib2'''
|
||||
# coding=utf-8
|
||||
|
||||
# Internal imports
|
||||
import vcr
|
||||
from .common import TestVCR
|
||||
|
||||
# Testing urllib2 fetching
|
||||
import os
|
||||
import urllib2
|
||||
from urllib import urlencode
|
||||
|
||||
|
||||
class TestUrllib2(TestVCR):
|
||||
'''Base class for tests for urllib2'''
|
||||
fixtures = os.path.join('tests', 'fixtures', 'urllib2')
|
||||
|
||||
|
||||
class TestUrllib2Http(TestUrllib2):
|
||||
'''Tests for integration with urllib2'''
|
||||
scheme = 'http'
|
||||
|
||||
def test_response_code(self):
|
||||
'''Ensure we can read a response code from a fetch'''
|
||||
url = self.scheme + '://httpbin.org/'
|
||||
with vcr.use_cassette(self.fixture('atts.yaml')) as cass:
|
||||
# Ensure that this is empty to begin with
|
||||
self.assertEqual(len(cass), 0)
|
||||
self.assertEqual(len(cass.cached()), 0)
|
||||
self.assertEqual(
|
||||
urllib2.urlopen(url).getcode(),
|
||||
urllib2.urlopen(url).getcode())
|
||||
# Ensure that we've now cached a single response
|
||||
self.assertEqual(len(cass), 1)
|
||||
self.assertEqual(len(cass.cached()), 1)
|
||||
|
||||
def test_random_body(self):
|
||||
'''Ensure we can read the content, and that it's served from cache'''
|
||||
url = self.scheme + '://httpbin.org/bytes/1024'
|
||||
with vcr.use_cassette(self.fixture('body.yaml')) as cass:
|
||||
# Ensure that this is empty to begin with
|
||||
self.assertEqual(len(cass), 0)
|
||||
self.assertEqual(len(cass.cached()), 0)
|
||||
self.assertEqual(
|
||||
urllib2.urlopen(url).read(),
|
||||
urllib2.urlopen(url).read())
|
||||
# Ensure that we've now cached a single response
|
||||
self.assertEqual(len(cass), 1)
|
||||
self.assertEqual(len(cass.cached()), 1)
|
||||
|
||||
def test_response_headers(self):
|
||||
'''Ensure we can get information from the response'''
|
||||
url = self.scheme + '://httpbin.org/'
|
||||
with vcr.use_cassette(self.fixture('headers.yaml')) as cass:
|
||||
# Ensure that this is empty to begin with
|
||||
self.assertEqual(len(cass), 0)
|
||||
self.assertEqual(len(cass.cached()), 0)
|
||||
self.assertEqual(
|
||||
urllib2.urlopen(url).info().items(),
|
||||
urllib2.urlopen(url).info().items())
|
||||
# Ensure that we've now cached a single response
|
||||
self.assertEqual(len(cass), 1)
|
||||
|
||||
def test_multiple_requests(self):
|
||||
'''Ensure that we can cache multiple requests'''
|
||||
urls = [
|
||||
self.scheme + '://httpbin.org/',
|
||||
self.scheme + '://httpbin.org/get',
|
||||
self.scheme + '://httpbin.org/bytes/1024'
|
||||
]
|
||||
with vcr.use_cassette(self.fixture('multiple.yaml')) as cass:
|
||||
for index in range(len(urls)):
|
||||
url = urls[index]
|
||||
self.assertEqual(len(cass), index)
|
||||
self.assertEqual(len(cass.cached()), index)
|
||||
self.assertEqual(
|
||||
urllib2.urlopen(url).read(),
|
||||
urllib2.urlopen(url).read())
|
||||
self.assertEqual(len(cass), index + 1)
|
||||
self.assertEqual(len(cass.cached()), index + 1)
|
||||
|
||||
def test_get_data(self):
|
||||
'''Ensure that it works with query data'''
|
||||
data = urlencode({'some': 1, 'data': 'here'})
|
||||
url = self.scheme + '://httpbin.org/get?' + data
|
||||
with vcr.use_cassette(self.fixture('get_data.yaml')) as cass:
|
||||
# Ensure that this is empty to begin with
|
||||
self.assertEqual(len(cass), 0)
|
||||
self.assertEqual(len(cass.cached()), 0)
|
||||
self.assertEqual(
|
||||
urllib2.urlopen(url).read(),
|
||||
urllib2.urlopen(url).read())
|
||||
# Ensure that we've now cached a single response
|
||||
self.assertEqual(len(cass), 1)
|
||||
self.assertEqual(len(cass.cached()), 1)
|
||||
|
||||
def test_post_data(self):
|
||||
'''Ensure that it works when posting data'''
|
||||
data = urlencode({'some': 1, 'data': 'here'})
|
||||
url = self.scheme + '://httpbin.org/post'
|
||||
with vcr.use_cassette(self.fixture('post_data.yaml')) as cass:
|
||||
# Ensure that this is empty to begin with
|
||||
self.assertEqual(len(cass), 0)
|
||||
self.assertEqual(len(cass.cached()), 0)
|
||||
self.assertEqual(
|
||||
urllib2.urlopen(url, data).read(),
|
||||
urllib2.urlopen(url, data).read())
|
||||
# Ensure that we've now cached a single response
|
||||
self.assertEqual(len(cass), 1)
|
||||
self.assertEqual(len(cass.cached()), 1)
|
||||
|
||||
def test_post_unicode_data(self):
|
||||
'''Ensure that it works when posting unicode data'''
|
||||
data = urlencode({'snowman': u'☃'.encode('utf-8')})
|
||||
url = self.scheme + '://httpbin.org/post'
|
||||
with vcr.use_cassette(self.fixture('post_data.yaml')) as cass:
|
||||
# Ensure that this is empty to begin with
|
||||
self.assertEqual(len(cass), 0)
|
||||
self.assertEqual(len(cass.cached()), 0)
|
||||
self.assertEqual(
|
||||
urllib2.urlopen(url, data).read(),
|
||||
urllib2.urlopen(url, data).read())
|
||||
# Ensure that we've now cached a single response
|
||||
self.assertEqual(len(cass), 1)
|
||||
self.assertEqual(len(cass.cached()), 1)
|
||||
|
||||
|
||||
class TestUrllib2Https(TestUrllib2Http):
|
||||
'''Similar tests but for http status codes'''
|
||||
scheme = 'https'
|
||||
|
||||
def test_cross_scheme(self):
|
||||
'''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(self.fixture('cross_scheme.yaml')) as cass:
|
||||
urllib2.urlopen('https://httpbin.org/')
|
||||
urllib2.urlopen('http://httpbin.org/')
|
||||
self.assertEqual(len(cass), 2)
|
||||
self.assertEqual(len(cass.cached()), 0)
|
||||
@@ -1 +1,6 @@
|
||||
from .patch import use_cassette
|
||||
# Import Cassette to make it available at the top level
|
||||
from .cassette import Cassette
|
||||
|
||||
# Also, make a 'load' function available
|
||||
def use_cassette(path):
|
||||
return Cassette.load(path)
|
||||
|
||||
102
vcr/cassette.py
102
vcr/cassette.py
@@ -1,27 +1,99 @@
|
||||
'''The container for recorded requests and responses'''
|
||||
|
||||
import os
|
||||
import yaml
|
||||
import tempfile
|
||||
try:
|
||||
# Use the libYAML versions if possible. They're much faster than the pure
|
||||
# python implemenations
|
||||
from yaml import CLoader as Loader, CDumper as Dumper
|
||||
except ImportError: # pragma: no cover
|
||||
from yaml import Loader, Dumper
|
||||
|
||||
# Internal imports
|
||||
from .patch import install, reset
|
||||
|
||||
|
||||
class Cassette(object):
|
||||
def __init__(self, ser_cassette=None):
|
||||
self.requests = []
|
||||
self.responses = []
|
||||
if ser_cassette:
|
||||
self._unserialize(ser_cassette)
|
||||
'''A container for recorded requests and responses'''
|
||||
@classmethod
|
||||
def load(cls, path):
|
||||
'''Load in the cassette stored at the provided path'''
|
||||
try:
|
||||
with open(path) as fin:
|
||||
return cls(path, yaml.load(fin, Loader=Loader))
|
||||
except IOError:
|
||||
return cls(path)
|
||||
|
||||
def __init__(self, path, data=None):
|
||||
self._path = path
|
||||
self._cached = []
|
||||
self._requests = []
|
||||
self._responses = []
|
||||
if data:
|
||||
self.deserialize(data)
|
||||
|
||||
def save(self, path):
|
||||
'''Save this cassette to a path'''
|
||||
dirname, filename = os.path.split(path)
|
||||
if not os.path.exists(dirname):
|
||||
os.makedirs(dirname)
|
||||
# We'll overwrite the old version securely by writing out a temporary
|
||||
# version and then moving it to replace the old version
|
||||
fd, name = tempfile.mkstemp(dir=dirname, prefix=filename)
|
||||
with os.fdopen(fd, 'w') as fout:
|
||||
fout.write(yaml.dump(self.serialize(), Dumper=Dumper))
|
||||
os.rename(name, path)
|
||||
|
||||
def serialize(self):
|
||||
'''Return a serializable version of the cassette'''
|
||||
return ([{
|
||||
'request': req,
|
||||
'response': res,
|
||||
} for req, res in zip(self.requests, self.responses)])
|
||||
} for req, res in zip(self._requests, self._responses)])
|
||||
|
||||
def _unserialize(self, source):
|
||||
self.requests, self.responses = [r['request'] for r in source], [r['response'] for r in source]
|
||||
def deserialize(self, source):
|
||||
'''Given a seritalized version, load the requests'''
|
||||
self._requests, self._responses = (
|
||||
[r['request'] for r in source], [r['response'] for r in source])
|
||||
|
||||
def get_request(self, match):
|
||||
def cached(self, request=None):
|
||||
'''Alert the cassete of a request that's been cached, or get the
|
||||
requests that we've cached'''
|
||||
if request:
|
||||
self._cached.append(request)
|
||||
else:
|
||||
return self._cached
|
||||
|
||||
def append(self, request, response):
|
||||
'''Add a pair of request, response pairs to this cassette'''
|
||||
self._requests.append(request)
|
||||
self._responses.append(response)
|
||||
|
||||
def __len__(self):
|
||||
'''Return the number of request / response pairs stored in here'''
|
||||
return len(self._requests)
|
||||
|
||||
def __contains__(self, request):
|
||||
'''Return whether or not a request has been stored'''
|
||||
try:
|
||||
return self.requests[self.requests.index(match)]
|
||||
self._requests.index(request)
|
||||
return True
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
def response(self, request):
|
||||
'''Find the response corresponding to a request'''
|
||||
try:
|
||||
return self._responses[self._requests.index(request)]
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
def get_response(self, match):
|
||||
try:
|
||||
return self.responses[self.requests.index(match)]
|
||||
except ValueError:
|
||||
return None
|
||||
def __enter__(self):
|
||||
'''Patch the fetching libraries we know about'''
|
||||
install(self)
|
||||
return self
|
||||
|
||||
def __exit__(self, typ, value, traceback):
|
||||
self.save(self._path)
|
||||
reset()
|
||||
|
||||
26
vcr/files.py
26
vcr/files.py
@@ -1,26 +0,0 @@
|
||||
import os
|
||||
import yaml
|
||||
from .cassette import Cassette
|
||||
|
||||
# Use the libYAML versions if possible
|
||||
try:
|
||||
from yaml import CLoader as Loader, CDumper as Dumper
|
||||
except ImportError:
|
||||
from yaml import Loader, Dumper
|
||||
|
||||
|
||||
def load_cassette(cassette_path):
|
||||
try:
|
||||
pc = yaml.load(open(cassette_path), Loader=Loader)
|
||||
cassette = Cassette(pc)
|
||||
return cassette
|
||||
except IOError:
|
||||
return None
|
||||
|
||||
|
||||
def save_cassette(cassette_path, cassette):
|
||||
dirname, filename = os.path.split(cassette_path)
|
||||
if not os.path.exists(dirname):
|
||||
os.makedirs(dirname)
|
||||
with open(cassette_path, 'a') as cassette_file:
|
||||
cassette_file.write(yaml.dump(cassette.serialize(), Dumper=Dumper))
|
||||
84
vcr/patch.py
84
vcr/patch.py
@@ -1,75 +1,75 @@
|
||||
'''Utilities for patching in cassettes'''
|
||||
|
||||
import httplib
|
||||
from contextlib import contextmanager
|
||||
from .stubs import VCRHTTPConnection, VCRHTTPSConnection
|
||||
|
||||
|
||||
# Save some of the original types for the purposes of unpatching
|
||||
_HTTPConnection = httplib.HTTPConnection
|
||||
_HTTPSConnection = httplib.HTTPSConnection
|
||||
|
||||
try:
|
||||
import requests.packages.urllib3.connectionpool
|
||||
_VerifiedHTTPSConnection = requests.packages.urllib3.connectionpool.VerifiedHTTPSConnection
|
||||
_HTTPConnection = requests.packages.urllib3.connectionpool.HTTPConnection
|
||||
except ImportError:
|
||||
# Try to save the original types for requests
|
||||
import requests.packages.urllib3.connectionpool as cpool
|
||||
_VerifiedHTTPSConnection = cpool.VerifiedHTTPSConnection
|
||||
_HTTPConnection = cpool.HTTPConnection
|
||||
except ImportError: # pragma: no cover
|
||||
pass
|
||||
|
||||
try:
|
||||
# Try to save the original types for urllib3
|
||||
import urllib3
|
||||
_VerifiedHTTPSConnection = urllib3.connectionpool.VerifiedHTTPSConnection
|
||||
except ImportError:
|
||||
_VerifiedHTTPSConnection = urllib3.connectionpool.VerifiedHTTPSConnection
|
||||
except ImportError: # pragma: no cover
|
||||
pass
|
||||
|
||||
|
||||
def install(cassette_path):
|
||||
def install(cassette):
|
||||
'''Install a cassette in lieu of actuall fetching'''
|
||||
httplib.HTTPConnection = httplib.HTTP._connection_class = VCRHTTPConnection
|
||||
httplib.HTTPSConnection = httplib.HTTPS._connection_class = VCRHTTPSConnection
|
||||
httplib.HTTPConnection._vcr_cassette_path = cassette_path
|
||||
httplib.HTTPSConnection._vcr_cassette_path = cassette_path
|
||||
httplib.HTTPSConnection = httplib.HTTPS._connection_class = (
|
||||
VCRHTTPSConnection)
|
||||
httplib.HTTPConnection.cassette = cassette
|
||||
httplib.HTTPSConnection.cassette = cassette
|
||||
|
||||
#patch requests
|
||||
# patch requests
|
||||
try:
|
||||
import requests.packages.urllib3.connectionpool
|
||||
from .requests_stubs import VCRVerifiedHTTPSConnection
|
||||
requests.packages.urllib3.connectionpool.VerifiedHTTPSConnection = VCRVerifiedHTTPSConnection
|
||||
requests.packages.urllib3.connectionpool.VerifiedHTTPSConnection._vcr_cassette_path = cassette_path
|
||||
requests.packages.urllib3.connectionpool.HTTPConnection = VCRHTTPConnection
|
||||
requests.packages.urllib3.connectionpool.HTTPConnection._vcr_cassette_path = cassette_path
|
||||
except ImportError:
|
||||
import requests.packages.urllib3.connectionpool as cpool
|
||||
from .stubs.requests_stubs import VCRVerifiedHTTPSConnection
|
||||
cpool.VerifiedHTTPSConnection = VCRVerifiedHTTPSConnection
|
||||
cpool.VerifiedHTTPSConnection.cassette = cassette
|
||||
cpool.HTTPConnection = VCRHTTPConnection
|
||||
cpool.HTTPConnection.cassette = cassette
|
||||
except ImportError: # pragma: no cover
|
||||
pass
|
||||
|
||||
#patch urllib3
|
||||
# patch urllib3
|
||||
try:
|
||||
import urllib3.connectionpool
|
||||
from .urllib3_stubs import VCRVerifiedHTTPSConnection
|
||||
urllib3.connectionpool.VerifiedHTTPSConnection = VCRVerifiedHTTPSConnection
|
||||
urllib3.connectionpool.VerifiedHTTPSConnection._vcr_cassette_path = cassette_path
|
||||
urllib3.connectionpool.HTTPConnection = VCRHTTPConnection
|
||||
urllib3.connectionpool.HTTPConnection._vcr_cassette_path = cassette_path
|
||||
except ImportError:
|
||||
import urllib3.connectionpool as cpool
|
||||
from .stubs.urllib3_stubs import VCRVerifiedHTTPSConnection
|
||||
cpool.VerifiedHTTPSConnection = VCRVerifiedHTTPSConnection
|
||||
cpool.VerifiedHTTPSConnection.cassette = cassette
|
||||
cpool.HTTPConnection = VCRHTTPConnection
|
||||
cpool.HTTPConnection.cassette = cassette
|
||||
except ImportError: # pragma: no cover
|
||||
pass
|
||||
|
||||
|
||||
def reset():
|
||||
'''Unto all the patching'''
|
||||
httplib.HTTPConnection = httplib.HTTP._connection_class = _HTTPConnection
|
||||
httplib.HTTPSConnection = httplib.HTTPS._connection_class = \
|
||||
_HTTPSConnection
|
||||
try:
|
||||
import requests.packages.urllib3.connectionpool
|
||||
requests.packages.urllib3.connectionpool.VerifiedHTTPSConnection = _VerifiedHTTPSConnection
|
||||
requests.packages.urllib3.connectionpool.HTTPConnection = _HTTPConnection
|
||||
except ImportError:
|
||||
import requests.packages.urllib3.connectionpool as cpool
|
||||
cpool.VerifiedHTTPSConnection = _VerifiedHTTPSConnection
|
||||
cpool.HTTPConnection = _HTTPConnection
|
||||
except ImportError: # pragma: no cover
|
||||
pass
|
||||
|
||||
try:
|
||||
import urllib3.connectionpool
|
||||
urllib3.connectionpool.VerifiedHTTPSConnection = _VerifiedHTTPSConnection
|
||||
urllib3.connectionpool.HTTPConnection = _HTTPConnection
|
||||
except ImportError:
|
||||
import urllib3.connectionpool as cpool
|
||||
cpool.VerifiedHTTPSConnection = _VerifiedHTTPSConnection
|
||||
cpool.HTTPConnection = _HTTPConnection
|
||||
except ImportError: # pragma: no cover
|
||||
pass
|
||||
|
||||
|
||||
@contextmanager
|
||||
def use_cassette(cassette_path):
|
||||
install(cassette_path)
|
||||
yield
|
||||
reset()
|
||||
|
||||
106
vcr/stubs.py
106
vcr/stubs.py
@@ -1,106 +0,0 @@
|
||||
from httplib import HTTPConnection, HTTPSConnection, HTTPMessage
|
||||
from cStringIO import StringIO
|
||||
from .files import save_cassette, load_cassette
|
||||
from .cassette import Cassette
|
||||
|
||||
|
||||
class VCRHTTPResponse(object):
|
||||
"""
|
||||
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.version = None
|
||||
self._content = StringIO(self.recorded_response['body']['string'])
|
||||
|
||||
self.msg = HTTPMessage(StringIO(''))
|
||||
for k, v in self.recorded_response['headers'].iteritems():
|
||||
self.msg.addheader(k, v)
|
||||
|
||||
self.length = self.msg.getheader('content-length') or None
|
||||
|
||||
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 close(self):
|
||||
return True
|
||||
|
||||
def isclosed(self):
|
||||
# Urllib3 seems to call this because it actually uses
|
||||
# the weird chunking support in httplib
|
||||
return True
|
||||
|
||||
def getheaders(self):
|
||||
return self.recorded_response['headers'].iteritems()
|
||||
|
||||
|
||||
class VCRConnectionMixin:
|
||||
def _load_old_response(self):
|
||||
old_cassette = load_cassette(self._vcr_cassette_path)
|
||||
if old_cassette:
|
||||
return old_cassette.get_response(self._vcr)
|
||||
|
||||
def request(self, method, url, body=None, headers={}):
|
||||
"""
|
||||
Persist the request metadata in self._vcr
|
||||
"""
|
||||
self._vcr = {
|
||||
'method': method,
|
||||
'url': url,
|
||||
'body': body,
|
||||
'headers': headers,
|
||||
}
|
||||
old_cassette = load_cassette(self._vcr_cassette_path)
|
||||
if old_cassette and old_cassette.get_request(self._vcr):
|
||||
return
|
||||
self._cassette.requests.append(dict(
|
||||
method=method,
|
||||
url=url,
|
||||
body=body,
|
||||
headers=headers
|
||||
))
|
||||
self._baseclass.request(self, method, url, body=body, headers=headers)
|
||||
|
||||
def getresponse(self, buffering=False):
|
||||
old_response = self._load_old_response()
|
||||
if not old_response:
|
||||
response = HTTPConnection.getresponse(self)
|
||||
self._cassette.responses.append({
|
||||
'status': {'code': response.status, 'message': response.reason},
|
||||
'headers': dict(response.getheaders()),
|
||||
'body': {'string': response.read()},
|
||||
})
|
||||
save_cassette(self._vcr_cassette_path, self._cassette)
|
||||
old_response = self._load_old_response()
|
||||
return VCRHTTPResponse(old_response)
|
||||
|
||||
|
||||
class VCRHTTPConnection(VCRConnectionMixin, HTTPConnection):
|
||||
|
||||
# Can't use super since this is an old-style class
|
||||
_baseclass = HTTPConnection
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._cassette = Cassette()
|
||||
HTTPConnection.__init__(self, *args, **kwargs)
|
||||
|
||||
|
||||
class VCRHTTPSConnection(VCRConnectionMixin, HTTPSConnection):
|
||||
|
||||
_baseclass = HTTPSConnection
|
||||
|
||||
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)
|
||||
self._cassette = Cassette()
|
||||
106
vcr/stubs/__init__.py
Normal file
106
vcr/stubs/__init__.py
Normal file
@@ -0,0 +1,106 @@
|
||||
'''Stubs for patching HTTP and HTTPS requests'''
|
||||
|
||||
from httplib import HTTPConnection, HTTPSConnection, HTTPMessage
|
||||
from cStringIO import StringIO
|
||||
|
||||
|
||||
class VCRHTTPResponse(object):
|
||||
"""
|
||||
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.version = None
|
||||
self._content = StringIO(self.recorded_response['body']['string'])
|
||||
|
||||
self.msg = HTTPMessage(StringIO(''))
|
||||
for key, val in self.recorded_response['headers'].iteritems():
|
||||
self.msg.addheader(key, val)
|
||||
|
||||
self.length = self.msg.getheader('content-length') or None
|
||||
|
||||
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 close(self):
|
||||
return True
|
||||
|
||||
def isclosed(self):
|
||||
# Urllib3 seems to call this because it actually uses
|
||||
# the weird chunking support in httplib
|
||||
return True
|
||||
|
||||
def getheaders(self):
|
||||
return self.recorded_response['headers'].iteritems()
|
||||
|
||||
|
||||
class VCRConnectionMixin:
|
||||
# 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'''
|
||||
self._request = {
|
||||
'host': self.host,
|
||||
'port': self.port,
|
||||
'method': method,
|
||||
'url': url,
|
||||
'body': body,
|
||||
'headers': headers or {},
|
||||
}
|
||||
# Check if we have a cassette set, and if we have a response saved.
|
||||
# If so, there's no need to keep processing and we can bail
|
||||
if self.cassette and self._request in self.cassette:
|
||||
return
|
||||
|
||||
# Otherwise, we should submit the request
|
||||
self._baseclass.request(
|
||||
self, method, url, body=body, headers=headers or {})
|
||||
|
||||
def getresponse(self, _=False):
|
||||
'''Retrieve a the response'''
|
||||
# Check to see if the cassette has a response for this request. If so,
|
||||
# then return it
|
||||
response = self.cassette.response(self._request)
|
||||
if response:
|
||||
# Alert the cassette to the fact that we've served another cached
|
||||
# response for the provided requests
|
||||
self.cassette.cached(self._request)
|
||||
return VCRHTTPResponse(response)
|
||||
|
||||
# Otherwise, we made an actual request, and should return the response
|
||||
# we got from the actual connection
|
||||
response = HTTPConnection.getresponse(self)
|
||||
response = {
|
||||
'status': {'code': response.status, 'message': response.reason},
|
||||
'headers': dict(response.getheaders()),
|
||||
'body': {'string': response.read()},
|
||||
}
|
||||
self.cassette.append(self._request, response)
|
||||
return VCRHTTPResponse(response)
|
||||
|
||||
|
||||
class VCRHTTPConnection(VCRConnectionMixin, HTTPConnection):
|
||||
'''A Mocked class for HTTP requests'''
|
||||
# Can't use super since this is an old-style class
|
||||
_baseclass = HTTPConnection
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
HTTPConnection.__init__(self, *args, **kwargs)
|
||||
|
||||
|
||||
class VCRHTTPSConnection(VCRConnectionMixin, HTTPSConnection):
|
||||
'''A Mocked class for HTTPS requests'''
|
||||
_baseclass = HTTPSConnection
|
||||
|
||||
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)
|
||||
@@ -1,7 +1,8 @@
|
||||
'''Stubs for requests'''
|
||||
|
||||
from requests.packages.urllib3.connectionpool import VerifiedHTTPSConnection
|
||||
from .stubs import VCRHTTPSConnection
|
||||
from ..stubs import VCRHTTPSConnection
|
||||
|
||||
|
||||
class VCRVerifiedHTTPSConnection(VCRHTTPSConnection, VerifiedHTTPSConnection):
|
||||
|
||||
_baseclass = VerifiedHTTPSConnection
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
'''Stubs for urllib3'''
|
||||
|
||||
from urllib3.connectionpool import VerifiedHTTPSConnection
|
||||
from .stubs import VCRHTTPSConnection
|
||||
from ..stubs import VCRHTTPSConnection
|
||||
|
||||
|
||||
class VCRVerifiedHTTPSConnection(VCRHTTPSConnection, VerifiedHTTPSConnection):
|
||||
|
||||
_baseclass = VerifiedHTTPSConnection
|
||||
|
||||
Reference in New Issue
Block a user