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

Merge pull request #133 from IvanMalison/custom_patches

Custom patches
This commit is contained in:
Ivan 'Goat' Malison
2015-01-08 11:08:55 -08:00
7 changed files with 67 additions and 9 deletions

View File

@@ -457,6 +457,8 @@ API in version 1.0.x
## Changelog ## Changelog
* 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 * 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. 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

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.4', version='1.2.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

@@ -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

@@ -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

@@ -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

@@ -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,17 @@ 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 this class that has cassette assigned to the
appropriate value. 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.
"""
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(