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

Compare commits

...

22 Commits

Author SHA1 Message Date
Ivan Malison
e9c690b9e7 Version 1.3.0. 2015-03-23 18:10:26 -07:00
Ivan Malison
bba5df2fbb clarifying comment in patch.py. 2015-03-23 18:00:23 -07:00
Ivan Malison
39c3b15e02 unused imports. 2015-03-23 17:56:46 -07:00
Ivan Malison
c87e6d6f6a Clarifying comments in patch.py. 2015-03-23 17:55:49 -07:00
Ivan Malison
5ab77e22db Use suggested emacs style coding statements (see https://www.python.org/dev/peps/pep-0263/). 2015-03-23 17:55:49 -07:00
Ivan 'Goat' Malison
ec6f27bbad Merge pull request #138 from aisch/patch-and-test-urllib3
update urllib3 patch/stub to be same as used for requests and add tests
2015-03-23 17:47:42 -07:00
aisch
8930c97ff7 rm unused imports 2015-03-23 13:56:48 -07:00
aisch
e6b43a0374 rename urllib3 patch method and rm unused imports from tests 2015-03-23 13:43:30 -07:00
aisch
63ec95be06 update urllib3 patch/stub to be same as used for requests and add tests 2015-03-23 12:12:49 -07:00
Kevin McCarthy
84c45b2742 Merge pull request #136 from abhinav/https-port-fix
Fix default port for HTTPS
2015-02-24 09:12:49 -10:00
Abhinav Gupta
87a25e9ab0 Fix httplib2 integration test. 2015-02-24 00:10:08 -08:00
Abhinav Gupta
2473bdb77a Fix default port for HTTPS. 2015-02-23 23:37:04 -08:00
Ivan 'Goat' Malison
32831d4151 Merge pull request #135 from RomuloOliveira/patch-1
Fix missing quotes on Custom Response Filtering
2015-01-28 12:02:57 -08:00
Rômulo Oliveira
4991d6f1c8 Fix missing quotes on Custom Response Filtering
Missing quotes are bad
2015-01-28 11:34:47 -02:00
Ivan Malison
14ef1e87f7 Add custom_patches section to README.md 2015-01-08 14:02:41 -08:00
Ivan 'Goat' Malison
fb14739cc1 Merge pull request #133 from IvanMalison/custom_patches
Custom patches
2015-01-08 11:08:55 -08:00
Ivan Malison
a7c7e4e279 Bump version to 1.2.0 2015-01-08 10:56:39 -08:00
Ivan Malison
c0a22df7ed Add ability to add custom patches to vcr and cassettes. 2015-01-08 10:54:27 -08:00
Ivan Malison
83aed99058 Bump vesrsion to 1.1.4, add to release notes. 2014-12-26 05:26:24 -05:00
Ivan Malison
e1f65bcbdc Add force reset around calls to actual connection from stubs, to ensure
compatibility with version of httplib/urlib2 in python 2.7.9. Closes #130.
2014-12-26 05:10:20 -05:00
Kevin McCarthy
5301149bd8 Merge pull request #128 from gazpachoking/patch-1
Update changelog to note requests 2.5 support
2014-12-09 08:55:52 -10:00
Chase Sterling
0297fcdde7 Update changelog to note requests 2.5 support 2014-12-09 13:26:46 -05:00
20 changed files with 332 additions and 86 deletions

View File

@@ -12,6 +12,9 @@ env:
- WITH_LIB="requests1.x" - WITH_LIB="requests1.x"
- WITH_LIB="httplib2" - WITH_LIB="httplib2"
- WITH_LIB="boto" - WITH_LIB="boto"
- WITH_LIB="urllib31.7"
- WITH_LIB="urllib31.9"
- WITH_LIB="urllib31.10"
matrix: matrix:
allow_failures: allow_failures:
- env: WITH_LIB="boto" - env: WITH_LIB="boto"
@@ -37,4 +40,7 @@ install:
- if [ $WITH_LIB = "requests2.5" ] ; then pip install requests==2.5.0; fi - if [ $WITH_LIB = "requests2.5" ] ; then pip install requests==2.5.0; fi
- if [ $WITH_LIB = "httplib2" ] ; then pip install httplib2; fi - if [ $WITH_LIB = "httplib2" ] ; then pip install httplib2; fi
- if [ $WITH_LIB = "boto" ] ; then pip install boto; fi - if [ $WITH_LIB = "boto" ] ; then pip install boto; fi
- if [ $WITH_LIB = "urllib31.7" ] ; then pip install certifi urllib3==1.7.1; fi
- if [ $WITH_LIB = "urllib31.9" ] ; then pip install certifi urllib3==1.9.1; fi
- if [ $WITH_LIB = "urllib31.10" ] ; then pip install certifi urllib3==1.10.2; fi
script: python setup.py test script: python setup.py test

View File

@@ -336,7 +336,7 @@ argument. It's usage is similar to that of `before_record`:
```python ```python
def scrub_string(string, replacement=''): def scrub_string(string, replacement=''):
def before_record_reponse(response): def before_record_reponse(response):
return response['body']['string] = response['body']['string].replace(string, replacement) return response['body']['string'] = response['body']['string'].replace(string, replacement)
return scrub_string return scrub_string
my_vcr = vcr.VCR( my_vcr = vcr.VCR(
@@ -362,6 +362,23 @@ back from a cassette. VCR will completely ignore those requests as if it
didn't notice them at all, and they will continue to hit the server as if VCR didn't notice them at all, and they will continue to hit the server as if VCR
were not there. were not there.
## Custom Patches
If you use a custom `HTTPConnection` class, or otherwise make http
requests in a way that requires additional patching, you can use the
`custom_patches` keyword argument of the `VCR` and `Cassette` objects
to patch those objects whenever a cassette's context is entered. To
patch a custom version of `HTTPConnection` you can do something like
this:
```
import where_the_custom_https_connection_lives
from vcr.stubs import VCRHTTPSConnection
my_vcr = config.VCR(custom_patches=((where_the_custom_https_connection_lives, 'CustomHTTPSConnection', VCRHTTPSConnection),))
@my_vcr.use_cassette(...)
```
## Installation ## Installation
VCR.py is a package on PyPI, so you can `pip install vcrpy` (first you may need VCR.py is a package on PyPI, so you can `pip install vcrpy` (first you may need
@@ -457,9 +474,15 @@ API in version 1.0.x
## Changelog ## Changelog
* 1.3.0 Fix/add support for urllib3 (thanks @aisch), fix default
port for https (thanks @abhinav).
* 1.2.0 Add custom_patches argument to VCR/Cassette objects to allow
users to stub custom classes when cassettes become active.
* 1.1.4 Add force reset around calls to actual connection from stubs, to ensure
compatibility with the version of httplib/urlib2 in python 2.7.9.
* 1.1.3 Fix python3 headers field (thanks @rtaboada), fix boto test (thanks * 1.1.3 Fix python3 headers field (thanks @rtaboada), fix boto test (thanks
@telaviv), fix new_episodes record mode (thanks @jashugan), fix Windows @telaviv), fix new_episodes record mode (thanks @jashugan), fix Windows
connectionpool stub bug (thanks @gazpachoking) connectionpool stub bug (thanks @gazpachoking), add support for requests 2.5
* 1.1.2 Add urllib==1.7.1 support. Make json serialize error handling correct * 1.1.2 Add urllib==1.7.1 support. Make json serialize error handling correct
Improve logging of match failures. Improve logging of match failures.
* 1.1.1 Use function signature preserving `wrapt.decorator` to write the * 1.1.1 Use function signature preserving `wrapt.decorator` to write the

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python ##!/usr/bin/env python
import sys import sys
from setuptools import setup from setuptools import setup
@@ -20,7 +20,7 @@ class PyTest(TestCommand):
setup( setup(
name='vcrpy', name='vcrpy',
version='1.1.3', version='1.3.0',
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

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
'''Basic tests for cassettes''' '''Basic tests for cassettes'''
# coding=utf-8
# External imports # External imports
import os import os

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
'''Basic tests about save behavior''' '''Basic tests about save behavior'''
# coding=utf-8
# External imports # External imports
import os import os

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
'''Integration tests with httplib2''' '''Integration tests with httplib2'''
# coding=utf-8
# External imports # External imports
from six.moves.urllib_parse import urlencode from six.moves.urllib_parse import urlencode
@@ -54,7 +54,7 @@ def test_response_headers(scheme, tmpdir):
with vcr.use_cassette(str(tmpdir.join('headers.yaml'))) as cass: with vcr.use_cassette(str(tmpdir.join('headers.yaml'))) as cass:
resp, _ = httplib2.Http().request(url) resp, _ = httplib2.Http().request(url)
assert headers == resp.items() assert set(headers) == set(resp.items())
def test_multiple_requests(scheme, tmpdir): def test_multiple_requests(scheme, tmpdir):

View File

@@ -1,23 +1,17 @@
# -*- coding: utf-8 -*-
'''Test requests' interaction with vcr''' '''Test requests' interaction with vcr'''
# coding=utf-8
import os
import pytest import pytest
import vcr import vcr
from assertions import ( from assertions import assert_cassette_empty, assert_is_json
assert_cassette_empty,
assert_cassette_has_one_response,
assert_is_json
)
requests = pytest.importorskip("requests") requests = pytest.importorskip("requests")
@pytest.fixture(params=["https", "http"]) @pytest.fixture(params=["https", "http"])
def scheme(request): def scheme(request):
""" '''Fixture that returns both http and https.'''
Fixture that returns both http and https
"""
return request.param return request.param

View File

@@ -1,8 +1,5 @@
# -*- coding: utf-8 -*-
'''Integration tests with urllib2''' '''Integration tests with urllib2'''
# coding=utf-8
# External imports
import os
import pytest import pytest
from six.moves.urllib.request import urlopen from six.moves.urllib.request import urlopen
@@ -11,7 +8,7 @@ from six.moves.urllib_parse import urlencode
# Internal imports # Internal imports
import vcr import vcr
from assertions import assert_cassette_empty, assert_cassette_has_one_response from assertions import assert_cassette_has_one_response
@pytest.fixture(params=["https", "http"]) @pytest.fixture(params=["https", "http"])

View File

@@ -0,0 +1,148 @@
'''Integration tests with urllib3'''
# coding=utf-8
import pytest
import vcr
from assertions import assert_cassette_empty, assert_is_json
certifi = pytest.importorskip("certifi")
urllib3 = pytest.importorskip("urllib3")
@pytest.fixture(params=["https", "http"])
def scheme(request):
"""
Fixture that returns both http and https
"""
return request.param
@pytest.fixture(scope='module')
def verify_pool_mgr():
return urllib3.PoolManager(
cert_reqs='CERT_REQUIRED', # Force certificate check.
ca_certs=certifi.where()
)
@pytest.fixture(scope='module')
def pool_mgr():
return urllib3.PoolManager()
def test_status_code(scheme, tmpdir, verify_pool_mgr):
'''Ensure that we can read the status code'''
url = scheme + '://httpbin.org/'
with vcr.use_cassette(str(tmpdir.join('atts.yaml'))):
status_code = verify_pool_mgr.request('GET', url).status
with vcr.use_cassette(str(tmpdir.join('atts.yaml'))):
assert status_code == verify_pool_mgr.request('GET', url).status
def test_headers(scheme, tmpdir, verify_pool_mgr):
'''Ensure that we can read the headers back'''
url = scheme + '://httpbin.org/'
with vcr.use_cassette(str(tmpdir.join('headers.yaml'))):
headers = verify_pool_mgr.request('GET', url).headers
with vcr.use_cassette(str(tmpdir.join('headers.yaml'))):
assert headers == verify_pool_mgr.request('GET', url).headers
def test_body(tmpdir, scheme, verify_pool_mgr):
'''Ensure the responses are all identical enough'''
url = scheme + '://httpbin.org/bytes/1024'
with vcr.use_cassette(str(tmpdir.join('body.yaml'))):
content = verify_pool_mgr.request('GET', url).data
with vcr.use_cassette(str(tmpdir.join('body.yaml'))):
assert content == verify_pool_mgr.request('GET', url).data
def test_auth(tmpdir, scheme, verify_pool_mgr):
'''Ensure that we can handle basic auth'''
auth = ('user', 'passwd')
headers = urllib3.util.make_headers(basic_auth='{0}:{1}'.format(*auth))
url = scheme + '://httpbin.org/basic-auth/user/passwd'
with vcr.use_cassette(str(tmpdir.join('auth.yaml'))):
one = verify_pool_mgr.request('GET', url, headers=headers)
with vcr.use_cassette(str(tmpdir.join('auth.yaml'))):
two = verify_pool_mgr.request('GET', url, headers=headers)
assert one.data == two.data
assert one.status == two.status
def test_auth_failed(tmpdir, scheme, verify_pool_mgr):
'''Ensure that we can save failed auth statuses'''
auth = ('user', 'wrongwrongwrong')
headers = urllib3.util.make_headers(basic_auth='{0}:{1}'.format(*auth))
url = scheme + '://httpbin.org/basic-auth/user/passwd'
with vcr.use_cassette(str(tmpdir.join('auth-failed.yaml'))) as cass:
# Ensure that this is empty to begin with
assert_cassette_empty(cass)
one = verify_pool_mgr.request('GET', url, headers=headers)
two = verify_pool_mgr.request('GET', url, headers=headers)
assert one.data == two.data
assert one.status == two.status == 401
def test_post(tmpdir, scheme, verify_pool_mgr):
'''Ensure that we can post and cache the results'''
data = {'key1': 'value1', 'key2': 'value2'}
url = scheme + '://httpbin.org/post'
with vcr.use_cassette(str(tmpdir.join('verify_pool_mgr.yaml'))):
req1 = verify_pool_mgr.request('POST', url, data).data
with vcr.use_cassette(str(tmpdir.join('verify_pool_mgr.yaml'))):
req2 = verify_pool_mgr.request('POST', url, data).data
assert req1 == req2
def test_redirects(tmpdir, scheme, verify_pool_mgr):
'''Ensure that we can handle redirects'''
url = scheme + '://httpbin.org/redirect-to?url=bytes/1024'
with vcr.use_cassette(str(tmpdir.join('verify_pool_mgr.yaml'))):
content = verify_pool_mgr.request('GET', url).data
with vcr.use_cassette(str(tmpdir.join('verify_pool_mgr.yaml'))) as cass:
assert content == verify_pool_mgr.request('GET', url).data
# Ensure that we've now cached *two* responses. One for the redirect
# and one for the final fetch
assert len(cass) == 2
assert cass.play_count == 2
def test_cross_scheme(tmpdir, scheme, verify_pool_mgr):
'''Ensure that requests between schemes are treated separately'''
# First fetch a url under http, and then again under https and then
# ensure that we haven't served anything out of cache, and we have two
# requests / response pairs in the cassette
with vcr.use_cassette(str(tmpdir.join('cross_scheme.yaml'))) as cass:
verify_pool_mgr.request('GET', 'https://httpbin.org/')
verify_pool_mgr.request('GET', 'http://httpbin.org/')
assert cass.play_count == 0
assert len(cass) == 2
def test_gzip(tmpdir, scheme, verify_pool_mgr):
'''
Ensure that requests (actually urllib3) is able to automatically decompress
the response body
'''
url = scheme + '://httpbin.org/gzip'
response = verify_pool_mgr.request('GET', url)
with vcr.use_cassette(str(tmpdir.join('gzip.yaml'))):
response = verify_pool_mgr.request('GET', url)
assert_is_json(response.data)
with vcr.use_cassette(str(tmpdir.join('gzip.yaml'))):
assert_is_json(response.data)
def test_https_with_cert_validation_disabled(tmpdir, pool_mgr):
with vcr.use_cassette(str(tmpdir.join('cert_validation_disabled.yaml'))):
pool_mgr.request('GET', 'https://httpbin.org')

View File

@@ -7,8 +7,10 @@ import pytest
import yaml import yaml
from vcr.cassette import Cassette from vcr.cassette import Cassette
from vcr.patch import force_reset
from vcr.errors import UnhandledHTTPRequestError from vcr.errors import UnhandledHTTPRequestError
from vcr.patch import force_reset
from vcr.stubs import VCRHTTPSConnection
def test_cassette_load(tmpdir): def test_cassette_load(tmpdir):
@@ -181,3 +183,21 @@ def test_nesting_context_managers_by_checking_references_of_http_connection():
assert httplib.HTTPConnection is original assert httplib.HTTPConnection is original
assert httplib.HTTPConnection is second_cassette_HTTPConnection assert httplib.HTTPConnection is second_cassette_HTTPConnection
assert httplib.HTTPConnection is first_cassette_HTTPConnection assert httplib.HTTPConnection is first_cassette_HTTPConnection
def test_custom_patchers():
class Test(object):
attribute = None
with Cassette.use('custom_patches', custom_patches=((Test, 'attribute', VCRHTTPSConnection),)):
assert issubclass(Test.attribute, VCRHTTPSConnection)
assert VCRHTTPSConnection is not Test.attribute
old_attribute = Test.attribute
with Cassette.use('custom_patches', custom_patches=((Test, 'attribute', VCRHTTPSConnection),)):
assert issubclass(Test.attribute, VCRHTTPSConnection)
assert VCRHTTPSConnection is not Test.attribute
assert Test.attribute is not old_attribute
assert issubclass(Test.attribute, VCRHTTPSConnection)
assert VCRHTTPSConnection is not Test.attribute
assert Test.attribute is old_attribute

View File

@@ -21,8 +21,8 @@ def test_headers():
('http://go.com/', 80), ('http://go.com/', 80),
('http://go.com:80/', 80), ('http://go.com:80/', 80),
('http://go.com:3000/', 3000), ('http://go.com:3000/', 3000),
('https://go.com/', 433), ('https://go.com/', 443),
('https://go.com:433/', 433), ('https://go.com:443/', 443),
('https://go.com:3000/', 3000), ('https://go.com:3000/', 3000),
]) ])
def test_port(uri, expected_port): def test_port(uri, expected_port):

View File

@@ -3,6 +3,7 @@ import pytest
from vcr import VCR, use_cassette from vcr import VCR, use_cassette
from vcr.request import Request from vcr.request import Request
from vcr.stubs import VCRHTTPSConnection
def test_vcr_use_cassette(): def test_vcr_use_cassette():
@@ -74,3 +75,18 @@ def test_fixtures_with_use_cassette(random_fixture):
# fixtures. It is admittedly a bit strange because the test would never even # fixtures. It is admittedly a bit strange because the test would never even
# run if the relevant feature were broken. # run if the relevant feature were broken.
pass pass
def test_custom_patchers():
class Test(object):
attribute = None
attribute2 = None
test_vcr = VCR(custom_patches=((Test, 'attribute', VCRHTTPSConnection),))
with test_vcr.use_cassette('custom_patches'):
assert issubclass(Test.attribute, VCRHTTPSConnection)
assert VCRHTTPSConnection is not Test.attribute
with test_vcr.use_cassette('custom_patches', custom_patches=((Test, 'attribute2', VCRHTTPSConnection),)):
assert issubclass(Test.attribute, VCRHTTPSConnection)
assert VCRHTTPSConnection is not Test.attribute
assert Test.attribute is Test.attribute2

View File

@@ -21,5 +21,7 @@ deps =
requests23: requests==2.3.0 requests23: requests==2.3.0
requests22: requests==2.2.1 requests22: requests==2.2.1
httplib2: httplib2 httplib2: httplib2
urllib3: urllib3==1.7.1 urllib317: urllib3==1.7.1
urllib319: urllib3==1.9.1
urllib3110: urllib3==1.10.2
boto: boto boto: boto

View File

@@ -87,7 +87,7 @@ class Cassette(object):
match_on=(uri, method), filter_headers=(), match_on=(uri, method), filter_headers=(),
filter_query_parameters=(), before_record_request=None, filter_query_parameters=(), before_record_request=None,
before_record_response=None, ignore_hosts=(), before_record_response=None, ignore_hosts=(),
ignore_localhost=()): ignore_localhost=(), custom_patches=()):
self._path = path self._path = path
self._serializer = serializer self._serializer = serializer
self._match_on = match_on self._match_on = match_on
@@ -100,6 +100,7 @@ class Cassette(object):
self.dirty = False self.dirty = False
self.rewound = False self.rewound = False
self.record_mode = record_mode self.record_mode = record_mode
self.custom_patches = custom_patches
@property @property
def play_count(self): def play_count(self):

View File

@@ -12,7 +12,7 @@ from . import filters
class VCR(object): class VCR(object):
def __init__(self, serializer='yaml', cassette_library_dir=None, def __init__(self, serializer='yaml', cassette_library_dir=None,
record_mode="once", filter_headers=(), record_mode="once", filter_headers=(), custom_patches=(),
filter_query_parameters=(), before_record_request=None, filter_query_parameters=(), before_record_request=None,
before_record_response=None, ignore_hosts=(), before_record_response=None, ignore_hosts=(),
match_on=('method', 'scheme', 'host', 'port', 'path', 'query',), match_on=('method', 'scheme', 'host', 'port', 'path', 'query',),
@@ -43,6 +43,7 @@ class VCR(object):
self.before_record_response = before_record_response self.before_record_response = before_record_response
self.ignore_hosts = ignore_hosts self.ignore_hosts = ignore_hosts
self.ignore_localhost = ignore_localhost self.ignore_localhost = ignore_localhost
self._custom_patches = tuple(custom_patches)
def _get_serializer(self, serializer_name): def _get_serializer(self, serializer_name):
try: try:
@@ -68,6 +69,9 @@ class VCR(object):
def use_cassette(self, path, with_current_defaults=False, **kwargs): def use_cassette(self, path, with_current_defaults=False, **kwargs):
if with_current_defaults: if with_current_defaults:
return Cassette.use(path, self.get_path_and_merged_config(path, **kwargs)) return Cassette.use(path, self.get_path_and_merged_config(path, **kwargs))
# This is made a function that evaluates every time a cassette is made so that
# changes that are made to this VCR instance that occur AFTER the use_cassette
# decorator is applied still affect subsequent calls to the decorated function.
args_getter = functools.partial(self.get_path_and_merged_config, path, **kwargs) args_getter = functools.partial(self.get_path_and_merged_config, path, **kwargs)
return Cassette.use_arg_getter(args_getter) return Cassette.use_arg_getter(args_getter)
@@ -86,7 +90,8 @@ class VCR(object):
'match_on': self._get_matchers(matcher_names), 'match_on': self._get_matchers(matcher_names),
'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(kwargs) 'before_record_response': self._build_before_record_response(kwargs),
'custom_patches': self._custom_patches + kwargs.get('custom_patches', ())
} }
return path, merged_config return path, merged_config

View File

@@ -58,7 +58,7 @@ PARTS = [
def build_uri(**parts): def build_uri(**parts):
port = parts['port'] port = parts['port']
scheme = parts['protocol'] scheme = parts['protocol']
default_port = {'https': 433, 'http': 80}[scheme] default_port = {'https': 443, 'http': 80}[scheme]
parts['port'] = ':{0}'.format(port) if port != default_port else '' parts['port'] = ':{0}'.format(port) if port != default_port else ''
return "{protocol}://{host}{port}{path}".format(**parts) return "{protocol}://{host}{port}{path}".format(**parts)

View File

@@ -69,9 +69,12 @@ class CassettePatcherBuilder(object):
self._class_to_cassette_subclass = {} self._class_to_cassette_subclass = {}
def build(self): def build(self):
return itertools.chain(self._httplib(), self._requests(), return itertools.chain(
self._urllib3(), self._httplib2(), self._httplib(), self._requests(), self._urllib3(), self._httplib2(),
self._boto()) self._boto(), self._build_patchers_from_mock_triples(
self._cassette.custom_patches
)
)
def _build_patchers_from_mock_triples(self, mock_triples): def _build_patchers_from_mock_triples(self, mock_triples):
for args in mock_triples: for args in mock_triples:
@@ -88,6 +91,23 @@ class CassettePatcherBuilder(object):
replacement_class)) replacement_class))
def _recursively_apply_get_cassette_subclass(self, replacement_dict_or_obj): def _recursively_apply_get_cassette_subclass(self, replacement_dict_or_obj):
"""One of the subtleties of this class is that it does not directly
replace HTTPSConnection with `VCRRequestsHTTPSConnection`, but a
subclass of the aforementioned class that has the `cassette`
class attribute assigned to `self._cassette`. This behavior is
necessary to properly support nested cassette contexts.
This function exists to ensure that we use the same class
object (reference) to patch everything that replaces
VCRRequestHTTP[S]Connection, but that we can talk about
patching them with the raw references instead, and without
worrying about exactly where the subclass with the relevant
value for `cassette` is first created.
The function is recursive because it looks in to dictionaries
and replaces class values at any depth with the subclass
described in the previous paragraph.
"""
if isinstance(replacement_dict_or_obj, dict): if isinstance(replacement_dict_or_obj, dict):
for key, replacement_obj in replacement_dict_or_obj.items(): for key, replacement_obj in replacement_dict_or_obj.items():
replacement_obj = self._recursively_apply_get_cassette_subclass( replacement_obj = self._recursively_apply_get_cassette_subclass(
@@ -124,39 +144,8 @@ class CassettePatcherBuilder(object):
import requests.packages.urllib3.connectionpool as cpool import requests.packages.urllib3.connectionpool as cpool
except ImportError: # pragma: no cover except ImportError: # pragma: no cover
return () return ()
from .stubs.requests_stubs import VCRRequestsHTTPConnection, VCRRequestsHTTPSConnection from .stubs import requests_stubs
http_connection_remover = ConnectionRemover( return self._urllib3_patchers(cpool, requests_stubs)
self._get_cassette_subclass(VCRRequestsHTTPConnection)
)
https_connection_remover = ConnectionRemover(
self._get_cassette_subclass(VCRRequestsHTTPSConnection)
)
mock_triples = (
(cpool, 'VerifiedHTTPSConnection', VCRRequestsHTTPSConnection),
(cpool, 'VerifiedHTTPSConnection', VCRRequestsHTTPSConnection),
(cpool, 'HTTPConnection', VCRRequestsHTTPConnection),
(cpool, 'HTTPSConnection', VCRRequestsHTTPSConnection),
(cpool, 'is_connection_dropped', mock.Mock(return_value=False)), # Needed on Windows only
(cpool.HTTPConnectionPool, 'ConnectionCls', VCRRequestsHTTPConnection),
(cpool.HTTPSConnectionPool, 'ConnectionCls', VCRRequestsHTTPSConnection),
)
# These handle making sure that sessions only use the
# connections of the appropriate type.
mock_triples += ((cpool.HTTPConnectionPool, '_get_conn',
self._patched_get_conn(cpool.HTTPConnectionPool,
lambda : cpool.HTTPConnection)),
(cpool.HTTPSConnectionPool, '_get_conn',
self._patched_get_conn(cpool.HTTPSConnectionPool,
lambda : cpool.HTTPSConnection)),
(cpool.HTTPConnectionPool, '_new_conn',
self._patched_new_conn(cpool.HTTPConnectionPool,
http_connection_remover)),
(cpool.HTTPSConnectionPool, '_new_conn',
self._patched_new_conn(cpool.HTTPSConnectionPool,
https_connection_remover)))
return itertools.chain(self._build_patchers_from_mock_triples(mock_triples),
(http_connection_remover, https_connection_remover))
def _patched_get_conn(self, connection_pool_class, connection_class_getter): def _patched_get_conn(self, connection_pool_class, connection_class_getter):
get_conn = connection_pool_class._get_conn get_conn = connection_pool_class._get_conn
@@ -165,6 +154,12 @@ class CassettePatcherBuilder(object):
connection = get_conn(pool, timeout) connection = get_conn(pool, timeout)
connection_class = pool.ConnectionCls if hasattr(pool, 'ConnectionCls') \ connection_class = pool.ConnectionCls if hasattr(pool, 'ConnectionCls') \
else connection_class_getter() else connection_class_getter()
# We need to make sure that we are actually providing a
# patched version of the connection class. This might not
# always be the case because the pool keeps previously
# used connections (which might actually be of a different
# class) around. This while loop will terminate because
# eventually the pool will run out of connections.
while not isinstance(connection, connection_class): while not isinstance(connection, connection_class):
connection = get_conn(pool, timeout) connection = get_conn(pool, timeout)
return connection return connection
@@ -179,17 +174,13 @@ class CassettePatcherBuilder(object):
return new_connection return new_connection
return patched_new_conn return patched_new_conn
@_build_patchers_from_mock_triples_decorator
def _urllib3(self): def _urllib3(self):
try: try:
import urllib3.connectionpool as cpool import urllib3.connectionpool as cpool
except ImportError: # pragma: no cover except ImportError: # pragma: no cover
pass return ()
else: from .stubs import urllib3_stubs
from .stubs.urllib3_stubs import VCRVerifiedHTTPSConnection return self._urllib3_patchers(cpool, urllib3_stubs)
yield cpool, 'VerifiedHTTPSConnection', VCRVerifiedHTTPSConnection
yield cpool, 'HTTPConnection', VCRHTTPConnection
@_build_patchers_from_mock_triples_decorator @_build_patchers_from_mock_triples_decorator
def _httplib2(self): def _httplib2(self):
@@ -215,6 +206,40 @@ class CassettePatcherBuilder(object):
else: else:
from .stubs.boto_stubs import VCRCertValidatingHTTPSConnection from .stubs.boto_stubs import VCRCertValidatingHTTPSConnection
yield cpool, 'CertValidatingHTTPSConnection', VCRCertValidatingHTTPSConnection yield cpool, 'CertValidatingHTTPSConnection', VCRCertValidatingHTTPSConnection
def _urllib3_patchers(self, cpool, stubs):
http_connection_remover = ConnectionRemover(
self._get_cassette_subclass(stubs.VCRRequestsHTTPConnection)
)
https_connection_remover = ConnectionRemover(
self._get_cassette_subclass(stubs.VCRRequestsHTTPSConnection)
)
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
(cpool.HTTPConnectionPool, 'ConnectionCls', stubs.VCRRequestsHTTPConnection),
(cpool.HTTPSConnectionPool, 'ConnectionCls', stubs.VCRRequestsHTTPSConnection),
)
# These handle making sure that sessions only use the
# connections of the appropriate type.
mock_triples += ((cpool.HTTPConnectionPool, '_get_conn',
self._patched_get_conn(cpool.HTTPConnectionPool,
lambda : cpool.HTTPConnection)),
(cpool.HTTPSConnectionPool, '_get_conn',
self._patched_get_conn(cpool.HTTPSConnectionPool,
lambda : cpool.HTTPSConnection)),
(cpool.HTTPConnectionPool, '_new_conn',
self._patched_new_conn(cpool.HTTPConnectionPool,
http_connection_remover)),
(cpool.HTTPSConnectionPool, '_new_conn',
self._patched_new_conn(cpool.HTTPSConnectionPool,
https_connection_remover)))
return itertools.chain(self._build_patchers_from_mock_triples(mock_triples),
(http_connection_remover, https_connection_remover))
class ConnectionRemover(object): class ConnectionRemover(object):

View File

@@ -51,7 +51,7 @@ class Request(object):
parse_uri = urlparse(self.uri) parse_uri = urlparse(self.uri)
port = parse_uri.port port = parse_uri.port
if port is None: if port is None:
port = {'https': 433, 'http': 80}[parse_uri.scheme] port = {'https': 443, 'http': 80}[parse_uri.scheme]
return port return port
@property @property

View File

@@ -128,7 +128,7 @@ class VCRConnection(object):
Returns empty string for the default port and ':port' otherwise Returns empty string for the default port and ':port' otherwise
""" """
port = self.real_connection.port port = self.real_connection.port
default_port = {'https': 433, 'http': 80}[self._protocol] default_port = {'https': 443, 'http': 80}[self._protocol]
return ':{0}'.format(port) if port != default_port else '' return ':{0}'.format(port) if port != default_port else ''
def _uri(self, url): def _uri(self, url):
@@ -236,12 +236,16 @@ class VCRConnection(object):
self._vcr_request self._vcr_request
) )
) )
self.real_connection.request( # This is imported here to avoid circular import.
method=self._vcr_request.method, # TODO(@IvanMalison): Refactor to allow normal import.
url=self._url(self._vcr_request.uri), from vcr.patch import force_reset
body=self._vcr_request.body, with force_reset():
headers=self._vcr_request.headers, self.real_connection.request(
) method=self._vcr_request.method,
url=self._url(self._vcr_request.uri),
body=self._vcr_request.body,
headers=self._vcr_request.headers,
)
# get the response # get the response
response = self.real_connection.getresponse() response = self.real_connection.getresponse()

View File

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