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

Compare commits

...

25 Commits

Author SHA1 Message Date
Ivan Malison
efe6744eda v1.7.3 2015-08-23 18:10:01 -07:00
Ivan 'Goat' Malison
58f4b98f7f Merge pull request #191 from agriffis/trivial-fixes
Trivial cleanups and one bugfix
2015-08-23 13:33:34 -07:00
Aron Griffis
3305f0ca7d Repair a docstring 2015-08-23 16:33:25 -04:00
Aron Griffis
7f02d65dd9 Make hosts_to_ignore a set() earlier for clarity. 2015-08-23 16:00:52 -04:00
Aron Griffis
3e5553c56a Don't drop passed-in before_record_response
This is an actual bugfix. If before_record_response is passed into VCR as
an iterable then it won't be included in filter_functions. This commit
repairs the logic to separate the tests (just as it's already done for
before_record_request).

Also use .extend() rather than looping on .append()
2015-08-23 16:00:52 -04:00
Aron Griffis
a569dd4dc8 Raise KeyError with message instead of print, just like in get_matchers below 2015-08-23 12:37:01 -04:00
Aron Griffis
eb1cdad03a self._before_record_response can never be falsy in Cassette (just like self._before_record_request above it) 2015-08-23 12:37:01 -04:00
Aron Griffis
08bb3bd187 Remove an extra space 2015-08-23 12:37:01 -04:00
Ivan Malison
ae5580c8f9 style changes in test_vcr.py 2015-08-22 18:59:59 -07:00
Ivan Malison
f342f92f03 additional_matchers test 2015-08-22 18:58:12 -07:00
Ivan Malison
be3bf39161 Style changes in vcr/config.py 2015-08-22 18:48:04 -07:00
Ivan Malison
29d37e410a Add additional_matchers to use_cassette
Closes #188.
2015-08-22 18:06:13 -07:00
Ivan Malison
8b7e6c0ab8 v1.7.2 2015-08-18 17:00:45 -07:00
Ivan Malison
bd7c6ed03f Update comment about reentrance on cassette.py 2015-08-18 16:17:41 -07:00
Ivan 'Goat' Malison
1e414826e7 Merge pull request #187 from abhinav/master
Set request_time on Tornadoo HTTPResponses
2015-08-18 16:10:42 -07:00
Abhinav Gupta
1e1c093b3c Set request_time on Tornadoo HTTPResponses 2015-08-18 15:53:35 -07:00
Ivan 'Goat' Malison
bb8f563135 Merge pull request #186 from ByteInternet/capture-effective-url
Capture effective url in Tornado
2015-08-18 01:55:24 -07:00
Maarten van Schaik
ca3200d96e Add test for urllib2 2015-08-14 12:42:17 +02:00
Maarten van Schaik
04b5978adc Add effective url test for httplib2 2015-08-14 12:37:34 +02:00
Maarten van Schaik
01f1f9fdc1 Verify effective_url is ok 2015-08-14 12:29:50 +02:00
Maarten van Schaik
a82e8628c2 Requests actually stores redirected request 2015-08-14 12:28:41 +02:00
Maarten van Schaik
7d68f0577a Capture effective URL in tornado 2015-08-14 12:08:57 +02:00
Ivan Malison
d0aa5fddb7 1.7.1 2015-08-12 12:21:29 -07:00
Kevin McCarthy
e54aeadc68 Merge pull request #184 from abhinav/master
For Tornado AsyncHTTPClient, replace the methods instead of the class.
2015-08-12 09:16:22 -10:00
Abhinav Gupta
c4a33d1cff For Tornado AsyncHTTPClient, replace the methods instead of the class.
This makes it so patching works even if the user has a reference to, or an
instance of the original unpatched AsyncHTTPClient class.

Fixes #183.
2015-08-12 10:51:08 -07:00
13 changed files with 285 additions and 130 deletions

View File

@@ -14,17 +14,18 @@ library <https://github.com/vcr/vcr>`__.
What it does What it does
------------ ------------
VCR.py simplifies and speeds up tests that make HTTP requests. The first VCR.py simplifies and speeds up tests that make HTTP requests. The
time you run code that is inside a VCR.py context manager or decorated first time you run code that is inside a VCR.py context manager or
function, VCR.py records all HTTP interactions that take place through decorated function, VCR.py records all HTTP interactions that take
the libraries it supports and serializes and writes them to a flat file place through the libraries it supports and serializes and writes them
(in yaml format by default). This flat file is called a cassette. When to a flat file (in yaml format by default). This flat file is called a
the relevant peice of code is executed again, VCR.py will read the cassette. When the relevant peice of code is executed again, VCR.py
serialized requests and responses from the aforementioned cassette file, will read the serialized requests and responses from the
and intercept any HTTP requests that it recognizes from the original aforementioned cassette file, and intercept any HTTP requests that it
test run and return responses that corresponded to those requests. This recognizes from the original test run and return the responses that
means that the requests will not actually result in HTTP traffic, which corresponded to those requests. This means that the requests will not
confers several benefits including: actually result in HTTP traffic, which confers several benefits
including:
- The ability to work offline - The ability to work offline
- Completely deterministic tests - Completely deterministic tests
@@ -608,6 +609,13 @@ new API in version 1.0.x
Changelog Changelog
--------- ---------
- 1.7.3 [#188] ``additional_matchers`` kwarg on ``use_casstte``.
[#191] Actually support passing multiple before_record_request
functions (thanks @agriffis).
- 1.7.2 [#186] Get effective_url in tornado (thanks @mvschaik), [#187]
Set request_time on Response object in tornado (thanks @abhinav).
- 1.7.1 [#183] Patch ``fetch_impl`` instead of the entire HTTPClient
class for Tornado (thanks @abhinav).
- 1.7.0 [#177] Properly support coroutine/generator decoration. [#178] - 1.7.0 [#177] Properly support coroutine/generator decoration. [#178]
Support distribute (thanks @graingert). [#163] Make compatibility Support distribute (thanks @graingert). [#163] Make compatibility
between python2 and python3 recorded cassettes more robust (thanks between python2 and python3 recorded cassettes more robust (thanks

View File

@@ -9,6 +9,7 @@ import pkg_resources
long_description = open('README.rst', 'r').read() long_description = open('README.rst', 'r').read()
class PyTest(TestCommand): class PyTest(TestCommand):
def finalize_options(self): def finalize_options(self):
@@ -23,7 +24,7 @@ class PyTest(TestCommand):
sys.exit(errno) sys.exit(errno)
install_requires=['PyYAML', 'wrapt', 'six>=1.5'] install_requires = ['PyYAML', 'wrapt', 'six>=1.5']
extras_require = { extras_require = {
@@ -47,9 +48,10 @@ except Exception:
if key.startswith(':'): if key.startswith(':'):
install_requires.extend(value) install_requires.extend(value)
setup( setup(
name='vcrpy', name='vcrpy',
version='1.7.0', version='1.7.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

@@ -56,6 +56,17 @@ def test_response_headers(scheme, tmpdir):
resp, _ = httplib2.Http().request(url) resp, _ = httplib2.Http().request(url)
assert set(headers) == set(resp.items()) assert set(headers) == set(resp.items())
def test_effective_url(scheme, tmpdir):
'''Ensure that the effective_url is captured'''
url = scheme + '://httpbin.org/redirect-to?url=/html'
with vcr.use_cassette(str(tmpdir.join('headers.yaml'))) as cass:
resp, _ = httplib2.Http().request(url)
effective_url = resp['content-location']
assert effective_url == scheme + '://httpbin.org/html'
with vcr.use_cassette(str(tmpdir.join('headers.yaml'))) as cass:
resp, _ = httplib2.Http().request(url)
assert effective_url == resp['content-location']
def test_multiple_requests(scheme, tmpdir): def test_multiple_requests(scheme, tmpdir):
'''Ensure that we can cache multiple requests''' '''Ensure that we can cache multiple requests'''

View File

@@ -44,6 +44,15 @@ def test_body(tmpdir, scheme):
with vcr.use_cassette(str(tmpdir.join('body.yaml'))): with vcr.use_cassette(str(tmpdir.join('body.yaml'))):
assert content == requests.get(url).content assert content == requests.get(url).content
def test_effective_url(scheme, tmpdir):
'''Ensure that the effective_url is captured'''
url = scheme + '://httpbin.org/redirect-to?url=/html'
with vcr.use_cassette(str(tmpdir.join('url.yaml'))):
effective_url = requests.get(url).url
assert effective_url == scheme + '://httpbin.org/html'
with vcr.use_cassette(str(tmpdir.join('url.yaml'))):
assert effective_url == requests.get(url).url
def test_auth(tmpdir, scheme): def test_auth(tmpdir, scheme):
'''Ensure that we can handle basic auth''' '''Ensure that we can handle basic auth'''

View File

@@ -81,6 +81,17 @@ def test_body(get_client, tmpdir, scheme):
assert content == (yield get(get_client(), url)).body assert content == (yield get(get_client(), url)).body
assert 1 == cass.play_count assert 1 == cass.play_count
@pytest.mark.gen_test
def test_effective_url(get_client, scheme, tmpdir):
'''Ensure that the effective_url is captured'''
url = scheme + '://httpbin.org/redirect-to?url=/html'
with vcr.use_cassette(str(tmpdir.join('url.yaml'))):
effective_url = (yield get(get_client(), url)).effective_url
assert effective_url == scheme + '://httpbin.org/html'
with vcr.use_cassette(str(tmpdir.join('url.yaml'))) as cass:
assert effective_url == (yield get(get_client(), url)).effective_url
assert 1 == cass.play_count
@pytest.mark.gen_test @pytest.mark.gen_test
def test_auth(get_client, tmpdir, scheme): def test_auth(get_client, tmpdir, scheme):
@@ -298,3 +309,47 @@ def test_tornado_exception_can_be_caught(get_client):
yield get(get_client(), 'http://httpbin.org/status/404') yield get(get_client(), 'http://httpbin.org/status/404')
except http.HTTPError as e: except http.HTTPError as e:
assert e.code == 404 assert e.code == 404
@pytest.mark.gen_test
def test_existing_references_get_patched(tmpdir):
from tornado.httpclient import AsyncHTTPClient
with vcr.use_cassette(str(tmpdir.join('data.yaml'))):
client = AsyncHTTPClient()
yield get(client, 'http://httpbin.org/get')
with vcr.use_cassette(str(tmpdir.join('data.yaml'))) as cass:
yield get(client, 'http://httpbin.org/get')
assert cass.play_count == 1
@pytest.mark.gen_test
def test_existing_instances_get_patched(get_client, tmpdir):
'''Ensure that existing instances of AsyncHTTPClient get patched upon
entering VCR context.'''
client = get_client()
with vcr.use_cassette(str(tmpdir.join('data.yaml'))):
yield get(client, 'http://httpbin.org/get')
with vcr.use_cassette(str(tmpdir.join('data.yaml'))) as cass:
yield get(client, 'http://httpbin.org/get')
assert cass.play_count == 1
@pytest.mark.gen_test
def test_request_time_is_set(get_client, tmpdir):
'''Ensures that the request_time on HTTPResponses is set.'''
with vcr.use_cassette(str(tmpdir.join('data.yaml'))):
client = get_client()
response = yield get(client, 'http://httpbin.org/get')
assert response.request_time is not None
with vcr.use_cassette(str(tmpdir.join('data.yaml'))) as cass:
client = get_client()
response = yield get(client, 'http://httpbin.org/get')
assert response.request_time is not None
assert cass.play_count == 1

View File

@@ -49,6 +49,15 @@ def test_response_headers(scheme, tmpdir):
open2 = urlopen(url).info().items() open2 = urlopen(url).info().items()
assert sorted(open1) == sorted(open2) assert sorted(open1) == sorted(open2)
def test_effective_url(scheme, tmpdir):
'''Ensure that the effective_url is captured'''
url = scheme + '://httpbin.org/redirect-to?url=/html'
with vcr.use_cassette(str(tmpdir.join('headers.yaml'))) as cass:
effective_url = urlopen(url).geturl()
assert effective_url == scheme + '://httpbin.org/html'
with vcr.use_cassette(str(tmpdir.join('headers.yaml'))) as cass:
assert effective_url == urlopen(url).geturl()
def test_multiple_requests(scheme, tmpdir): def test_multiple_requests(scheme, tmpdir):
'''Ensure that we can cache multiple requests''' '''Ensure that we can cache multiple requests'''

View File

@@ -15,9 +15,11 @@ def test_vcr_use_cassette():
'vcr.cassette.Cassette.load', 'vcr.cassette.Cassette.load',
return_value=mock.MagicMock(inject=False) return_value=mock.MagicMock(inject=False)
) as mock_cassette_load: ) as mock_cassette_load:
@test_vcr.use_cassette('test') @test_vcr.use_cassette('test')
def function(): def function():
pass pass
assert mock_cassette_load.call_count == 0 assert mock_cassette_load.call_count == 0
function() function()
assert mock_cassette_load.call_args[1]['record_mode'] is record_mode assert mock_cassette_load.call_args[1]['record_mode'] is record_mode
@@ -38,9 +40,11 @@ def test_vcr_use_cassette():
def test_vcr_before_record_request_params(): def test_vcr_before_record_request_params():
base_path = 'http://httpbin.org/' base_path = 'http://httpbin.org/'
def before_record_cb(request): def before_record_cb(request):
if request.path != '/get': if request.path != '/get':
return request return request
test_vcr = VCR(filter_headers=('cookie',), before_record_request=before_record_cb, test_vcr = VCR(filter_headers=('cookie',), before_record_request=before_record_cb,
ignore_hosts=('www.test.com',), ignore_localhost=True, ignore_hosts=('www.test.com',), ignore_localhost=True,
filter_query_parameters=('foo',)) filter_query_parameters=('foo',))
@@ -53,8 +57,12 @@ def test_vcr_before_record_request_params():
assert cassette.filter_request( assert cassette.filter_request(
Request('GET', base_path + '?foo=bar', '', Request('GET', base_path + '?foo=bar', '',
{'cookie': 'test', 'other': 'fun'})).headers == {'other': 'fun'} {'cookie': 'test', 'other': 'fun'})).headers == {'other': 'fun'}
assert cassette.filter_request(Request('GET', base_path + '?foo=bar', '', assert cassette.filter_request(
{'cookie': 'test', 'other': 'fun'})).headers == {'other': 'fun'} Request(
'GET', base_path + '?foo=bar', '',
{'cookie': 'test', 'other': 'fun'}
)
).headers == {'other': 'fun'}
assert cassette.filter_request(Request('GET', 'http://www.test.com' + '?foo=bar', '', assert cassette.filter_request(Request('GET', 'http://www.test.com' + '?foo=bar', '',
{'cookie': 'test', 'other': 'fun'})) is None {'cookie': 'test', 'other': 'fun'})) is None
@@ -64,6 +72,32 @@ def test_vcr_before_record_request_params():
assert cassette.filter_request(Request('GET', base_path + 'get', '', {})) is not None assert cassette.filter_request(Request('GET', base_path + 'get', '', {})) is not None
def test_vcr_before_record_response_iterable():
# Regression test for #191
request = Request('GET', '/', '', {})
response = object() # just can't be None
# Prevent actually saving the cassette
with mock.patch('vcr.cassette.save_cassette'):
# Baseline: non-iterable before_record_response should work
mock_filter = mock.Mock()
vcr = VCR(before_record_response=mock_filter)
with vcr.use_cassette('test') as cassette:
assert mock_filter.call_count == 0
cassette.append(request, response)
assert mock_filter.call_count == 1
# Regression test: iterable before_record_response should work too
mock_filter = mock.Mock()
vcr = VCR(before_record_response=(mock_filter,))
with vcr.use_cassette('test') as cassette:
assert mock_filter.call_count == 0
cassette.append(request, response)
assert mock_filter.call_count == 1
@pytest.fixture @pytest.fixture
def random_fixture(): def random_fixture():
return 1 return 1
@@ -103,6 +137,7 @@ def test_custom_patchers():
def test_inject_cassette(): def test_inject_cassette():
vcr = VCR(inject_cassette=True) vcr = VCR(inject_cassette=True)
@vcr.use_cassette('test', record_mode='once') @vcr.use_cassette('test', record_mode='once')
def with_cassette_injected(cassette): def with_cassette_injected(cassette):
assert cassette.record_mode == 'once' assert cassette.record_mode == 'once'
@@ -117,9 +152,11 @@ def test_inject_cassette():
def test_with_current_defaults(): def test_with_current_defaults():
vcr = VCR(inject_cassette=True, record_mode='once') vcr = VCR(inject_cassette=True, record_mode='once')
@vcr.use_cassette('test', with_current_defaults=False) @vcr.use_cassette('test', with_current_defaults=False)
def changing_defaults(cassette, checks): def changing_defaults(cassette, checks):
checks(cassette) checks(cassette)
@vcr.use_cassette('test', with_current_defaults=True) @vcr.use_cassette('test', with_current_defaults=True)
def current_defaults(cassette, checks): def current_defaults(cassette, checks):
checks(cassette) checks(cassette)
@@ -141,27 +178,33 @@ def test_with_current_defaults():
def test_cassette_library_dir_with_decoration_and_no_explicit_path(): def test_cassette_library_dir_with_decoration_and_no_explicit_path():
library_dir = '/libary_dir' library_dir = '/libary_dir'
vcr = VCR(inject_cassette=True, cassette_library_dir=library_dir) vcr = VCR(inject_cassette=True, cassette_library_dir=library_dir)
@vcr.use_cassette() @vcr.use_cassette()
def function_name(cassette): def function_name(cassette):
assert cassette._path == os.path.join(library_dir, 'function_name') assert cassette._path == os.path.join(library_dir, 'function_name')
function_name() function_name()
def test_cassette_library_dir_with_decoration_and_explicit_path(): def test_cassette_library_dir_with_decoration_and_explicit_path():
library_dir = '/libary_dir' library_dir = '/libary_dir'
vcr = VCR(inject_cassette=True, cassette_library_dir=library_dir) vcr = VCR(inject_cassette=True, cassette_library_dir=library_dir)
@vcr.use_cassette(path='custom_name') @vcr.use_cassette(path='custom_name')
def function_name(cassette): def function_name(cassette):
assert cassette._path == os.path.join(library_dir, 'custom_name') assert cassette._path == os.path.join(library_dir, 'custom_name')
function_name() function_name()
def test_cassette_library_dir_with_decoration_and_super_explicit_path(): def test_cassette_library_dir_with_decoration_and_super_explicit_path():
library_dir = '/libary_dir' library_dir = '/libary_dir'
vcr = VCR(inject_cassette=True, cassette_library_dir=library_dir) vcr = VCR(inject_cassette=True, cassette_library_dir=library_dir)
@vcr.use_cassette(path=os.path.join(library_dir, 'custom_name')) @vcr.use_cassette(path=os.path.join(library_dir, 'custom_name'))
def function_name(cassette): def function_name(cassette):
assert cassette._path == os.path.join(library_dir, 'custom_name') assert cassette._path == os.path.join(library_dir, 'custom_name')
function_name() function_name()
@@ -169,26 +212,32 @@ def test_cassette_library_dir_with_path_transformer():
library_dir = '/libary_dir' library_dir = '/libary_dir'
vcr = VCR(inject_cassette=True, cassette_library_dir=library_dir, vcr = VCR(inject_cassette=True, cassette_library_dir=library_dir,
path_transformer=lambda path: path + '.json') path_transformer=lambda path: path + '.json')
@vcr.use_cassette() @vcr.use_cassette()
def function_name(cassette): def function_name(cassette):
assert cassette._path == os.path.join(library_dir, 'function_name.json') assert cassette._path == os.path.join(library_dir, 'function_name.json')
function_name() function_name()
def test_use_cassette_with_no_extra_invocation(): def test_use_cassette_with_no_extra_invocation():
vcr = VCR(inject_cassette=True, cassette_library_dir='/') vcr = VCR(inject_cassette=True, cassette_library_dir='/')
@vcr.use_cassette @vcr.use_cassette
def function_name(cassette): def function_name(cassette):
assert cassette._path == os.path.join('/', 'function_name') assert cassette._path == os.path.join('/', 'function_name')
function_name() function_name()
def test_path_transformer(): def test_path_transformer():
vcr = VCR(inject_cassette=True, cassette_library_dir='/', vcr = VCR(inject_cassette=True, cassette_library_dir='/',
path_transformer=lambda x: x + '_test') path_transformer=lambda x: x + '_test')
@vcr.use_cassette @vcr.use_cassette
def function_name(cassette): def function_name(cassette):
assert cassette._path == os.path.join('/', 'function_name_test') assert cassette._path == os.path.join('/', 'function_name_test')
function_name() function_name()
@@ -203,8 +252,25 @@ def test_cassette_name_generator_defaults_to_using_module_function_defined_in():
def test_ensure_suffix(): def test_ensure_suffix():
vcr = VCR(inject_cassette=True, path_transformer=VCR.ensure_suffix('.yaml')) vcr = VCR(inject_cassette=True, path_transformer=VCR.ensure_suffix('.yaml'))
@vcr.use_cassette @vcr.use_cassette
def function_name(cassette): def function_name(cassette):
assert cassette._path == os.path.join(os.path.dirname(__file__), assert cassette._path == os.path.join(os.path.dirname(__file__),
'function_name.yaml') 'function_name.yaml')
function_name() function_name()
def test_additional_matchers():
vcr = VCR(match_on=('uri',), inject_cassette=True)
@vcr.use_cassette
def function_defaults(cassette):
assert set(cassette._match_on) == set([vcr.matchers['uri']])
@vcr.use_cassette(additional_matchers=('body',))
def function_additional(cassette):
assert set(cassette._match_on) == set([vcr.matchers['uri'], vcr.matchers['body']])
function_defaults()
function_additional()

View File

@@ -20,10 +20,18 @@ class CassetteContextDecorator(object):
"""Context manager/decorator that handles installing the cassette and """Context manager/decorator that handles installing the cassette and
removing cassettes. removing cassettes.
This class defers the creation of a new cassette instance until the point at This class defers the creation of a new cassette instance until
which it is installed by context manager or decorator. The fact that a new the point at which it is installed by context manager or
cassette is used with each application prevents the state of any cassette decorator. The fact that a new cassette is used with each
from interfering with another. application prevents the state of any cassette from interfering
with another.
Instances of this class are NOT reentrant as context managers.
However, functions that are decorated by
``CassetteContextDecorator`` instances ARE reentrant. See the
implementation of ``__call__`` on this class for more details.
There is also a guard against attempts to reenter instances of
this class as a context manager in ``__exit__``.
""" """
_non_cassette_arguments = ('path_transformer', 'func_path_generator') _non_cassette_arguments = ('path_transformer', 'func_path_generator')
@@ -41,9 +49,14 @@ class CassetteContextDecorator(object):
with contextlib.ExitStack() as exit_stack: with contextlib.ExitStack() as exit_stack:
for patcher in CassettePatcherBuilder(cassette).build(): for patcher in CassettePatcherBuilder(cassette).build():
exit_stack.enter_context(patcher) exit_stack.enter_context(patcher)
log.debug('Entered context for cassette at {0}.'.format(cassette._path)) log_format = '{action} context for cassette at {path}.'
log.debug(log_format.format(
action="Entering", path=cassette._path
))
yield cassette yield cassette
log.debug('Exiting context for cassette at {0}.'.format(cassette._path)) log.debug(log_format.format(
action="Exiting", path=cassette._path
))
# TODO(@IvanMalison): Hmmm. it kind of feels like this should be # TODO(@IvanMalison): Hmmm. it kind of feels like this should be
# somewhere else. # somewhere else.
cassette._save() cassette._save()
@@ -78,7 +91,9 @@ class CassetteContextDecorator(object):
# functions are reentrant. This is required for thread # functions are reentrant. This is required for thread
# safety and the correct operation of recursive functions. # safety and the correct operation of recursive functions.
args_getter = self._build_args_getter_for_decorator(function) args_getter = self._build_args_getter_for_decorator(function)
return type(self)(self.cls, args_getter)._execute_function(function, args, kwargs) return type(self)(self.cls, args_getter)._execute_function(
function, args, kwargs
)
def _execute_function(self, function, args, kwargs): def _execute_function(self, function, args, kwargs):
if inspect.isgeneratorfunction(function): if inspect.isgeneratorfunction(function):
@@ -149,7 +164,7 @@ class Cassette(object):
return CassetteContextDecorator.from_args(cls, **kwargs) return CassetteContextDecorator.from_args(cls, **kwargs)
def __init__(self, path, serializer=yamlserializer, record_mode='once', def __init__(self, path, serializer=yamlserializer, record_mode='once',
match_on=(uri, method), before_record_request=None, match_on=(uri, method), before_record_request=None,
before_record_response=None, custom_patches=(), before_record_response=None, custom_patches=(),
inject=False): inject=False):
@@ -195,8 +210,7 @@ class Cassette(object):
request = self._before_record_request(request) request = self._before_record_request(request)
if not request: if not request:
return return
if self._before_record_response: response = self._before_record_response(response)
response = self._before_record_response(response)
self.data.append((request, response)) self.data.append((request, response))
self.dirty = True self.dirty = True

View File

@@ -67,10 +67,11 @@ class VCR(object):
try: try:
serializer = self.serializers[serializer_name] serializer = self.serializers[serializer_name]
except KeyError: except KeyError:
print("Serializer {0} doesn't exist or isn't registered".format( raise KeyError(
serializer_name "Serializer {0} doesn't exist or isn't registered".format(
)) serializer_name
raise KeyError )
)
return serializer return serializer
def _get_matchers(self, matcher_names): def _get_matchers(self, matcher_names):
@@ -117,12 +118,16 @@ class VCR(object):
'cassette_library_dir', 'cassette_library_dir',
self.cassette_library_dir self.cassette_library_dir
) )
additional_matchers = kwargs.get('additional_matchers', ())
if cassette_library_dir: if cassette_library_dir:
def add_cassette_library_dir(path): def add_cassette_library_dir(path):
if not path.startswith(cassette_library_dir): if not path.startswith(cassette_library_dir):
return os.path.join(cassette_library_dir, path) return os.path.join(cassette_library_dir, path)
return path return path
path_transformer = compose(add_cassette_library_dir, path_transformer) path_transformer = compose(
add_cassette_library_dir, path_transformer
)
elif not func_path_generator: elif not func_path_generator:
# If we don't have a library dir, use the functions # If we don't have a library dir, use the functions
# location to build a full path for cassettes. # location to build a full path for cassettes.
@@ -130,12 +135,12 @@ class VCR(object):
merged_config = { merged_config = {
'serializer': self._get_serializer(serializer_name), 'serializer': self._get_serializer(serializer_name),
'match_on': self._get_matchers(matcher_names), 'match_on': self._get_matchers(
tuple(matcher_names) + tuple(additional_matchers)
),
'record_mode': kwargs.get('record_mode', self.record_mode), 'record_mode': kwargs.get('record_mode', self.record_mode),
'before_record_request': self._build_before_record_request(kwargs), 'before_record_request': self._build_before_record_request(kwargs),
'before_record_response': self._build_before_record_response( 'before_record_response': self._build_before_record_response(kwargs),
kwargs
),
'custom_patches': self._custom_patches + kwargs.get( 'custom_patches': self._custom_patches + kwargs.get(
'custom_patches', () 'custom_patches', ()
), ),
@@ -153,11 +158,11 @@ class VCR(object):
'before_record_response', self.before_record_response 'before_record_response', self.before_record_response
) )
filter_functions = [] filter_functions = []
if before_record_response and not isinstance(before_record_response, if before_record_response:
collections.Iterable): if not isinstance(before_record_response, collections.Iterable):
before_record_response = (before_record_response,) before_record_response = (before_record_response,)
for function in before_record_response: filter_functions.extend(before_record_response)
filter_functions.append(function)
def before_record_response(response): def before_record_response(response):
for function in filter_functions: for function in filter_functions:
if response is None: if response is None:
@@ -178,7 +183,8 @@ class VCR(object):
'filter_post_data_parameters', self.filter_post_data_parameters 'filter_post_data_parameters', self.filter_post_data_parameters
) )
before_record_request = options.get( before_record_request = options.get(
"before_record_request", options.get("before_record", self.before_record_request) "before_record_request",
options.get("before_record", self.before_record_request)
) )
ignore_hosts = options.get( ignore_hosts = options.get(
'ignore_hosts', self.ignore_hosts 'ignore_hosts', self.ignore_hosts
@@ -187,28 +193,36 @@ class VCR(object):
'ignore_localhost', self.ignore_localhost 'ignore_localhost', self.ignore_localhost
) )
if filter_headers: if filter_headers:
filter_functions.append(functools.partial(filters.remove_headers, filter_functions.append(
headers_to_remove=filter_headers)) functools.partial(
filters.remove_headers,
headers_to_remove=filter_headers
)
)
if filter_query_parameters: if filter_query_parameters:
filter_functions.append(functools.partial(filters.remove_query_parameters, filter_functions.append(functools.partial(
query_parameters_to_remove=filter_query_parameters)) filters.remove_query_parameters,
query_parameters_to_remove=filter_query_parameters
))
if filter_post_data_parameters: if filter_post_data_parameters:
filter_functions.append(functools.partial(filters.remove_post_data_parameters, filter_functions.append(
post_data_parameters_to_remove=filter_post_data_parameters)) functools.partial(
filters.remove_post_data_parameters,
post_data_parameters_to_remove=filter_post_data_parameters
)
)
hosts_to_ignore = list(ignore_hosts) hosts_to_ignore = set(ignore_hosts)
if ignore_localhost: if ignore_localhost:
hosts_to_ignore.extend(('localhost', '0.0.0.0', '127.0.0.1')) hosts_to_ignore.update(('localhost', '0.0.0.0', '127.0.0.1'))
if hosts_to_ignore: if hosts_to_ignore:
hosts_to_ignore = set(hosts_to_ignore)
filter_functions.append(self._build_ignore_hosts(hosts_to_ignore)) filter_functions.append(self._build_ignore_hosts(hosts_to_ignore))
if before_record_request: if before_record_request:
if not isinstance(before_record_request, collections.Iterable): if not isinstance(before_record_request, collections.Iterable):
before_record_request = (before_record_request,) before_record_request = (before_record_request,)
for function in before_record_request: filter_functions.extend(before_record_request)
filter_functions.append(function)
def before_record_request(request): def before_record_request(request):
request = copy.copy(request) request = copy.copy(request)
for function in filter_functions: for function in filter_functions:
@@ -216,7 +230,6 @@ class VCR(object):
break break
request = function(request) request = function(request)
return request return request
return before_record_request return before_record_request
@staticmethod @staticmethod
@@ -230,7 +243,7 @@ class VCR(object):
@staticmethod @staticmethod
def _build_path_from_func_using_module(function): def _build_path_from_func_using_module(function):
return os.path.join(os.path.dirname(inspect.getfile(function)), return os.path.join(os.path.dirname(inspect.getfile(function)),
function.__name__) function.__name__)
def register_serializer(self, name, serializer): def register_serializer(self, name, serializer):
self.serializers[name] = serializer self.serializers[name] = serializer

View File

@@ -3,8 +3,5 @@ class CannotOverwriteExistingCassetteException(Exception):
class UnhandledHTTPRequestError(KeyError): class UnhandledHTTPRequestError(KeyError):
''' """Raised when a cassette does not contain the request we want."""
Raised when a cassette does not c
ontain the request we want
'''
pass pass

View File

@@ -2,6 +2,8 @@ import json
from six.moves import urllib, xmlrpc_client from six.moves import urllib, xmlrpc_client
from .util import CaseInsensitiveDict, read_body from .util import CaseInsensitiveDict, read_body
import logging import logging
log = logging.getLogger(__name__) log = logging.getLogger(__name__)

View File

@@ -54,13 +54,12 @@ else:
# Try to save the original types for Tornado # Try to save the original types for Tornado
try: try:
import tornado.httpclient
import tornado.simple_httpclient import tornado.simple_httpclient
except ImportError: # pragma: no cover except ImportError: # pragma: no cover
pass pass
else: else:
_AsyncHTTPClient = tornado.httpclient.AsyncHTTPClient _SimpleAsyncHTTPClient_fetch_impl = \
_SimpleAsyncHTTPClient = tornado.simple_httpclient.SimpleAsyncHTTPClient tornado.simple_httpclient.SimpleAsyncHTTPClient.fetch_impl
try: try:
@@ -68,7 +67,8 @@ try:
except ImportError: # pragma: no cover except ImportError: # pragma: no cover
pass pass
else: else:
_CurlAsyncHTTPClient = tornado.curl_httpclient.CurlAsyncHTTPClient _CurlAsyncHTTPClient_fetch_impl = \
tornado.curl_httpclient.CurlAsyncHTTPClient.fetch_impl
class CassettePatcherBuilder(object): class CassettePatcherBuilder(object):
@@ -228,23 +228,27 @@ class CassettePatcherBuilder(object):
@_build_patchers_from_mock_triples_decorator @_build_patchers_from_mock_triples_decorator
def _tornado(self): def _tornado(self):
try: try:
import tornado.httpclient as http
import tornado.simple_httpclient as simple import tornado.simple_httpclient as simple
except ImportError: # pragma: no cover except ImportError: # pragma: no cover
pass pass
else: else:
from .stubs.tornado_stubs import VCRAsyncHTTPClient from .stubs.tornado_stubs import vcr_fetch_impl
from .stubs.tornado_stubs import VCRSimpleAsyncHTTPClient
yield http, 'AsyncHTTPClient', VCRAsyncHTTPClient new_fetch_impl = vcr_fetch_impl(
yield simple, 'SimpleAsyncHTTPClient', VCRSimpleAsyncHTTPClient self._cassette, _SimpleAsyncHTTPClient_fetch_impl
)
yield simple.SimpleAsyncHTTPClient, 'fetch_impl', new_fetch_impl
try: try:
import tornado.curl_httpclient as curl import tornado.curl_httpclient as curl
except ImportError: # pragma: no cover except ImportError: # pragma: no cover
pass pass
else: else:
from .stubs.tornado_stubs import VCRCurlAsyncHTTPClient from .stubs.tornado_stubs import vcr_fetch_impl
yield curl, 'CurlAsyncHTTPClient', VCRCurlAsyncHTTPClient
new_fetch_impl = vcr_fetch_impl(
self._cassette, _CurlAsyncHTTPClient_fetch_impl
)
yield curl.CurlAsyncHTTPClient, 'fetch_impl', new_fetch_impl
def _urllib3_patchers(self, cpool, stubs): def _urllib3_patchers(self, cpool, stubs):
http_connection_remover = ConnectionRemover( http_connection_remover = ConnectionRemover(
@@ -362,19 +366,25 @@ def reset_patchers():
_CertValidatingHTTPSConnection) _CertValidatingHTTPSConnection)
try: try:
import tornado.httpclient as http
import tornado.simple_httpclient as simple import tornado.simple_httpclient as simple
except ImportError: # pragma: no cover except ImportError: # pragma: no cover
pass pass
else: else:
yield mock.patch.object(http, 'AsyncHTTPClient', _AsyncHTTPClient) yield mock.patch.object(
yield mock.patch.object(simple, 'SimpleAsyncHTTPClient', _SimpleAsyncHTTPClient) simple.SimpleAsyncHTTPClient,
'fetch_impl',
_SimpleAsyncHTTPClient_fetch_impl,
)
try: try:
import tornado.curl_httpclient as curl import tornado.curl_httpclient as curl
except ImportError: # pragma: no cover except ImportError: # pragma: no cover
pass pass
else: else:
yield mock.patch.object(curl, 'CurlAsyncHTTPClient', _CurlAsyncHTTPClient) yield mock.patch.object(
curl.CurlAsyncHTTPClient,
'fetch_impl',
_CurlAsyncHTTPClient_fetch_impl,
)
@contextlib.contextmanager @contextlib.contextmanager

View File

@@ -1,48 +1,20 @@
'''Stubs for tornado HTTP clients''' '''Stubs for tornado HTTP clients'''
from __future__ import absolute_import from __future__ import absolute_import
import functools
from six import BytesIO from six import BytesIO
from tornado import httputil from tornado import httputil
from tornado.httpclient import AsyncHTTPClient
from tornado.httpclient import HTTPResponse from tornado.httpclient import HTTPResponse
from tornado.simple_httpclient import SimpleAsyncHTTPClient
from vcr.errors import CannotOverwriteExistingCassetteException from vcr.errors import CannotOverwriteExistingCassetteException
from vcr.request import Request from vcr.request import Request
class _VCRAsyncClient(object): def vcr_fetch_impl(cassette, real_fetch_impl):
cassette = None
def __new__(cls, *args, **kwargs): @functools.wraps(real_fetch_impl)
from vcr.patch import force_reset def new_fetch_impl(self, request, callback):
with force_reset():
return super(_VCRAsyncClient, cls).__new__(cls, *args, **kwargs)
def initialize(self, *args, **kwargs):
from vcr.patch import force_reset
with force_reset():
self.real_client = self._baseclass(*args, **kwargs)
@property
def io_loop(self):
return self.real_client.io_loop
@property
def _closed(self):
return self.real_client._closed
@property
def defaults(self):
return self.real_client.defaults
def close(self):
from vcr.patch import force_reset
with force_reset():
self.real_client.close()
def fetch_impl(self, request, callback):
headers = dict(request.headers) headers = dict(request.headers)
if request.user_agent: if request.user_agent:
headers.setdefault('User-Agent', request.user_agent) headers.setdefault('User-Agent', request.user_agent)
@@ -64,6 +36,7 @@ class _VCRAsyncClient(object):
"that is not yet supported by VCR.py. Please make the " "that is not yet supported by VCR.py. Please make the "
"request outside a VCR.py context." % repr(request) "request outside a VCR.py context." % repr(request)
), ),
request_time=self.io_loop.time() - request.start_time,
) )
return callback(response) return callback(response)
@@ -74,8 +47,8 @@ class _VCRAsyncClient(object):
headers, headers,
) )
if self.cassette.can_play_response_for(vcr_request): if cassette.can_play_response_for(vcr_request):
vcr_response = self.cassette.play_response(vcr_request) vcr_response = cassette.play_response(vcr_request)
headers = httputil.HTTPHeaders() headers = httputil.HTTPHeaders()
recorded_headers = vcr_response['headers'] recorded_headers = vcr_response['headers']
@@ -90,10 +63,12 @@ class _VCRAsyncClient(object):
reason=vcr_response['status']['message'], reason=vcr_response['status']['message'],
headers=headers, headers=headers,
buffer=BytesIO(vcr_response['body']['string']), buffer=BytesIO(vcr_response['body']['string']),
effective_url=vcr_response.get('url'),
request_time=self.io_loop.time() - request.start_time,
) )
return callback(response) return callback(response)
else: else:
if self.cassette.write_protected and self.cassette.filter_request( if cassette.write_protected and cassette.filter_request(
vcr_request vcr_request
): ):
response = HTTPResponse( response = HTTPResponse(
@@ -103,9 +78,9 @@ class _VCRAsyncClient(object):
"No match for the request (%r) was found. " "No match for the request (%r) was found. "
"Can't overwrite existing cassette (%r) in " "Can't overwrite existing cassette (%r) in "
"your current record mode (%r)." "your current record mode (%r)."
% (vcr_request, self.cassette._path, % (vcr_request, cassette._path, cassette.record_mode)
self.cassette.record_mode)
), ),
request_time=self.io_loop.time() - request.start_time,
) )
return callback(response) return callback(response)
@@ -122,27 +97,11 @@ class _VCRAsyncClient(object):
}, },
'headers': headers, 'headers': headers,
'body': {'string': response.body}, 'body': {'string': response.body},
'url': response.effective_url,
} }
self.cassette.append(vcr_request, vcr_response) cassette.append(vcr_request, vcr_response)
return callback(response) return callback(response)
from vcr.patch import force_reset real_fetch_impl(self, request, new_callback)
with force_reset():
self.real_client.fetch_impl(request, new_callback)
return new_fetch_impl
class VCRAsyncHTTPClient(_VCRAsyncClient, AsyncHTTPClient):
_baseclass = AsyncHTTPClient
class VCRSimpleAsyncHTTPClient(_VCRAsyncClient, SimpleAsyncHTTPClient):
_baseclass = SimpleAsyncHTTPClient
try:
from tornado.curl_httpclient import CurlAsyncHTTPClient
except ImportError: # pragma: no cover
VCRCurlAsyncHTTPClient = None
else:
class VCRCurlAsyncHTTPClient(_VCRAsyncClient, CurlAsyncHTTPClient):
_baseclass = CurlAsyncHTTPClient