1
0
mirror of https://github.com/kevin1024/vcrpy.git synced 2025-12-09 17:15:35 +00:00

Compare commits

..

11 Commits

Author SHA1 Message Date
Kevin McCarthy
1bd3fbd2c6 bump version 2014-09-01 15:50:25 -10:00
Kevin McCarthy
cd715f37c6 Fix requests stub. Closes #94 2014-09-01 15:48:57 -10:00
Kevin McCarthy
9a1147196a getheader() in stubs should be case-insensitive 2014-08-01 16:28:21 -10:00
Kevin McCarthy
a23c5d8508 Merge pull request #87 from hartsock/master
Python 3: print_function
2014-07-24 18:10:24 -10:00
Shawn Hartsock
868a974900 Python 3: print_function
Use print function if you must print, this lets us use the
library in python 3 environments.

partial: https://github.com/kevin1024/vcrpy/issues/86
2014-07-24 16:33:47 -04:00
Kevin McCarthy
c56de472cd Remove extra colon in README
Closes #82
2014-07-07 16:02:29 -10:00
Kevin McCarthy
c6590f2caf bump version 2014-05-17 13:09:10 -10:00
Kevin McCarthy
70abc5058c requests 2.3 compat 2014-05-17 12:58:31 -10:00
Kevin McCarthy
0c1f1e2479 Version bump to 1.0.1 2014-05-17 09:44:02 -10:00
Kevin McCarthy
8d90dba16c Ignore requests before trying to play them
Closes #79
2014-05-17 09:34:50 -10:00
Kevin McCarthy
3072c56ed2 Update README.md 2014-05-12 09:22:09 -10:00
12 changed files with 228 additions and 70 deletions

View File

@@ -5,7 +5,9 @@ env:
- secure: AifoKzwhjV94cmcQZrdQmqRu/9rkZZvWpwBv1daeAQpLOKFPGsOm3D+x2cSw9+iCfkgDZDfqQVv1kCaFVxTll8v8jTq5SJdqEY0NmGWbj/UkNtShh609oRDsuzLxAEwtVKYjf/h8K2BRea+bl1tGkwZ2vtmYS6dxNlAijjWOfds= - secure: AifoKzwhjV94cmcQZrdQmqRu/9rkZZvWpwBv1daeAQpLOKFPGsOm3D+x2cSw9+iCfkgDZDfqQVv1kCaFVxTll8v8jTq5SJdqEY0NmGWbj/UkNtShh609oRDsuzLxAEwtVKYjf/h8K2BRea+bl1tGkwZ2vtmYS6dxNlAijjWOfds=
- secure: LBSEg/gMj4u4Hrpo3zs6Y/1mTpd2RtcN49mZIFgTdbJ9IhpiNPqcEt647Lz94F9Eses2x2WbNuKqZKZZReY7QLbEzU1m0nN5jlaKrjcG5NR5clNABfFFyhgc0jBikyS4abAG8jc2efeaTrFuQwdoF4sE8YiVrkiVj2X5Xoi6sBk= - secure: LBSEg/gMj4u4Hrpo3zs6Y/1mTpd2RtcN49mZIFgTdbJ9IhpiNPqcEt647Lz94F9Eses2x2WbNuKqZKZZReY7QLbEzU1m0nN5jlaKrjcG5NR5clNABfFFyhgc0jBikyS4abAG8jc2efeaTrFuQwdoF4sE8YiVrkiVj2X5Xoi6sBk=
matrix: matrix:
- WITH_LIB="requests2.x" - WITH_LIB="requests2.2"
- WITH_LIB="requests2.3"
- WITH_LIB="requests2.4"
- WITH_LIB="requests1.x" - WITH_LIB="requests1.x"
- WITH_LIB="httplib2" - WITH_LIB="httplib2"
- WITH_LIB="boto" - WITH_LIB="boto"
@@ -15,15 +17,22 @@ matrix:
exclude: exclude:
- env: WITH_LIB="boto" - env: WITH_LIB="boto"
python: 3.3 python: 3.3
- env: WITH_LIB="boto"
python: 3.4
- env: WITH_LIB="requests1.x"
python: 3.4
python: python:
- 2.6 - 2.6
- 2.7 - 2.7
- 3.3 - 3.3
- 3.4
- pypy - pypy
install: install:
- pip install PyYAML pytest pytest-localserver --use-mirrors - pip install PyYAML pytest pytest-localserver --use-mirrors
- if [ $WITH_LIB = "requests1.x" ] ; then pip install requests==1.2.3; fi - 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 = "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 = "httplib2" ] ; then pip install httplib2; fi - if [ $WITH_LIB = "httplib2" ] ; then pip install httplib2; fi
- if [ $WITH_LIB = "boto" ] ; then pip install boto; fi - if [ $WITH_LIB = "boto" ] ; then pip install boto; fi
script: python setup.py test script: python setup.py test

View File

@@ -51,7 +51,7 @@ You can also use VCR.py as a decorator. The same request above would look like
this: this:
```python ```python
@vcr.use_cassette('fixtures/vcr_cassettes/synopsis.yaml'): @vcr.use_cassette('fixtures/vcr_cassettes/synopsis.yaml')
def test_iana(): def test_iana():
response = urllib2.urlopen('http://www.iana.org/domains/reserved').read() response = urllib2.urlopen('http://www.iana.org/domains/reserved').read()
assert 'Example domains' in response assert 'Example domains' in response
@@ -423,14 +423,21 @@ API in version 1.0.x
## Changelog ## Changelog
* 1.0.3: Fix an issue with requests 2.4 and make sure case sensitivity is
consistent across python versions
* 1.0.2: Fix an issue with requests 2.3
* 1.0.1: Fix a bug with the new ignore requests feature and the once
record mode
* 1.0.0: _BACKWARDS INCOMPATIBLE_: Please see the 'upgrade' section in the * 1.0.0: _BACKWARDS INCOMPATIBLE_: Please see the 'upgrade' section in the
README. Add support for filtering sensitive data from requests, matching README. Take a look at the matcher section as well, you might want to
query strings after the order changes and improving the built-in matchers, update your `match_on` settings. Add support for filtering sensitive
(thanks to @mshytikov), support for ignoring requests to certain hosts, data from requests, matching query strings after the order changes and
bump supported Python3 version to 3.4, fix some bugs with Boto support improving the built-in matchers, (thanks to @mshytikov), support for
(thanks @marusich), fix error with URL field capitalization in README ignoring requests to certain hosts, bump supported Python3 version to
(thanks @simon-weber), added some log messages to help with debugging, 3.4, fix some bugs with Boto support (thanks @marusich), fix error with
added `all_played` property on cassette (thanks @mshytikov) URL field capitalization in README (thanks @simon-weber), added some log
messages to help with debugging, added `all_played` property on cassette
(thanks @mshytikov)
* 0.7.0: VCR.py now supports Python 3! (thanks @asundg) Also I refactored * 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 the stub connections quite a bit to add support for the putrequest and
putheader calls. This version also adds support for httplib2 (thanks putheader calls. This version also adds support for httplib2 (thanks

View File

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

View File

@@ -39,3 +39,17 @@ def test_ignore_localhost_and_httpbin(tmpdir, httpserver):
urlopen('http://httpbin.org') urlopen('http://httpbin.org')
urlopen(httpserver.url) urlopen(httpserver.url)
assert len(cass) == 0 assert len(cass) == 0
def test_ignore_localhost_twice(tmpdir, httpserver):
httpserver.serve_content('Hello!')
cass_file = str(tmpdir.join('filter_qs.yaml'))
with vcr.use_cassette(cass_file, ignore_localhost=True) as cass:
urlopen(httpserver.url)
assert len(cass) == 0
urlopen('http://httpbin.org')
assert len(cass) == 1
with vcr.use_cassette(cass_file, ignore_localhost=True) as cass:
assert len(cass) == 1
urlopen(httpserver.url)
urlopen('http://httpbin.org')
assert len(cass) == 1

View File

@@ -145,3 +145,7 @@ def test_session_and_connection_close(tmpdir, scheme):
resp = session.get('http://httpbin.org/get', headers={'Connection': 'close'}) resp = session.get('http://httpbin.org/get', headers={'Connection': 'close'})
resp = session.get('http://httpbin.org/get', headers={'Connection': 'close'}) resp = session.get('http://httpbin.org/get', headers={'Connection': 'close'})
def test_https_with_cert_validation_disabled(tmpdir):
with vcr.use_cassette(str(tmpdir.join('cert_validation_disabled.yaml'))):
requests.get('https://httpbin.org', verify=False)

View File

@@ -0,0 +1,26 @@
import vcr
import six.moves.http_client as httplib
def _headers_are_case_insensitive():
conn = httplib.HTTPConnection('httpbin.org')
conn.request('GET', "/cookies/set?k1=v1")
r1 = conn.getresponse()
cookie_data1 = r1.getheader('set-cookie')
conn = httplib.HTTPConnection('httpbin.org')
conn.request('GET', "/cookies/set?k1=v1")
r2 = conn.getresponse()
cookie_data2 = r2.getheader('Set-Cookie')
return cookie_data1 == cookie_data2
def test_case_insensitivity(tmpdir):
testfile = str(tmpdir.join('case_insensitivity.yml'))
# check if headers are case insensitive outside of vcrpy
outside = _headers_are_case_insensitive()
with vcr.use_cassette(testfile):
# check if headers are case insensitive inside of vcrpy
inside = _headers_are_case_insensitive()
# check if headers are case insensitive after vcrpy deserializes headers
inside2 = _headers_are_case_insensitive()
# behavior should be the same both inside and outside
assert outside == inside == inside2

153
tox.ini
View File

@@ -10,15 +10,22 @@ envlist =
py33, py33,
py34, py34,
pypy, pypy,
py26requests, py26requests24,
py27requests, py27requests24,
py34requests, py34requests24,
pypyrequests, pypyrequests24,
py26oldrequests, py26requests23,
py27oldrequests, py27requests23,
py33oldrequests, py34requests23,
py34oldrequests, pypyrequests23,
pypyoldrequests, py26requests22,
py27requests22,
py34requests22,
pypyrequests22,
py26requests1,
py27requests1,
py33requests1,
pypyrequests1,
py26httplib2, py26httplib2,
py27httplib2, py27httplib2,
py33httplib2, py33httplib2,
@@ -34,7 +41,7 @@ deps =
pytest-localserver pytest-localserver
PyYAML PyYAML
[testenv:py26oldrequests] [testenv:py26requests1]
basepython = python2.6 basepython = python2.6
deps = deps =
mock mock
@@ -43,7 +50,7 @@ deps =
PyYAML PyYAML
requests==1.2.3 requests==1.2.3
[testenv:py27oldrequests] [testenv:py27requests1]
basepython = python2.7 basepython = python2.7
deps = deps =
mock mock
@@ -52,7 +59,7 @@ deps =
PyYAML PyYAML
requests==1.2.3 requests==1.2.3
[testenv:py33oldrequests] [testenv:py33requests1]
basepython = python3.3 basepython = python3.3
deps = deps =
mock mock
@@ -61,16 +68,7 @@ deps =
PyYAML PyYAML
requests==1.2.3 requests==1.2.3
[testenv:py34oldrequests] [testenv:pypyrequests1]
basepython = python3.4
deps =
mock
pytest
pytest-localserver
PyYAML
requests==1.2.3
[testenv:pypyoldrequests]
basepython = pypy basepython = pypy
deps = deps =
mock mock
@@ -79,50 +77,141 @@ deps =
PyYAML PyYAML
requests==1.2.3 requests==1.2.3
[testenv:py26requests] [testenv:py26requests24]
basepython = python2.6 basepython = python2.6
deps = deps =
mock mock
pytest pytest
pytest-localserver pytest-localserver
PyYAML PyYAML
requests requests==2.4.0
[testenv:py27requests] [testenv:py27requests24]
basepython = python2.7 basepython = python2.7
deps = deps =
mock mock
pytest pytest
pytest-localserver pytest-localserver
PyYAML PyYAML
requests requests==2.4.0
[testenv:py33requests] [testenv:py33requests24]
basepython = python3.4 basepython = python3.4
deps = deps =
mock mock
pytest pytest
pytest-localserver pytest-localserver
PyYAML PyYAML
requests requests==2.4.0
[testenv:py34requests] [testenv:py34requests24]
basepython = python3.4 basepython = python3.4
deps = deps =
mock mock
pytest pytest
pytest-localserver pytest-localserver
PyYAML PyYAML
requests requests==2.4.0
[testenv:pypyrequests] [testenv:pypyrequests24]
basepython = pypy basepython = pypy
deps = deps =
mock mock
pytest pytest
pytest-localserver pytest-localserver
PyYAML PyYAML
requests requests==2.4.0
[testenv:py26requests23]
basepython = python2.6
deps =
mock
pytest
pytest-localserver
PyYAML
requests==2.3.0
[testenv:py27requests23]
basepython = python2.7
deps =
mock
pytest
pytest-localserver
PyYAML
requests==2.3.0
[testenv:py33requests23]
basepython = python3.4
deps =
mock
pytest
pytest-localserver
PyYAML
requests==2.3.0
[testenv:py34requests23]
basepython = python3.4
deps =
mock
pytest
pytest-localserver
PyYAML
requests==2.3.0
[testenv:pypyrequests23]
basepython = pypy
deps =
mock
pytest
pytest-localserver
PyYAML
requests==2.3.0
[testenv:py26requests22]
basepython = python2.6
deps =
mock
pytest
pytest-localserver
PyYAML
requests==2.2.1
[testenv:py27requests22]
basepython = python2.7
deps =
mock
pytest
pytest-localserver
PyYAML
requests==2.2.1
[testenv:py33requests22]
basepython = python3.4
deps =
mock
pytest
pytest-localserver
PyYAML
requests==2.2.1
[testenv:py34requests22]
basepython = python3.4
deps =
mock
pytest
pytest-localserver
PyYAML
requests==2.2.1
[testenv:pypyrequests22]
basepython = pypy
deps =
mock
pytest
pytest-localserver
PyYAML
requests==2.2.1
[testenv:py26httplib2] [testenv:py26httplib2]
basepython = python2.6 basepython = python2.6

View File

@@ -80,15 +80,18 @@ class Cassette(ContextDecorator):
return self.rewound and self.record_mode == 'once' or \ return self.rewound and self.record_mode == 'once' or \
self.record_mode == 'none' self.record_mode == 'none'
def append(self, request, response): def _filter_request(self, request):
'''Add a request, response pair to this cassette''' return filter_request(
request = filter_request(
request=request, request=request,
filter_headers=self._filter_headers, filter_headers=self._filter_headers,
filter_query_parameters=self._filter_query_parameters, filter_query_parameters=self._filter_query_parameters,
before_record=self._before_record, before_record=self._before_record,
ignore_hosts=self._ignore_hosts ignore_hosts=self._ignore_hosts
) )
def append(self, request, response):
'''Add a request, response pair to this cassette'''
request = self._filter_request(request)
if not request: if not request:
return return
self.data.append((request, response)) self.data.append((request, response))
@@ -99,19 +102,19 @@ class Cassette(ContextDecorator):
internal API, returns an iterator with all responses matching internal API, returns an iterator with all responses matching
the request. the request.
""" """
request = filter_request( request = self._filter_request(request)
request=request,
filter_headers=self._filter_headers,
filter_query_parameters=self._filter_query_parameters,
before_record=self._before_record,
ignore_hosts=self._ignore_hosts
)
if not request: if not request:
return return
for index, (stored_request, response) in enumerate(self.data): for index, (stored_request, response) in enumerate(self.data):
if requests_match(request, stored_request, self._match_on): if requests_match(request, stored_request, self._match_on):
yield index, response yield index, response
def can_play_response_for(self, request):
request = self._filter_request(request)
return request and request in self and \
self.record_mode != 'all' and \
self.rewound
def play_response(self, request): def play_response(self, request):
''' '''
Get the response corresponding to a request, but only if it Get the response corresponding to a request, but only if it

View File

@@ -1,3 +1,4 @@
from __future__ import print_function
from operator import itemgetter from operator import itemgetter
from heapq import nlargest from heapq import nlargest
from itertools import repeat, ifilter from itertools import repeat, ifilter
@@ -189,5 +190,5 @@ class Counter(dict):
if __name__ == '__main__': if __name__ == '__main__':
import doctest import doctest
print doctest.testmod() print(doctest.testmod())

View File

@@ -56,15 +56,16 @@ def install(cassette):
# patch requests v1.x # patch requests v1.x
try: try:
import requests.packages.urllib3.connectionpool as cpool import requests.packages.urllib3.connectionpool as cpool
from .stubs.requests_stubs import VCRVerifiedHTTPSConnection from .stubs.requests_stubs import VCRRequestsHTTPConnection, VCRRequestsHTTPSConnection
cpool.VerifiedHTTPSConnection = VCRVerifiedHTTPSConnection cpool.VerifiedHTTPSConnection = VCRRequestsHTTPSConnection
cpool.HTTPConnection = VCRRequestsHTTPConnection
cpool.VerifiedHTTPSConnection.cassette = cassette cpool.VerifiedHTTPSConnection.cassette = cassette
cpool.HTTPConnection = VCRHTTPConnection cpool.HTTPConnection = VCRHTTPConnection
cpool.HTTPConnection.cassette = cassette cpool.HTTPConnection.cassette = cassette
# patch requests v2.x # patch requests v2.x
cpool.HTTPConnectionPool.ConnectionCls = VCRHTTPConnection cpool.HTTPConnectionPool.ConnectionCls = VCRRequestsHTTPConnection
cpool.HTTPConnectionPool.cassette = cassette cpool.HTTPConnectionPool.cassette = cassette
cpool.HTTPSConnectionPool.ConnectionCls = VCRHTTPSConnection cpool.HTTPSConnectionPool.ConnectionCls = VCRRequestsHTTPSConnection
cpool.HTTPSConnectionPool.cassette = cassette cpool.HTTPSConnectionPool.cassette = cassette
except ImportError: # pragma: no cover except ImportError: # pragma: no cover
pass pass

View File

@@ -111,8 +111,8 @@ class VCRHTTPResponse(HTTPResponse):
return compat.get_header_items(message) return compat.get_header_items(message)
def getheader(self, header, default=None): def getheader(self, header, default=None):
headers = dict(((k, v) for k, v in self.getheaders())) headers = dict(((k.lower(), v) for k, v in self.getheaders()))
return headers.get(header, default) return headers.get(header.lower(), default)
class VCRConnection: class VCRConnection:
@@ -204,9 +204,7 @@ class VCRConnection:
'''Retrieve a the response''' '''Retrieve a the response'''
# Check to see if the cassette has a response for this request. If so, # Check to see if the cassette has a response for this request. If so,
# then return it # then return it
if self._vcr_request in self.cassette and \ if self.cassette.can_play_response_for(self._vcr_request):
self.cassette.record_mode != "all" and \
self.cassette.rewound:
log.info( log.info(
"Playing response for {0} from cassette".format( "Playing response for {0} from cassette".format(
self._vcr_request self._vcr_request
@@ -215,7 +213,7 @@ class VCRConnection:
response = self.cassette.play_response(self._vcr_request) response = self.cassette.play_response(self._vcr_request)
return VCRHTTPResponse(response) return VCRHTTPResponse(response)
else: else:
if self.cassette.write_protected: if self.cassette.write_protected and self.cassette._filter_request(self._vcr_request):
raise CannotOverwriteExistingCassetteException( raise CannotOverwriteExistingCassetteException(
"Can't overwrite existing cassette (%r) in " "Can't overwrite existing cassette (%r) in "
"your current record mode (%r)." "your current record mode (%r)."
@@ -264,9 +262,7 @@ class VCRConnection:
""" """
if hasattr(self, '_vcr_request') and \ if hasattr(self, '_vcr_request') and \
self._vcr_request in self.cassette and \ self.cassette.can_play_response_for(self._vcr_request):
self.cassette.record_mode != "all" and \
self.cassette.rewound:
# We already have a response we are going to play, don't # We already have a response we are going to play, don't
# actually connect # actually connect
return return
@@ -289,6 +285,9 @@ class VCRConnection:
self.real_connection.sock = value self.real_connection.sock = value
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
if six.PY3:
kwargs.pop('strict', None) # apparently this is gone in py3
# need to temporarily reset here because the real connection # need to temporarily reset here because the real connection
# inherits from the thing that we are mocking out. Take out # inherits from the thing that we are mocking out. Take out
# the reset if you want to see what I mean :) # the reset if you want to see what I mean :)

View File

@@ -1,8 +1,13 @@
'''Stubs for requests''' '''Stubs for requests'''
from requests.packages.urllib3.connectionpool import VerifiedHTTPSConnection from requests.packages.urllib3.connectionpool import HTTPConnection, VerifiedHTTPSConnection
from ..stubs import VCRHTTPSConnection from ..stubs import VCRHTTPConnection, VCRHTTPSConnection
# urllib3 defines its own HTTPConnection classes, which requests goes ahead and assumes
# you're using. It includes some polyfills for newer features missing in older pythons.
class VCRVerifiedHTTPSConnection(VCRHTTPSConnection, VerifiedHTTPSConnection): class VCRRequestsHTTPConnection(VCRHTTPConnection, HTTPConnection):
_baseclass = HTTPConnection
class VCRRequestsHTTPSConnection(VCRHTTPSConnection, VerifiedHTTPSConnection):
_baseclass = VerifiedHTTPSConnection _baseclass = VerifiedHTTPSConnection