1
0
mirror of https://github.com/kevin1024/vcrpy.git synced 2025-12-09 01:03:24 +00:00

Compare commits

...

28 Commits

Author SHA1 Message Date
Kevin McCarthy
9480954c33 update release notes 2014-12-08 17:10:35 -10:00
Kevin McCarthy
8432ad32f1 Merge pull request #127 from gazpachoking/1.1.3
Version bump to v1.1.3
2014-12-08 17:09:07 -10:00
Chase Sterling
fabef3d988 Version bump to v1.1.3 2014-12-08 21:43:01 -05:00
Ivan 'Goat' Malison
da45f46b2d Merge pull request #125 from gazpachoking/pool_is_none
Fix crash with requests 2.5 where connectionpool was None
2014-12-08 13:20:36 -08:00
Ivan 'Goat' Malison
562a0ebadc Merge pull request #126 from gazpachoking/116
Play back requests requests on windows. fix #116
2014-12-08 12:29:34 -08:00
Chase Sterling
ef8ba6d51b Add requests 2.5 to testing list in .travis.yml and tox.ini 2014-12-08 14:40:55 -05:00
Chase Sterling
f6aa6eac84 Play back requests requests on windows. fix #116 2014-12-08 14:28:48 -05:00
Chase Sterling
821e148752 Fix crash with requests 2.5 where connectionpool was None 2014-12-07 13:49:23 -05:00
Ivan Malison
7306205b8a Improve test_new_episodes_record_mode_two_times test. 2014-11-21 17:15:15 -08:00
Nithin Reddy
2a128893cc Adds a test to ensure that the cassette created with "new_episodes" has different expected behavior when opened with "once". 2014-11-21 09:47:28 -08:00
Nithin Reddy
5162d183e5 Fixes #123. When attempting to replay the same request twice using record_mode="new_episodes", vcr.py raises UnhandledHTTPRequestError. 2014-11-20 19:07:21 -08:00
Ivan Malison
9d52c3ed42 Remove warning message caused by lack of is_verified property on HTTPSConnection stub. 2014-11-13 16:32:38 -08:00
Ivan 'Goat' Malison
0e37759175 Merge pull request #118 from rtaboada/fix-response-stub-headers-field
Create headers field in VCRHTTPResponse. Fixes #117.
2014-11-03 04:07:12 -08:00
Ivan 'Goat' Malison
78c6258ba3 Merge pull request #119 from telaviv/make_boto_tests_pass_again
test_boto_stubs passes again.
2014-10-31 00:14:45 -07:00
Shawn Krisman
b047336690 test_boto_stubs passes again. 2014-10-30 16:08:17 -07:00
Rodrigo Taboada
c955a5ea88 String in request body should be bytes. 2014-10-24 18:30:32 -02:00
Rodrigo Taboada
5423d99f5a Tests for VCRHTTPResponse headers field. 2014-10-24 17:40:51 -02:00
Rodrigo Taboada
a71c15f398 Create headers field in VCRHTTPResponse. Fixes #117. 2014-10-24 16:37:12 -02:00
Ivan Malison
6e049ba7a1 version bump to v1.1.2 2014-10-08 12:11:53 -07:00
Ivan Malison
916e7839e5 Actually use pytest.raises in test. 2014-10-07 13:45:09 -07:00
Ivan Malison
99692a92d2 Handle unicode error in json serialize properly. 2014-10-07 13:21:47 -07:00
Ivan Malison
a9a68ba44b Random tweaks. 2014-10-05 18:37:01 -07:00
Ivan Malison
e9f35db405 Remove .travis.yml changes. 2014-10-05 16:42:46 -07:00
Ivan Malison
7193407a07 Remove ipdb because it causes python below 2.6 to blow up. 2014-10-03 01:40:02 -07:00
Ivan Malison
c3427ae3a2 Fix pip install of tox in travis. 2014-10-02 15:48:29 -07:00
Ivan Malison
3a46a6f210 travis through tox. 2014-10-02 15:26:22 -07:00
Ivan Malison
163181844b Refactor tox.ini using new 1.8 features. 2014-10-02 14:57:53 -07:00
Ivan Malison
2c6f072d11 better logging when matches aren't working. 2014-09-25 04:49:00 -07:00
14 changed files with 180 additions and 215 deletions

View File

@@ -8,6 +8,7 @@ env:
- WITH_LIB="requests2.2"
- WITH_LIB="requests2.3"
- WITH_LIB="requests2.4"
- WITH_LIB="requests2.5"
- WITH_LIB="requests1.x"
- WITH_LIB="httplib2"
- WITH_LIB="boto"
@@ -33,6 +34,7 @@ install:
- if [ $WITH_LIB = "requests2.2" ] ; then pip install requests==2.2.1; fi
- if [ $WITH_LIB = "requests2.3" ] ; then pip install requests==2.3.0; fi
- if [ $WITH_LIB = "requests2.4" ] ; then pip install requests==2.4.0; fi
- if [ $WITH_LIB = "requests2.5" ] ; then pip install requests==2.5.0; fi
- if [ $WITH_LIB = "httplib2" ] ; then pip install httplib2; fi
- if [ $WITH_LIB = "boto" ] ; then pip install boto; fi
script: python setup.py test

View File

@@ -457,6 +457,11 @@ API in version 1.0.x
## Changelog
* 1.1.3 Fix python3 headers field (thanks @rtaboada), fix boto test (thanks
@telaviv), fix new_episodes record mode (thanks @jashugan), fix Windows
connectionpool stub bug (thanks @gazpachoking)
* 1.1.2 Add urllib==1.7.1 support. Make json serialize error handling correct
Improve logging of match failures.
* 1.1.1 Use function signature preserving `wrapt.decorator` to write the
decorator version of use_cassette in order to ensure compatibility with
py.test fixtures and python 2. Move all request filtering into the

View File

@@ -20,7 +20,7 @@ class PyTest(TestCommand):
setup(
name='vcrpy',
version='1.1.1',
version='1.1.3',
description=(
"Automatically mock your HTTP interactions to simplify and "
"speed up testing"

View File

@@ -1,4 +1,4 @@
'''Basic tests about cassettes'''
'''Basic tests for cassettes'''
# coding=utf-8
# External imports

View File

@@ -14,7 +14,7 @@ def test_boto_stubs(tmpdir):
from boto.https_connection import CertValidatingHTTPSConnection
from vcr.stubs.boto_stubs import VCRCertValidatingHTTPSConnection
# Prove that the class was patched by the stub and that we can instantiate it.
assert CertValidatingHTTPSConnection is VCRCertValidatingHTTPSConnection
assert issubclass(CertValidatingHTTPSConnection, VCRCertValidatingHTTPSConnection)
CertValidatingHTTPSConnection('hostname.does.not.matter')
def test_boto_without_vcr():

View File

@@ -72,6 +72,31 @@ def test_new_episodes_record_mode(tmpdir):
assert len(cass.responses) == 2
def test_new_episodes_record_mode_two_times(tmpdir):
testfile = str(tmpdir.join('recordmode.yml'))
url = 'http://httpbin.org/bytes/1024'
with vcr.use_cassette(testfile, record_mode="new_episodes"):
# cassette file doesn't exist, so create.
original_first_response = urlopen(url).read()
with vcr.use_cassette(testfile, record_mode="new_episodes"):
# make the same request again
assert urlopen(url).read() == original_first_response
# in the "new_episodes" record mode, we can add the same request
# to the cassette without repercussions
original_second_response = urlopen(url).read()
with vcr.use_cassette(testfile, record_mode="once"):
# make the same request again
assert urlopen(url).read() == original_first_response
assert urlopen(url).read() == original_second_response
# now that we are back in once mode, this should raise
# an error.
with pytest.raises(Exception):
urlopen(url).read()
def test_all_record_mode(tmpdir):
testfile = str(tmpdir.join('recordmode.yml'))

View File

@@ -0,0 +1,68 @@
# coding: UTF-8
from vcr.stubs import VCRHTTPResponse
def test_response_should_have_headers_field():
recorded_response = {
"status": {
"message": "OK",
"code": 200
},
"headers": {
"content-length": ["0"],
"server": ["gunicorn/18.0"],
"connection": ["Close"],
"access-control-allow-credentials": ["true"],
"date": ["Fri, 24 Oct 2014 18:35:37 GMT"],
"access-control-allow-origin": ["*"],
"content-type": ["text/html; charset=utf-8"],
},
"body": {
"string": b""
}
}
response = VCRHTTPResponse(recorded_response)
assert response.headers is not None
def test_response_headers_should_be_equal_to_msg():
recorded_response = {
"status": {
"message": b"OK",
"code": 200
},
"headers": {
"content-length": ["0"],
"server": ["gunicorn/18.0"],
"connection": ["Close"],
"content-type": ["text/html; charset=utf-8"],
},
"body": {
"string": b""
}
}
response = VCRHTTPResponse(recorded_response)
assert response.headers == response.msg
def test_response_headers_should_have_correct_values():
recorded_response = {
"status": {
"message": "OK",
"code": 200
},
"headers": {
"content-length": ["10806"],
"date": ["Fri, 24 Oct 2014 18:35:37 GMT"],
"content-type": ["text/html; charset=utf-8"],
},
"body": {
"string": b""
}
}
response = VCRHTTPResponse(recorded_response)
assert response.headers.get('content-length') == "10806"
assert response.headers.get('date') == "Fri, 24 Oct 2014 18:35:37 GMT"

View File

@@ -1,21 +1,35 @@
import mock
import pytest
from vcr.serialize import deserialize
from vcr.serializers import yamlserializer, jsonserializer
def test_deserialize_old_yaml_cassette():
with open('tests/fixtures/migration/old_cassette.yaml', 'r') as f:
with pytest.raises(ValueError):
deserialize(f.read(), yamlserializer)
def test_deserialize_old_json_cassette():
with open('tests/fixtures/migration/old_cassette.json', 'r') as f:
with pytest.raises(ValueError):
deserialize(f.read(), jsonserializer)
def test_deserialize_new_yaml_cassette():
with open('tests/fixtures/migration/new_cassette.yaml', 'r') as f:
deserialize(f.read(), yamlserializer)
def test_deserialize_new_json_cassette():
with open('tests/fixtures/migration/new_cassette.json', 'r') as f:
deserialize(f.read(), jsonserializer)
@mock.patch.object(jsonserializer.json, 'dumps',
side_effect=UnicodeDecodeError('utf-8', b'unicode error in serialization',
0, 10, 'blew up'))
def test_serialize_constructs_UnicodeDecodeError(mock_dumps):
with pytest.raises(UnicodeDecodeError):
jsonserializer.serialize({})

193
tox.ini
View File

@@ -1,188 +1,25 @@
# Tox (http://tox.testrun.org/) is a tool for running tests
# in multiple virtualenvs. This configuration file will run the
# test suite on all supported python versions. To use it, "pip install tox"
# and then run "tox" from this directory.
[tox]
envlist =
py26,
py27,
py33,
py34,
pypy,
py26requests24,
py27requests24,
py34requests24,
pypyrequests24,
py26requests23,
py27requests23,
py34requests23,
pypyrequests23,
py26requests22,
py27requests22,
py34requests22,
pypyrequests22,
py26requests1,
py27requests1,
py33requests1,
pypyrequests1,
py26httplib2,
py27httplib2,
py33httplib2,
py34httplib2,
pypyhttplib2,
envlist = {py26,py27,py33,py34,pypy}-{requests25,requests24,requests23,requests22,requests1,httplib2,urllib3,boto}
[testenv]
commands =
py.test {posargs}
basepython =
py26: python2.6
py27: python2.7
py33: python3.3
py34: python3.4
pypy: pypy
deps =
mock
pytest
pytest-localserver
PyYAML
ipdb
[testenv:py26requests1]
basepython = python2.6
deps =
{[testenv]deps}
requests==1.2.3
[testenv:py27requests1]
basepython = python2.7
deps =
{[testenv]deps}
requests==1.2.3
[testenv:py33requests1]
basepython = python3.3
deps =
{[testenv]deps}
requests==1.2.3
[testenv:pypyrequests1]
basepython = pypy
deps =
{[testenv]deps}
requests==1.2.3
[testenv:py26requests24]
basepython = python2.6
deps =
{[testenv]deps}
requests==2.4.0
[testenv:py27requests24]
basepython = python2.7
deps =
{[testenv]deps}
requests==2.4.0
[testenv:py33requests24]
basepython = python3.4
deps =
{[testenv]deps}
requests==2.4.0
[testenv:py34requests24]
basepython = python3.4
deps =
{[testenv]deps}
requests==2.4.0
[testenv:pypyrequests24]
basepython = pypy
deps =
{[testenv]deps}
requests==2.4.0
[testenv:py26requests23]
basepython = python2.6
deps =
{[testenv]deps}
requests==2.3.0
[testenv:py27requests23]
basepython = python2.7
deps =
{[testenv]deps}
requests==2.3.0
[testenv:py33requests23]
basepython = python3.4
deps =
{[testenv]deps}
requests==2.3.0
[testenv:py34requests23]
basepython = python3.4
deps =
{[testenv]deps}
requests==2.3.0
[testenv:pypyrequests23]
basepython = pypy
deps =
{[testenv]deps}
requests==2.3.0
[testenv:py26requests22]
basepython = python2.6
deps =
{[testenv]deps}
requests==2.2.1
[testenv:py27requests22]
basepython = python2.7
deps =
{[testenv]deps}
requests==2.2.1
[testenv:py33requests22]
basepython = python3.4
deps =
{[testenv]deps}
requests==2.2.1
[testenv:py34requests22]
basepython = python3.4
deps =
{[testenv]deps}
requests==2.2.1
[testenv:pypyrequests22]
basepython = pypy
deps =
{[testenv]deps}
requests==2.2.1
[testenv:py26httplib2]
basepython = python2.6
deps =
{[testenv]deps}
httplib2
[testenv:py27httplib2]
basepython = python2.7
deps =
{[testenv]deps}
httplib2
[testenv:py33httplib2]
basepython = python3.4
deps =
{[testenv]deps}
httplib2
[testenv:py34httplib2]
basepython = python3.4
deps =
{[testenv]deps}
httplib2
[testenv:pypyhttplib2]
basepython = pypy
deps =
{[testenv]deps}
httplib2
requests1: requests==1.2.3
requests25: requests==2.5.0
requests24: requests==2.4.0
requests23: requests==2.3.0
requests22: requests==2.2.1
httplib2: httplib2
urllib3: urllib3==1.7.1
boto: boto

View File

@@ -1,4 +1,4 @@
'''The container for recorded requests and responses'''
"""The container for recorded requests and responses"""
import logging
import contextlib2
@@ -24,7 +24,7 @@ class CassetteContextDecorator(object):
removing cassettes.
This class defers the creation of a new cassette instance until the point at
which it is installned by context manager or decorator. The fact that a new
which it is installed by context manager or decorator. The fact that a new
cassette is used with each application prevents the state of any cassette
from interfering with another.
"""
@@ -45,7 +45,8 @@ class CassetteContextDecorator(object):
log.debug('Entered context for cassette at {0}.'.format(cassette._path))
yield cassette
log.debug('Exiting context for cassette at {0}.'.format(cassette._path))
# TODO(@IvanMalison): Hmmm. it kind of feels like this should be somewhere else.
# TODO(@IvanMalison): Hmmm. it kind of feels like this should be
# somewhere else.
cassette._save()
def __enter__(self):
@@ -65,11 +66,11 @@ class CassetteContextDecorator(object):
class Cassette(object):
'''A container for recorded requests and responses'''
"""A container for recorded requests and responses"""
@classmethod
def load(cls, path, **kwargs):
'''Instantiate and load the cassette stored at the specified path.'''
"""Instantiate and load the cassette stored at the specified path."""
new_cassette = cls(path, **kwargs)
new_cassette._load()
return new_cassette
@@ -85,7 +86,8 @@ class Cassette(object):
def __init__(self, path, serializer=yamlserializer, record_mode='once',
match_on=(uri, method), filter_headers=(),
filter_query_parameters=(), before_record_request=None,
before_record_response=None, ignore_hosts=(), ignore_localhost=()):
before_record_response=None, ignore_hosts=(),
ignore_localhost=()):
self._path = path
self._serializer = serializer
self._match_on = match_on
@@ -105,9 +107,7 @@ class Cassette(object):
@property
def all_played(self):
"""
Returns True if all responses have been played, False otherwise.
"""
"""Returns True if all responses have been played, False otherwise."""
return self.play_count == len(self)
@property
@@ -124,7 +124,7 @@ class Cassette(object):
self.record_mode == 'none'
def append(self, request, response):
'''Add a request, response pair to this cassette'''
"""Add a request, response pair to this cassette"""
request = self._before_record_request(request)
if not request:
return
@@ -153,10 +153,10 @@ class Cassette(object):
self.rewound
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 played
'''
"""
for index, response in self._responses(request):
if self.play_counts[index] == 0:
self.play_counts[index] += 1
@@ -168,11 +168,11 @@ class Cassette(object):
)
def responses_of(self, request):
'''
"""
Find the responses corresponding to a request.
This function isn't actually used by VCR internally, but is
provided as an external API.
'''
"""
responses = [response for index, response in self._responses(request)]
if responses:
@@ -214,11 +214,12 @@ class Cassette(object):
)
def __len__(self):
'''Return the number of request,response pairs stored in here'''
"""Return the number of request,response pairs stored in here"""
return len(self.data)
def __contains__(self, request):
'''Return whether or not a request has been stored'''
for response in self._responses(request):
return True
"""Return whether or not a request has been stored"""
for index, response in self._responses(request):
if self.play_counts[index] == 0:
return True
return False

View File

@@ -38,16 +38,16 @@ def headers(r1, r2):
return r1.headers == r2.headers
def _log_matches(matches):
def _log_matches(r1, r2, matches):
differences = [m for m in matches if not m[0]]
if differences:
log.debug(
'Requests differ according to the following matchers: ' +
str(differences)
"Requests {0} and {1} differ according to "
"the following matchers: {2}".format(r1, r2, differences)
)
def requests_match(r1, r2, matchers):
matches = [(m(r1, r2), m) for m in matchers]
_log_matches(matches)
_log_matches(r1, r2, matches)
return all([m[0] for m in matches])

View File

@@ -59,7 +59,9 @@ class CassettePatcherBuilder(object):
def _build_patchers_from_mock_triples_decorator(function):
@functools.wraps(function)
def wrapped(self, *args, **kwargs):
return self._build_patchers_from_mock_triples(function(self, *args, **kwargs))
return self._build_patchers_from_mock_triples(
function(self, *args, **kwargs)
)
return wrapped
def __init__(self, cassette):
@@ -134,6 +136,7 @@ class CassettePatcherBuilder(object):
(cpool, 'VerifiedHTTPSConnection', VCRRequestsHTTPSConnection),
(cpool, 'HTTPConnection', VCRRequestsHTTPConnection),
(cpool, 'HTTPSConnection', VCRRequestsHTTPSConnection),
(cpool, 'is_connection_dropped', mock.Mock(return_value=False)), # Needed on Windows only
(cpool.HTTPConnectionPool, 'ConnectionCls', VCRRequestsHTTPConnection),
(cpool.HTTPSConnectionPool, 'ConnectionCls', VCRRequestsHTTPSConnection),
)
@@ -234,7 +237,7 @@ class ConnectionRemover(object):
def __exit__(self, *args):
for pool, connections in self._connection_pool_to_connections.items():
readd_connections = []
while not pool.pool.empty() and connections:
while pool.pool and not pool.pool.empty() and connections:
connection = pool.pool.get()
if isinstance(connection, self._connection_class):
connections.remove(connection)
@@ -273,8 +276,9 @@ def reset_patchers():
yield mock.patch.object(cpool, 'VerifiedHTTPSConnection', _VerifiedHTTPSConnection)
yield mock.patch.object(cpool, 'HTTPConnection', _HTTPConnection)
yield mock.patch.object(cpool, 'HTTPSConnection', _HTTPSConnection)
yield mock.patch.object(cpool.HTTPConnectionPool, 'ConnectionCls', _HTTPConnection)
yield mock.patch.object(cpool.HTTPSConnectionPool, 'ConnectionCls', _HTTPSConnection)
if hasattr(cpool.HTTPConnectionPool, 'ConnectionCls'):
yield mock.patch.object(cpool.HTTPConnectionPool, 'ConnectionCls', _HTTPConnection)
yield mock.patch.object(cpool.HTTPSConnectionPool, 'ConnectionCls', _HTTPSConnection)
try:
import httplib2 as cpool

View File

@@ -11,10 +11,14 @@ def deserialize(cassette_string):
def serialize(cassette_dict):
try:
return json.dumps(cassette_dict, indent=4)
except UnicodeDecodeError:
except UnicodeDecodeError as original:
raise UnicodeDecodeError(
"Error serializing cassette to JSON. ",
"Does this HTTP interaction contain binary data? ",
"If so, use a different serializer (like the yaml serializer) ",
"for this request"
original.encoding,
b"Error serializing cassette to JSON",
original.start,
original.end,
original.args[-1] +
("Does this HTTP interaction contain binary data? "
"If so, use a different serializer (like the yaml serializer) "
"for this request?")
)

View File

@@ -76,7 +76,7 @@ class VCRHTTPResponse(HTTPResponse):
self._closed = False
headers = self.recorded_response['headers']
self.msg = parse_headers(headers)
self.headers = self.msg = parse_headers(headers)
self.length = compat.get_header(self.msg, 'content-length') or None
@@ -217,11 +217,15 @@ class VCRConnection(object):
response = self.cassette.play_response(self._vcr_request)
return VCRHTTPResponse(response)
else:
if self.cassette.write_protected and self.cassette.filter_request(self._vcr_request):
if self.cassette.write_protected and self.cassette.filter_request(
self._vcr_request
):
raise CannotOverwriteExistingCassetteException(
"No match for the request (%r) was found. "
"Can't overwrite existing cassette (%r) in "
"your current record mode (%r)."
% (self.cassette._path, self.cassette.record_mode)
% (self._vcr_request, self.cassette._path,
self.cassette.record_mode)
)
# Otherwise, we should send the request, then get the response
@@ -310,3 +314,4 @@ class VCRHTTPSConnection(VCRConnection):
'''A Mocked class for HTTPS requests'''
_baseclass = HTTPSConnection
_protocol = 'https'
is_verified = True