[Python-modules-commits] [vcr.py] 02/05: Import vcr.py_1.11.1.orig.tar.gz

Daniele Tricoli eriol-guest at moszumanska.debian.org
Mon Sep 11 00:54:26 UTC 2017


This is an automated email from the git hooks/post-receive script.

eriol-guest pushed a commit to branch master
in repository vcr.py.

commit 141da83d0d6f46411ffa581c900c441ceb0ca0cd
Author: Daniele Tricoli <eriol at mornie.org>
Date:   Mon Sep 11 01:23:16 2017 +0200

    Import vcr.py_1.11.1.orig.tar.gz
---
 .gitignore                                         |  1 +
 .travis.yml                                        | 25 +++++---
 docs/advanced.rst                                  | 27 ++++++++-
 docs/changelog.rst                                 | 13 ++++
 runtests.sh                                        |  2 +-
 setup.py                                           |  9 ++-
 tests/integration/aiohttp_utils.py                 | 12 +++-
 tests/integration/async_def.py                     | 13 ++++
 tests/integration/test_aiohttp.py                  | 70 +++++++++++++++++-----
 tests/integration/test_http                        | 22 +++++++
 tests/integration/test_register_persister.py       | 40 +++++++++++++
 tests/integration/test_requests.py                 | 33 +++++++++-
 tests/unit/test_cassettes.py                       |  3 +-
 tests/unit/test_persist.py                         |  6 +-
 tests/unit/test_serialize.py                       |  8 ++-
 tests/unit/test_stubs.py                           |  9 +++
 tests/unit/test_vcr.py                             |  6 +-
 tox.ini                                            | 19 +++---
 vcr/_handle_coroutine.py                           |  7 +++
 vcr/cassette.py                                    | 61 +++++++++++--------
 vcr/config.py                                      |  6 ++
 vcr/matchers.py                                    |  3 +-
 vcr/migration.py                                   |  1 +
 vcr/patch.py                                       | 33 +++++-----
 vcr/persist.py                                     | 14 -----
 vcr/persisters/filesystem.py                       | 17 +++++-
 vcr/serializers/compat.py                          |  2 +-
 vcr/stubs/__init__.py                              | 23 ++++++-
 .../__init__.py}                                   | 13 ++--
 vcr/stubs/requests_stubs.py                        |  4 +-
 30 files changed, 386 insertions(+), 116 deletions(-)

diff --git a/.gitignore b/.gitignore
index cb7a2b4..7c904e5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
 *.pyc
 .tox
+.cache
 build/
 dist/
 *.egg/
diff --git a/.travis.yml b/.travis.yml
index b76b624..651b57e 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -13,13 +13,15 @@ env:
     - TOX_SUFFIX="requests25"
     - TOX_SUFFIX="requests26"
     - TOX_SUFFIX="requests27"
+    - TOX_SUFFIX="requests213"
+    - TOX_SUFFIX="requests216"
     - TOX_SUFFIX="requests1"
     - TOX_SUFFIX="httplib2"
     - TOX_SUFFIX="boto"
     - TOX_SUFFIX="boto3"
-    - TOX_SUFFIX="urllib317"
     - TOX_SUFFIX="urllib319"
     - TOX_SUFFIX="urllib3110"
+    - TOX_SUFFIX="urllib3121"
     - TOX_SUFFIX="tornado3"
     - TOX_SUFFIX="tornado4"
     - TOX_SUFFIX="aiohttp"
@@ -30,30 +32,37 @@ matrix:
   exclude:
     - env: TOX_SUFFIX="flakes"
       python: 2.6
-    - env: TOX_SUFFIX="boto"
-      python: 3.3
-    - env: TOX_SUFFIX="boto"
+    - env: TOX_SUFFIX="flakes"
+      python: 2.7
+    - env: TOX_SUFFIX="flakes"
       python: 3.4
+    - env: TOX_SUFFIX="flakes"
+      python: 3.5
+    - env: TOX_SUFFIX="flakes"
+      python: pypy
+    - env: TOX_SUFFIX="flakes"
+      python: pypy3
+    - 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"
       python: 2.7
     - env: TOX_SUFFIX="aiohttp"
-      python: 3.3
-    - env: TOX_SUFFIX="aiohttp"
       python: pypy
     - env: TOX_SUFFIX="aiohttp"
       python: pypy3
 python:
 - 2.6
 - 2.7
-- 3.3
-- 3.4
 - 3.5
+- 3.6
 - pypy
 - pypy3
 install:
diff --git a/docs/advanced.rst b/docs/advanced.rst
index 7fff29f..5e6822d 100644
--- a/docs/advanced.rst
+++ b/docs/advanced.rst
@@ -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
diff --git a/docs/changelog.rst b/docs/changelog.rst
index 06f54a3..22240ac 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -1,5 +1,18 @@
 Changelog
 ---------
+-  1.11.1 Fix compatibility with newest requests and urllib3 releases
+-  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)
+-  1.10.2 Fix 1.10.1 release - add aiohttp support back in
+-  1.10.1 [bad release] Fix build for Fedora package + python2 (thanks @puiterwijk and @lamenezes)
 -  1.10.0 Add support for aiohttp (thanks @lamenezes)
 -  1.9.0 Add support for boto3 (thanks @desdm, @foorbarna). Fix deepcopy issue
    for response headers when `decode_compressed_response` is enabled (thanks
diff --git a/runtests.sh b/runtests.sh
index 6779517..d6718bc 100755
--- a/runtests.sh
+++ b/runtests.sh
@@ -1,3 +1,3 @@
 #!/bin/bash
 
-REQUESTS_CA_BUNDLE=`python -m pytest_httpbin.certs` py.test $1
+REQUESTS_CA_BUNDLE=`python -m pytest_httpbin.certs` py.test $*
diff --git a/setup.py b/setup.py
index 6ca61d6..63b66e2 100644
--- a/setup.py
+++ b/setup.py
@@ -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'],
 }
 
 
@@ -49,9 +50,13 @@ except Exception:
             install_requires.extend(value)
 
 
+excluded_packages = ["tests*"]
+if sys.version_info[0] == 2:
+    excluded_packages.append("vcr.stubs.aiohttp_stubs")
+
 setup(
     name='vcrpy',
-    version='1.10.0',
+    version='1.11.1',
     description=(
         "Automatically mock your HTTP interactions to simplify and "
         "speed up testing"
@@ -60,7 +65,7 @@ setup(
     author='Kevin McCarthy',
     author_email='me at kevinmccarthy.org',
     url='https://github.com/kevin1024/vcrpy',
-    packages=find_packages(exclude=("tests*",)),
+    packages=find_packages(exclude=excluded_packages),
     install_requires=install_requires,
     extras_require=extras_require,
     license='MIT',
diff --git a/tests/integration/aiohttp_utils.py b/tests/integration/aiohttp_utils.py
index 195e5eb..5b77fae 100644
--- a/tests/integration/aiohttp_utils.py
+++ b/tests/integration/aiohttp_utils.py
@@ -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
diff --git a/tests/integration/async_def.py b/tests/integration/async_def.py
new file mode 100644
index 0000000..96cab1d
--- /dev/null
+++ b/tests/integration/async_def.py
@@ -0,0 +1,13 @@
+import aiohttp
+import pytest
+import vcr
+
+
+ at vcr.use_cassette()
+ at 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
diff --git a/tests/integration/test_aiohttp.py b/tests/integration/test_aiohttp.py
index 1aa9c05..280be3a 100644
--- a/tests/integration/test_aiohttp.py
+++ b/tests/integration/test_aiohttp.py
@@ -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 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))
+
+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):
+    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"])
@@ -85,3 +97,33 @@ def test_post(tmpdir, scheme):
         _, cassette_response_json = post(url, data=data)
         assert cassette_response_json == response_json
         assert cassette.play_count == 1
+
+
+def test_params(tmpdir, scheme):
+    url = scheme + '://httpbin.org/get'
+    params = {'a': 1, 'b': False, 'c': 'c'}
+    with vcr.use_cassette(str(tmpdir.join('get.yaml'))) as cassette:
+        _, response_json = get(url, as_text=False, params=params)
+
+    with vcr.use_cassette(str(tmpdir.join('get.yaml'))) as cassette:
+        _, cassette_response_json = get(url, as_text=False, params=params)
+        assert cassette_response_json == response_json
+        assert cassette.play_count == 1
+
+
+def test_params_same_url_distinct_params(tmpdir, scheme):
+    url = scheme + '://httpbin.org/get'
+    params = {'a': 1, 'b': False, 'c': 'c'}
+    with vcr.use_cassette(str(tmpdir.join('get.yaml'))) as cassette:
+        _, response_json = get(url, as_text=False, params=params)
+
+    with vcr.use_cassette(str(tmpdir.join('get.yaml'))) as cassette:
+        _, cassette_response_json = get(url, as_text=False, params=params)
+        assert cassette_response_json == response_json
+        assert cassette.play_count == 1
+
+    other_params = {'other': 'params'}
+    with vcr.use_cassette(str(tmpdir.join('get.yaml'))) as cassette:
+        response, cassette_response_text = get(url, as_text=True, params=other_params)
+        assert 'No match for the request' in cassette_response_text
+        assert response.status == 599
diff --git a/tests/integration/test_http b/tests/integration/test_http
new file mode 100644
index 0000000..522363b
--- /dev/null
+++ b/tests/integration/test_http
@@ -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
diff --git a/tests/integration/test_register_persister.py b/tests/integration/test_register_persister.py
new file mode 100644
index 0000000..64e20b6
--- /dev/null
+++ b/tests/integration/test_register_persister.py
@@ -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
diff --git a/tests/integration/test_requests.py b/tests/integration/test_requests.py
index 84a992b..4229a0f 100644
--- a/tests/integration/test_requests.py
+++ b/tests/integration/test_requests.py
@@ -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):
@@ -38,6 +38,18 @@ def test_body(tmpdir, httpbin_both):
         assert content == requests.get(url).content
 
 
+def test_get_empty_content_type_json(tmpdir, httpbin_both):
+    '''Ensure GET with application/json content-type and empty request body doesn't crash'''
+    url = httpbin_both + '/status/200'
+    headers = {'Content-Type': 'application/json'}
+
+    with vcr.use_cassette(str(tmpdir.join('get_empty_json.yaml')), match_on=('body',)):
+        status = requests.get(url, headers=headers).status_code
+
+    with vcr.use_cassette(str(tmpdir.join('get_empty_json.yaml')), match_on=('body',)):
+        assert status == requests.get(url, headers=headers).status_code
+
+
 def test_effective_url(tmpdir, httpbin_both):
     '''Ensure that the effective_url is captured'''
     url = httpbin_both.url + '/redirect-to?url=/html'
@@ -88,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
+
+
+ at 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)
diff --git a/tests/unit/test_cassettes.py b/tests/unit/test_cassettes.py
index ef9e808..e719156 100644
--- a/tests/unit/test_cassettes.py
+++ b/tests/unit/test_cassettes.py
@@ -83,7 +83,8 @@ def make_get_request():
 
 
 @mock.patch('vcr.cassette.requests_match', return_value=True)
- at mock.patch('vcr.cassette.load_cassette', lambda *args, **kwargs: (('foo',), (mock.MagicMock(),)))
+ at 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):
diff --git a/tests/unit/test_persist.py b/tests/unit/test_persist.py
index e9b30be..18ac4a1 100644
--- a/tests/unit/test_persist.py
+++ b/tests/unit/test_persist.py
@@ -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()
diff --git a/tests/unit/test_serialize.py b/tests/unit/test_serialize.py
index cf3f0a8..6555cca 100644
--- a/tests/unit/test_serialize.py
+++ b/tests/unit/test_serialize.py
@@ -4,7 +4,7 @@ import pytest
 from vcr.compat import mock
 from vcr.request import Request
 from vcr.serialize import deserialize, serialize
-from vcr.serializers import yamlserializer, jsonserializer
+from vcr.serializers import yamlserializer, jsonserializer, compat
 
 
 def test_deserialize_old_yaml_cassette():
@@ -131,3 +131,9 @@ def test_serialize_binary_request():
         )
     except (UnicodeDecodeError, TypeError) as exc:
         assert msg in str(exc)
+
+
+def test_deserialize_no_body_string():
+    data = {'body': {'string': None}}
+    output = compat.convert_to_bytes(data)
+    assert data == output
diff --git a/tests/unit/test_stubs.py b/tests/unit/test_stubs.py
index a3b7cca..7eb0684 100644
--- a/tests/unit/test_stubs.py
+++ b/tests/unit/test_stubs.py
@@ -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
diff --git a/tests/unit/test_vcr.py b/tests/unit/test_vcr.py
index decb366..3592ef0 100644
--- a/tests/unit/test_vcr.py
+++ b/tests/unit/test_vcr.py
@@ -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()
diff --git a/tox.ini b/tox.ini
index fa60b54..979c378 100644
--- a/tox.ini
+++ b/tox.ini
@@ -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,py35,py36,pypy,pypy3}-{flakes,requests216,requests213,requests27,requests26,requests25,requests24,requests23,requests22,requests1,httplib2,urllib319,urllib3110,urllib3121,tornado3,tornado4,boto,boto3,aiohttp}
 
 [testenv:flakes]
 skipsdist = True
@@ -20,6 +20,8 @@ deps =
     pytest-httpbin
     PyYAML
     requests1: requests==1.2.3
+    requests216: requests==2.16.3
+    requests213: requests==2.13.0
     requests27: requests==2.7.0
     requests26: requests==2.6.0
     requests25: requests==2.5.0
@@ -27,18 +29,19 @@ deps =
     requests23: requests==2.3.0
     requests22: requests==2.2.1
     httplib2: httplib2
-    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
+    urllib3121: urllib3==1.21.1
+    {py26,py27,py35,py36,pypy}-tornado3: tornado>=3,<4
+    {py26,py27,py35,py36,pypy}-tornado4: tornado>=4,<5
+    {py26,py27,py35,py36,pypy}-tornado3: pytest-tornado
+    {py26,py27,py35,py36,pypy}-tornado4: pytest-tornado
+    {py26,py27,py35,py36}-tornado3: pycurl
+    {py26,py27,py35,py36}-tornado4: pycurl
     boto: boto
     boto3: boto3
     aiohttp: aiohttp
+    aiohttp: pytest-asyncio
 
 [flake8]
 max_line_length = 110
diff --git a/vcr/_handle_coroutine.py b/vcr/_handle_coroutine.py
new file mode 100644
index 0000000..0b20be6
--- /dev/null
+++ b/vcr/_handle_coroutine.py
@@ -0,0 +1,7 @@
+import asyncio
+
+
+ at asyncio.coroutine
+def handle_coroutine(vcr, fn):
+    with vcr as cassette:
+        return (yield from fn(cassette))  # noqa: E999
diff --git a/vcr/cassette.py b/vcr/cassette.py
index 2f5c293..fc3f0fa 100644
--- a/vcr/cassette.py
+++ b/vcr/cassette.py
@@ -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):
+        def handle_function(cassette):
+            if cassette.inject:
+                return function(cassette, *args, **kwargs)
+            else:
+                return function(*args, **kwargs)
+
+        if iscoroutinefunction(function):
+            return handle_coroutine(vcr=self, fn=handle_function)
         if inspect.isgeneratorfunction(function):
-            handler = self._handle_coroutine
-        else:
-            handler = self._handle_function
-        return handler(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.
+            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):
diff --git a/vcr/config.py b/vcr/config.py
index ae99346..15ae459 100644
--- a/vcr/config.py
+++ b/vcr/config.py
@@ -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))
diff --git a/vcr/matchers.py b/vcr/matchers.py
index b54ed2f..8fe334e 100644
--- a/vcr/matchers.py
+++ b/vcr/matchers.py
@@ -49,7 +49,8 @@ def _transform_json(body):
     # Request body is always a byte string, but json.loads() wants a text
     # string. RFC 7159 says the default encoding is UTF-8 (although UTF-16
     # and UTF-32 are also allowed: hmmmmm).
-    return json.loads(body.decode('utf-8'))
+    if body:
+        return json.loads(body.decode('utf-8'))
 
 
 _xml_header_checker = _header_checker('text/xml')
diff --git a/vcr/migration.py b/vcr/migration.py
index 6e30027..23b5e5e 100644
--- a/vcr/migration.py
+++ b/vcr/migration.py
@@ -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()
diff --git a/vcr/patch.py b/vcr/patch.py
index ac1127a..bc93ad9 100644
--- a/vcr/patch.py
+++ b/vcr/patch.py
@@ -12,16 +12,6 @@ _HTTPConnection = httplib.HTTPConnection
 _HTTPSConnection = httplib.HTTPSConnection
 
 
-# Try to save the original types for requests
-try:
-    import requests.packages.urllib3.connectionpool as cpool
-except ImportError:  # pragma: no cover
-    pass
-else:
-    _VerifiedHTTPSConnection = cpool.VerifiedHTTPSConnection
-    _cpoolHTTPConnection = cpool.HTTPConnection
-    _cpoolHTTPSConnection = cpool.HTTPSConnection
-
 # Try to save the original types for boto3
 try:
     import botocore.vendored.requests.packages.urllib3.connectionpool as cpool
@@ -32,14 +22,27 @@ else:
     _cpoolBoto3HTTPConnection = cpool.HTTPConnection
     _cpoolBoto3HTTPSConnection = cpool.HTTPSConnection
 
-
+cpool = None
 # Try to save the original types for urllib3
 try:
-    import urllib3
+    import urllib3.connectionpool as cpool
+except ImportError:  # pragma: no cover
+    pass
+else:
+    _VerifiedHTTPSConnection = cpool.VerifiedHTTPSConnection
+    _cpoolHTTPConnection = cpool.HTTPConnection
+    _cpoolHTTPSConnection = cpool.HTTPSConnection
+
+# Try to save the original types for requests
+try:
+    if not cpool:
+        import requests.packages.urllib3.connectionpool as cpool
 except ImportError:  # pragma: no cover
     pass
 else:
-    _VerifiedHTTPSConnection = urllib3.connectionpool.VerifiedHTTPSConnection
+    _VerifiedHTTPSConnection = cpool.VerifiedHTTPSConnection
+    _cpoolHTTPConnection = cpool.HTTPConnection
+    _cpoolHTTPSConnection = cpool.HTTPSConnection
 
 
 # Try to save the original types for httplib2
@@ -176,10 +179,9 @@ class CassettePatcherBuilder(object):
 
     def _requests(self):
         try:
-            import requests.packages.urllib3.connectionpool as cpool
+            from .stubs import requests_stubs
         except ImportError:  # pragma: no cover
             return ()
-        from .stubs import requests_stubs
         return self._urllib3_patchers(cpool, requests_stubs)
 
     def _boto3(self):
@@ -302,7 +304,6 @@ class CassettePatcherBuilder(object):
         )
         mock_triples = (
             (cpool, 'VerifiedHTTPSConnection', stubs.VCRRequestsHTTPSConnection),
-            (cpool, 'VerifiedHTTPSConnection', stubs.VCRRequestsHTTPSConnection),
             (cpool, 'HTTPConnection', stubs.VCRRequestsHTTPConnection),
             (cpool, 'HTTPSConnection', stubs.VCRRequestsHTTPSConnection),
             (cpool, 'is_connection_dropped', mock.Mock(return_value=False)),  # Needed on Windows only
diff --git a/vcr/persist.py b/vcr/persist.py
deleted file mode 100644
index f8b8d2a..0000000
--- a/vcr/persist.py
+++ /dev/null
@@ -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)
diff --git a/vcr/persisters/filesystem.py b/vcr/persisters/filesystem.py
index 884d891..43b52d4 100644
--- a/vcr/persisters/filesystem.py
+++ b/vcr/persisters/filesystem.py
@@ -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)
diff --git a/vcr/serializers/compat.py b/vcr/serializers/compat.py
index 0fcc583..8364997 100644
--- a/vcr/serializers/compat.py
+++ b/vcr/serializers/compat.py
@@ -24,7 +24,7 @@ def convert_body_to_bytes(resp):
     http://pyyaml.org/wiki/PyYAMLDocumentation#Python3support
     """
     try:
-        if not isinstance(resp['body']['string'], six.binary_type):
+        if resp['body']['string'] is not None and not isinstance(resp['body']['string'], six.binary_type):
... 119 lines suppressed ...

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/vcr.py.git



More information about the Python-modules-commits mailing list