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

Compare commits

...

33 Commits

Author SHA1 Message Date
Thomas Grainger
0c4020df7d version bump 1.11.0 2017-05-02 11:36:16 +01:00
Thomas Grainger
204cb8f2ac Merge pull request #303 from graingert/support-3.6
support 3.6
2017-05-02 11:17:39 +01:00
Thomas Grainger
0e421b5327 Merge pull request #301 from graingert/handle-pytest-asyncio-coroutines
handle pytest-asyncio async def coroutines
2017-05-02 11:17:06 +01:00
Thomas Grainger
dc2dc306d5 pytest-httpbin doesn't support chunked requests on Python 3.6 2017-04-04 11:30:16 +01:00
Thomas Grainger
1092bcd1a1 add requests 2.13 2017-04-04 10:32:17 +01:00
Thomas Grainger
73dbc6f8cb add missing _get_content_length static method
also add _is_textIO
2017-04-04 10:32:17 +01:00
Thomas Grainger
3e9fb10c11 Merge remote-tracking branch 'derekbekoe/fix-vcrconnection-3.6' into support-3.6 2017-04-04 10:32:17 +01:00
Thomas Grainger
3588ed6341 support 3.6 2017-04-04 10:32:16 +01:00
Kevin McCarthy
26326c3ef0 Merge pull request #305 from graingert/add-cache-to-gitignore
add .cache to gitignore
2017-04-03 06:40:21 -10:00
Thomas Grainger
7514d94262 handle pytest-asyncio async def coroutines 2017-04-03 15:49:49 +01:00
Thomas Grainger
1df577f0fc add .cache to gitignore 2017-04-03 15:45:42 +01:00
Kevin McCarthy
70f4707063 Merge pull request #302 from AartGoossens/feature/before_record_docs_fix
Improves docs for before_record_request
2017-03-16 08:55:34 -10:00
Aart Goossens
521146d64e Improves docs for before_record_request 2017-03-16 19:25:16 +01:00
Derek Bekoe
091b402594 Revert "Add Python 3.6 to CI"
This reverts commit 24b617a427.
2017-01-24 16:26:02 -08:00
Derek Bekoe
24b617a427 Add Python 3.6 to CI 2017-01-24 13:57:37 -08:00
Derek Bekoe
97473bb8d8 Correctly patch HTTPConnection.request in Python 3.6
Fixes https://github.com/kevin1024/vcrpy/issues/293
2017-01-23 14:54:26 -08:00
Kevin McCarthy
ed35643c3e Merge pull request #292 from j-funk/master
Allow injection of persistence methods
2017-01-22 08:29:52 -06:00
Julien Funk
2fb3b52c7e add custom persister docs 2017-01-19 13:10:27 -05:00
Julien Funk
9e70993d57 substiture IOError with more appropriate ValueError 2017-01-19 13:10:08 -05:00
Julien Funk
6887e2cff9 remove unused imports 2017-01-15 15:04:20 -08:00
Julien Funk
ba38680402 Merge pull request #1 from IvanMalison/persistence_methods
Fix patch of FliesystemPersister.load_cassette
2017-01-14 15:59:13 -05:00
Ivan Malison
06b00837fc Fix patch of FliesystemPersister.load_cassette 2017-01-13 15:29:45 -08:00
Julien Funk
a033bc729c refactored, 1 failing test 2017-01-13 16:09:42 -05:00
Julien Funk
6f8486e0a2 allow injection of persistence methods 2017-01-12 16:41:26 -05:00
Kevin McCarthy
53c55b13e7 version bump 2017-01-11 17:54:16 -10:00
MAA
365e7cb112 Removed duplicate mock triple. 2017-01-11 17:54:15 -10:00
Charly
e5d6327de9 added a fix to httplib2 2017-01-11 17:54:15 -10:00
Luiz Menezes
d86ffe7130 Add missing requirement yarl for python >= 3.4 2017-01-06 10:43:40 -02:00
Kevin McCarthy
d9fd563812 bump version 2016-12-15 08:47:01 -10:00
Kevin McCarthy
9e548718e5 fix whitespace 2016-12-15 08:47:01 -10:00
Kevin McCarthy
83720793fb Merge pull request #280 from madninja/fix_aiohttp
Fix up to support aiohttp 1.x
2016-11-08 10:15:07 -10:00
Marc Nijdam
188326b10e Fix flake errors 2016-11-07 12:03:21 -08:00
Marc Nijdam
ff90190660 Fix up to support aiohttp 1.x 2016-11-07 10:07:08 -08:00
25 changed files with 277 additions and 108 deletions

1
.gitignore vendored
View File

@@ -1,5 +1,6 @@
*.pyc
.tox
.cache
build/
dist/
*.egg/

View File

@@ -13,6 +13,7 @@ env:
- TOX_SUFFIX="requests25"
- TOX_SUFFIX="requests26"
- TOX_SUFFIX="requests27"
- TOX_SUFFIX="requests213"
- TOX_SUFFIX="requests1"
- TOX_SUFFIX="httplib2"
- TOX_SUFFIX="boto"
@@ -34,10 +35,14 @@ matrix:
python: 3.3
- env: TOX_SUFFIX="boto"
python: 3.4
- env: TOX_SUFFIX="boto"
python: 3.6
- env: TOX_SUFFIX="requests1"
python: 3.4
- env: TOX_SUFFIX="requests1"
python: 3.5
- env: TOX_SUFFIX="requests1"
python: 3.6
- env: TOX_SUFFIX="aiohttp"
python: 2.6
- env: TOX_SUFFIX="aiohttp"
@@ -54,6 +59,7 @@ python:
- 3.3
- 3.4
- 3.5
- 3.6
- pypy
- pypy3
install:

View File

@@ -122,6 +122,27 @@ Finally, register your method with VCR to use your new request matcher.
with my_vcr.use_cassette('test.yml'):
# your http here
Register your own cassette persister
------------------------------------
Create your own persistence class, see the :ref:`persister_example`.
Your custom persister must implement both ``load_cassette`` and ``save_cassette``
methods. The ``load_cassette`` method must return a deserialized cassette or raise
``ValueError`` if no cassette is found.
Once the persister class is defined, register with VCR like so...
.. code:: python
import vcr
my_vcr = vcr.VCR()
class CustomerPersister(object):
# implement Persister methods...
my_vcr.register_persister(CustomPersister)
Filter sensitive data from the request
--------------------------------------
@@ -201,7 +222,7 @@ Custom Request filtering
If none of these covers your request filtering needs, you can register a
callback that will manipulate the HTTP request before adding it to the
cassette. Use the ``before_record`` configuration option to so this.
cassette. Use the ``before_record_request`` configuration option to so this.
Here is an example that will never record requests to the /login
endpoint.
@@ -212,7 +233,7 @@ endpoint.
return request
my_vcr = vcr.VCR(
before_record = before_record_cb,
before_record_request = before_record_cb,
)
with my_vcr.use_cassette('test.yml'):
# your http code here
@@ -229,7 +250,7 @@ path.
return request
my_vcr = vcr.VCR(
before_record=scrub_login_request,
before_record_request=scrub_login_request,
)
with my_vcr.use_cassette('test.yml'):
# your http code here

View File

@@ -1,5 +1,12 @@
Changelog
---------
- 1.11.0 Allow injection of persistence methods + bugfixes (thanks @j-funk and @IvanMalison),
Support python 3.6 + CI tests (thanks @derekbekoe and @graingert),
Support pytest-asyncio coroutines (thanks @graingert)
- 1.10.5 Added a fix to httplib2 (thanks @carlosds730), Fix an issue with
aiohttp (thanks @madninja), Add missing requirement yarl (thanks @lamenezes),
Remove duplicate mock triple (thanks @FooBarQuaxx)
- 1.10.4 Fix an issue with asyncio aiohttp (thanks @madninja)
- 1.10.3 Fix some issues with asyncio and params (thanks @anovikov1984 and
@lamenezes), Fix some issues with cassette serialize / deserialize and empty
response bodies (thanks @gRoussac and @dz0ny)

View File

@@ -31,6 +31,7 @@ extras_require = {
':python_version in "2.4, 2.5, 2.6"':
['contextlib2', 'backport_collections', 'mock'],
':python_version in "2.7, 3.1, 3.2"': ['contextlib2', 'mock'],
':python_version in "3.4, 3.5, 3.6"': ['yarl'],
}
@@ -55,7 +56,7 @@ if sys.version_info[0] == 2:
setup(
name='vcrpy',
version='1.10.3',
version='1.11.0',
description=(
"Automatically mock your HTTP interactions to simplify and "
"speed up testing"

View File

@@ -1,7 +1,13 @@
import asyncio
import aiohttp
@asyncio.coroutine
def aiohttp_request(session, method, url, as_text, **kwargs):
response = yield from session.request(method, url, **kwargs) # NOQA: E999
return response, (yield from response.text()) if as_text else (yield from response.json()) # NOQA: E999
def aiohttp_request(loop, method, url, as_text, **kwargs):
with aiohttp.ClientSession(loop=loop) as session:
response = yield from session.request(method, url, **kwargs) # NOQA: E999
if as_text:
content = yield from response.text() # NOQA: E999
else:
content = yield from response.json() # NOQA: E999
return response, content

View File

@@ -0,0 +1,13 @@
import aiohttp
import pytest
import vcr
@vcr.use_cassette()
@pytest.mark.asyncio
async def test_http(): # noqa: E999
async with aiohttp.ClientSession() as session:
url = 'https://httpbin.org/get'
params = {'ham': 'spam'}
resp = await session.get(url, params=params) # noqa: E999
assert (await resp.json())['args'] == {'ham': 'spam'} # noqa: E999

View File

@@ -1,28 +1,40 @@
import pytest
aiohttp = pytest.importorskip("aiohttp")
import asyncio # NOQA
import sys # NOQA
import asyncio # noqa: E402
import contextlib # noqa: E402
import aiohttp # NOQA
import pytest # NOQA
import vcr # NOQA
import pytest # noqa: E402
import vcr # noqa: E402
from .aiohttp_utils import aiohttp_request # NOQA
from .aiohttp_utils import aiohttp_request # noqa: E402
try:
from .async_def import test_http # noqa: F401
except SyntaxError:
pass
def run_in_loop(fn):
with contextlib.closing(asyncio.new_event_loop()) as loop:
asyncio.set_event_loop(loop)
task = loop.create_task(fn(loop))
return loop.run_until_complete(task)
def request(method, url, as_text=True, **kwargs):
def run(loop):
return aiohttp_request(loop, method, url, as_text, **kwargs)
return run_in_loop(run)
def get(url, as_text=True, **kwargs):
loop = asyncio.get_event_loop()
with aiohttp.ClientSession() as session:
task = loop.create_task(aiohttp_request(session, 'GET', url, as_text, **kwargs))
return loop.run_until_complete(task)
return request('GET', url, as_text, **kwargs)
def post(url, as_text=True, **kwargs):
loop = asyncio.get_event_loop()
with aiohttp.ClientSession() as session:
task = loop.create_task(aiohttp_request(session, 'POST', url, as_text, **kwargs))
return loop.run_until_complete(task)
return request('POST', url, as_text, **kwargs)
@pytest.fixture(params=["https", "http"])

View File

@@ -0,0 +1,22 @@
interactions:
- request:
body: null
headers: {}
method: GET
uri: https://httpbin.org/get?ham=spam
response:
body: {string: "{\n \"args\": {\n \"ham\": \"spam\"\n }, \n \"headers\"\
: {\n \"Accept\": \"*/*\", \n \"Accept-Encoding\": \"gzip, deflate\"\
, \n \"Connection\": \"close\", \n \"Host\": \"httpbin.org\", \n \
\ \"User-Agent\": \"Python/3.5 aiohttp/2.0.1\"\n }, \n \"origin\": \"213.86.221.35\"\
, \n \"url\": \"https://httpbin.org/get?ham=spam\"\n}\n"}
headers: {Access-Control-Allow-Credentials: 'true', Access-Control-Allow-Origin: '*',
Connection: keep-alive, Content-Length: '299', Content-Type: application/json,
Date: 'Wed, 22 Mar 2017 20:08:29 GMT', Server: gunicorn/19.7.1, Via: 1.1 vegur}
status: {code: 200, message: OK}
url: !!python/object/new:yarl.URL
state: !!python/tuple
- !!python/object/new:urllib.parse.SplitResult [https, httpbin.org, /get, ham=spam,
'']
- false
version: 1

View File

@@ -0,0 +1,40 @@
# -*- coding: utf-8 -*-
'''Tests for cassettes with custom persistence'''
# External imports
import os
from six.moves.urllib.request import urlopen
# Internal imports
import vcr
from vcr.persisters.filesystem import FilesystemPersister
def test_save_cassette_with_custom_persister(tmpdir, httpbin):
'''Ensure you can save a cassette using custom persister'''
my_vcr = vcr.VCR()
my_vcr.register_persister(FilesystemPersister)
# Check to make sure directory doesnt exist
assert not os.path.exists(str(tmpdir.join('nonexistent')))
# Run VCR to create dir and cassette file using new save_cassette callback
with my_vcr.use_cassette(str(tmpdir.join('nonexistent', 'cassette.yml'))):
urlopen(httpbin.url).read()
# Callback should have made the file and the directory
assert os.path.exists(str(tmpdir.join('nonexistent', 'cassette.yml')))
def test_load_cassette_with_custom_persister(tmpdir, httpbin):
'''
Ensure you can load a cassette using custom persister
'''
my_vcr = vcr.VCR()
my_vcr.register_persister(FilesystemPersister)
test_fixture = str(tmpdir.join('synopsis.json'))
with my_vcr.use_cassette(test_fixture, serializer='json'):
response = urlopen(httpbin.url).read()
assert b'difficult sometimes' in response

View File

@@ -4,8 +4,8 @@ import pytest
import vcr
from assertions import assert_cassette_empty, assert_is_json
requests = pytest.importorskip("requests")
from requests.exceptions import ConnectionError # noqa E402
def test_status_code(httpbin_both, tmpdir):
@@ -100,11 +100,26 @@ def test_post(tmpdir, httpbin_both):
assert req1 == req2
def test_post_chunked_binary(tmpdir, httpbin_both):
def test_post_chunked_binary(tmpdir, httpbin):
'''Ensure that we can send chunked binary without breaking while trying to concatenate bytes with str.'''
data1 = iter([b'data', b'to', b'send'])
data2 = iter([b'data', b'to', b'send'])
url = httpbin_both.url + '/post'
url = httpbin.url + '/post'
with vcr.use_cassette(str(tmpdir.join('requests.yaml'))):
req1 = requests.post(url, data1).content
with vcr.use_cassette(str(tmpdir.join('requests.yaml'))):
req2 = requests.post(url, data2).content
assert req1 == req2
@pytest.mark.xfail('sys.version_info >= (3, 6)', strict=True, raises=ConnectionError)
def test_post_chunked_binary_secure(tmpdir, httpbin_secure):
'''Ensure that we can send chunked binary without breaking while trying to concatenate bytes with str.'''
data1 = iter([b'data', b'to', b'send'])
data2 = iter([b'data', b'to', b'send'])
url = httpbin_secure.url + '/post'
with vcr.use_cassette(str(tmpdir.join('requests.yaml'))):
req1 = requests.post(url, data1).content
print(req1)

View File

@@ -83,7 +83,8 @@ def make_get_request():
@mock.patch('vcr.cassette.requests_match', return_value=True)
@mock.patch('vcr.cassette.load_cassette', lambda *args, **kwargs: (('foo',), (mock.MagicMock(),)))
@mock.patch('vcr.cassette.FilesystemPersister.load_cassette',
classmethod(lambda *args, **kwargs: (('foo',), (mock.MagicMock(),))))
@mock.patch('vcr.cassette.Cassette.can_play_response_for', return_value=True)
@mock.patch('vcr.stubs.VCRHTTPResponse')
def test_function_decorated_with_use_cassette_can_be_invoked_multiple_times(*args):

View File

@@ -1,6 +1,6 @@
import pytest
import vcr.persist
from vcr.persisters.filesystem import FilesystemPersister
from vcr.serializers import jsonserializer, yamlserializer
@@ -10,7 +10,7 @@ from vcr.serializers import jsonserializer, yamlserializer
])
def test_load_cassette_with_old_cassettes(cassette_path, serializer):
with pytest.raises(ValueError) as excinfo:
vcr.persist.load_cassette(cassette_path, serializer)
FilesystemPersister.load_cassette(cassette_path, serializer)
assert "run the migration script" in excinfo.exconly()
@@ -20,5 +20,5 @@ def test_load_cassette_with_old_cassettes(cassette_path, serializer):
])
def test_load_cassette_with_invalid_cassettes(cassette_path, serializer):
with pytest.raises(Exception) as excinfo:
vcr.persist.load_cassette(cassette_path, serializer)
FilesystemPersister.load_cassette(cassette_path, serializer)
assert "run the migration script" not in excinfo.exconly()

View File

@@ -1,4 +1,6 @@
from vcr.stubs import VCRHTTPSConnection
from vcr.compat import mock
from vcr.cassette import Cassette
class TestVCRConnection(object):
@@ -7,3 +9,10 @@ class TestVCRConnection(object):
vcr_connection = VCRHTTPSConnection('www.examplehost.com')
vcr_connection.ssl_version = 'example_ssl_version'
assert vcr_connection.real_connection.ssl_version == 'example_ssl_version'
@mock.patch('vcr.cassette.Cassette.can_play_response_for', return_value=False)
def testing_connect(*args):
vcr_connection = VCRHTTPSConnection('www.google.com')
vcr_connection.cassette = Cassette('test', record_mode='all')
vcr_connection.real_connection.connect()
assert vcr_connection.real_connection.sock is not None

View File

@@ -94,7 +94,7 @@ def test_vcr_before_record_response_iterable():
response = object() # just can't be None
# Prevent actually saving the cassette
with mock.patch('vcr.cassette.save_cassette'):
with mock.patch('vcr.cassette.FilesystemPersister.save_cassette'):
# Baseline: non-iterable before_record_response should work
mock_filter = mock.Mock()
@@ -118,7 +118,7 @@ def test_before_record_response_as_filter():
response = object() # just can't be None
# Prevent actually saving the cassette
with mock.patch('vcr.cassette.save_cassette'):
with mock.patch('vcr.cassette.FilesystemPersister.save_cassette'):
filter_all = mock.Mock(return_value=None)
vcr = VCR(before_record_response=filter_all)
@@ -132,7 +132,7 @@ def test_vcr_path_transformer():
# Regression test for #199
# Prevent actually saving the cassette
with mock.patch('vcr.cassette.save_cassette'):
with mock.patch('vcr.cassette.FilesystemPersister.save_cassette'):
# Baseline: path should be unchanged
vcr = VCR()

16
tox.ini
View File

@@ -1,5 +1,5 @@
[tox]
envlist = {py26,py27,py33,py34,pypy,pypy3}-{flakes,requests27,requests26,requests25,requests24,requests23,requests22,requests1,httplib2,urllib317,urllib319,urllib3110,tornado3,tornado4,boto,boto3,aiohttp}
envlist = {py26,py27,py33,py34,py35,py36,pypy,pypy3}-{flakes,requests213,requests27,requests26,requests25,requests24,requests23,requests22,requests1,httplib2,urllib317,urllib319,urllib3110,tornado3,tornado4,boto,boto3,aiohttp}
[testenv:flakes]
skipsdist = True
@@ -20,6 +20,7 @@ deps =
pytest-httpbin
PyYAML
requests1: requests==1.2.3
requests213: requests==2.13.0
requests27: requests==2.7.0
requests26: requests==2.6.0
requests25: requests==2.5.0
@@ -30,15 +31,16 @@ deps =
urllib317: urllib3==1.7.1
urllib319: urllib3==1.9.1
urllib3110: urllib3==1.10.2
{py26,py27,py33,py34,pypy}-tornado3: tornado>=3,<4
{py26,py27,py33,py34,pypy}-tornado4: tornado>=4,<5
{py26,py27,py33,py34,pypy}-tornado3: pytest-tornado
{py26,py27,py33,py34,pypy}-tornado4: pytest-tornado
{py26,py27,py33,py34}-tornado3: pycurl
{py26,py27,py33,py34}-tornado4: pycurl
{py26,py27,py33,py34,py35,py36,pypy}-tornado3: tornado>=3,<4
{py26,py27,py33,py34,py35,py36,pypy}-tornado4: tornado>=4,<5
{py26,py27,py33,py34,py35,py36,pypy}-tornado3: pytest-tornado
{py26,py27,py33,py34,py35,py36,pypy}-tornado4: pytest-tornado
{py26,py27,py33,py34,py35,py36}-tornado3: pycurl
{py26,py27,py33,py34,py35,py36}-tornado4: pycurl
boto: boto
boto3: boto3
aiohttp: aiohttp
aiohttp: pytest-asyncio
[flake8]
max_line_length = 110

7
vcr/_handle_coroutine.py Normal file
View File

@@ -0,0 +1,7 @@
import asyncio
@asyncio.coroutine
def handle_coroutine(vcr, fn):
with vcr as cassette:
return (yield from fn(cassette)) # noqa: E999

View File

@@ -8,10 +8,20 @@ from .compat import contextlib, collections
from .errors import UnhandledHTTPRequestError
from .matchers import requests_match, uri, method
from .patch import CassettePatcherBuilder
from .persist import load_cassette, save_cassette
from .serializers import yamlserializer
from .persisters.filesystem import FilesystemPersister
from .util import partition_dict
try:
from asyncio import iscoroutinefunction
from ._handle_coroutine import handle_coroutine
except ImportError:
def iscoroutinefunction(*args, **kwargs):
return False
def handle_coroutine(*args, **kwags):
raise NotImplementedError('Not implemented on Python 2')
log = logging.getLogger(__name__)
@@ -96,18 +106,25 @@ class CassetteContextDecorator(object):
)
def _execute_function(self, function, args, kwargs):
if inspect.isgeneratorfunction(function):
handler = self._handle_coroutine
else:
handler = self._handle_function
return handler(function, args, kwargs)
def handle_function(cassette):
if cassette.inject:
return function(cassette, *args, **kwargs)
else:
return function(*args, **kwargs)
def _handle_coroutine(self, function, args, kwargs):
"""Wraps a coroutine so that we're inside the cassette context for the
duration of the coroutine.
if iscoroutinefunction(function):
return handle_coroutine(vcr=self, fn=handle_function)
if inspect.isgeneratorfunction(function):
return self._handle_generator(fn=handle_function)
return self._handle_function(fn=handle_function)
def _handle_generator(self, fn):
"""Wraps a generator so that we're inside the cassette context for the
duration of the generator.
"""
with self as cassette:
coroutine = self.__handle_function(cassette, function, args, kwargs)
coroutine = fn(cassette)
# We don't need to catch StopIteration. The caller (Tornado's
# gen.coroutine, for example) will handle that.
to_yield = next(coroutine)
@@ -119,15 +136,9 @@ class CassetteContextDecorator(object):
else:
to_yield = coroutine.send(to_send)
def __handle_function(self, cassette, function, args, kwargs):
if cassette.inject:
return function(cassette, *args, **kwargs)
else:
return function(*args, **kwargs)
def _handle_function(self, function, args, kwargs):
def _handle_function(self, fn):
with self as cassette:
return self.__handle_function(cassette, function, args, kwargs)
return fn(cassette)
@staticmethod
def get_function_name(function):
@@ -163,11 +174,11 @@ class Cassette(object):
def use(cls, **kwargs):
return CassetteContextDecorator.from_args(cls, **kwargs)
def __init__(self, path, serializer=yamlserializer, record_mode='once',
def __init__(self, path, serializer=yamlserializer, persister=FilesystemPersister, record_mode='once',
match_on=(uri, method), before_record_request=None,
before_record_response=None, custom_patches=(),
inject=False):
self._persister = persister
self._path = path
self._serializer = serializer
self._match_on = match_on
@@ -271,24 +282,24 @@ class Cassette(object):
def _save(self, force=False):
if force or self.dirty:
save_cassette(
self._persister.save_cassette(
self._path,
self._as_dict(),
serializer=self._serializer
serializer=self._serializer,
)
self.dirty = False
def _load(self):
try:
requests, responses = load_cassette(
requests, responses = self._persister.load_cassette(
self._path,
serializer=self._serializer
serializer=self._serializer,
)
for request, response in zip(requests, responses):
self.append(request, response)
self.dirty = False
self.rewound = True
except IOError:
except ValueError:
pass
def __str__(self):

View File

@@ -9,6 +9,7 @@ import six
from .compat import collections
from .cassette import Cassette
from .serializers import yamlserializer, jsonserializer
from .persisters.filesystem import FilesystemPersister
from .util import compose, auto_decorate
from . import matchers
from . import filters
@@ -57,6 +58,7 @@ class VCR(object):
'raw_body': matchers.raw_body,
'body': matchers.body,
}
self.persister = FilesystemPersister
self.record_mode = record_mode
self.filter_headers = filter_headers
self.filter_query_parameters = filter_query_parameters
@@ -270,6 +272,10 @@ class VCR(object):
def register_matcher(self, name, matcher):
self.matchers[name] = matcher
def register_persister(self, persister):
# Singleton, no name required
self.persister = persister
def test_case(self, predicate=None):
predicate = predicate or self.is_test_method
return six.with_metaclass(auto_decorate(self.use_cassette, predicate))

View File

@@ -164,5 +164,6 @@ def main():
sys.stderr.write("[{0}] {1}\n".format(status, file_path))
sys.stderr.write("Done.\n")
if __name__ == '__main__':
main()

View File

@@ -301,7 +301,6 @@ class CassettePatcherBuilder(object):
self._get_cassette_subclass(stubs.VCRRequestsHTTPSConnection)
)
mock_triples = (
(cpool, 'VerifiedHTTPSConnection', stubs.VCRRequestsHTTPSConnection),
(cpool, 'VerifiedHTTPSConnection', stubs.VCRRequestsHTTPSConnection),
(cpool, 'HTTPConnection', stubs.VCRRequestsHTTPConnection),
(cpool, 'HTTPSConnection', stubs.VCRRequestsHTTPSConnection),

View File

@@ -1,14 +0,0 @@
from .persisters.filesystem import FilesystemPersister
from .serialize import serialize, deserialize
def load_cassette(cassette_path, serializer):
with open(cassette_path) as f:
cassette_content = f.read()
cassette = deserialize(cassette_content, serializer)
return cassette
def save_cassette(cassette_path, cassette_dict, serializer):
data = serialize(cassette_dict, serializer)
FilesystemPersister.write(cassette_path, data)

View File

@@ -1,9 +1,24 @@
# .. _persister_example:
import os
from ..serialize import serialize, deserialize
class FilesystemPersister(object):
@classmethod
def write(cls, cassette_path, data):
def load_cassette(cls, cassette_path, serializer):
try:
with open(cassette_path) as f:
cassette_content = f.read()
except IOError:
raise ValueError('Cassette not found.')
cassette = deserialize(cassette_content, serializer)
return cassette
@staticmethod
def save_cassette(cassette_path, cassette_dict, serializer):
data = serialize(cassette_dict, serializer)
dirname, filename = os.path.split(cassette_path)
if dirname and not os.path.exists(dirname):
os.makedirs(dirname)

View File

@@ -153,7 +153,7 @@ class VCRConnection(object):
)
return uri.replace(prefix, '', 1)
def request(self, method, url, body=None, headers=None):
def request(self, method, url, body=None, headers=None, *args, **kwargs):
'''Persist the request metadata in self._vcr_request'''
self._vcr_request = Request(
method=method,
@@ -287,7 +287,9 @@ class VCRConnection(object):
# Cassette is write-protected, don't actually connect
return
return self.real_connection.connect(*args, **kwargs)
from vcr.patch import force_reset
with force_reset():
return self.real_connection.connect(*args, **kwargs)
@property
def sock(self):
@@ -333,6 +335,11 @@ class VCRConnection(object):
super(VCRConnection, self).__setattr__(name, value)
for k, v in HTTPConnection.__dict__.items():
if isinstance(v, staticmethod):
setattr(VCRConnection, k, v)
class VCRHTTPConnection(VCRConnection):
'''A Mocked class for HTTP requests'''
_baseclass = HTTPConnection

View File

@@ -4,9 +4,9 @@ from __future__ import absolute_import
import asyncio
import functools
import json
import urllib
from aiohttp import ClientResponse, helpers
from aiohttp import ClientResponse
from yarl import URL
from vcr.request import Request
@@ -34,36 +34,17 @@ def vcr_request(cassette, real_request):
headers = self._prepare_headers(headers)
data = kwargs.get('data')
params = kwargs.get('params')
# INFO: Query join logic from
# https://github.com/KeepSafe/aiohttp/blob/b3eeedbc2f515ec2aa6e87ba129524c17b6fe4e3/aiohttp/client_reqrep.py#L167-L188
scheme, netloc, path, query, fragment = urllib.parse.urlsplit(url)
if not path:
path = '/'
# NOTICE: Not sure this is applicable here:
# if isinstance(params, collections.Mapping):
# params = list(params.items())
if params:
if not isinstance(params, str):
params = urllib.parse.urlencode(params)
if query:
query = '%s&%s' % (query, params)
else:
query = params
for k, v in params.items():
params[k] = str(v)
request_path = urllib.parse.urlunsplit(('', '', helpers.requote_uri(path),
query, fragment))
request_url = urllib.parse.urlunsplit(
(scheme, netloc, request_path, '', ''))
vcr_request = Request(method, request_url, data, headers)
request_url = URL(url).with_query(params)
vcr_request = Request(method, str(request_url), data, headers)
if cassette.can_play_response_for(vcr_request):
vcr_response = cassette.play_response(vcr_request)
response = MockClientResponse(method, vcr_response.get('url'))
response = MockClientResponse(method, URL(vcr_response.get('url')))
response.status = vcr_response['status']['code']
response.content = vcr_response['body']['string']
response.reason = vcr_response['status']['message']
@@ -73,7 +54,7 @@ def vcr_request(cassette, real_request):
return response
if cassette.write_protected and cassette.filter_request(vcr_request):
response = MockClientResponse(method, url)
response = MockClientResponse(method, URL(url))
response.status = 599
msg = ("No match for the request {!r} was found. Can't overwrite "
"existing cassette {!r} in your current record mode {!r}.")