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

Merge pull request #292 from j-funk/master

Allow injection of persistence methods
This commit is contained in:
Kevin McCarthy
2017-01-22 08:29:52 -06:00
committed by GitHub
9 changed files with 99 additions and 30 deletions

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'): with my_vcr.use_cassette('test.yml'):
# your http here # 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 Filter sensitive data from the request
-------------------------------------- --------------------------------------

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

@@ -83,7 +83,8 @@ def make_get_request():
@mock.patch('vcr.cassette.requests_match', return_value=True) @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.cassette.Cassette.can_play_response_for', return_value=True)
@mock.patch('vcr.stubs.VCRHTTPResponse') @mock.patch('vcr.stubs.VCRHTTPResponse')
def test_function_decorated_with_use_cassette_can_be_invoked_multiple_times(*args): def test_function_decorated_with_use_cassette_can_be_invoked_multiple_times(*args):

View File

@@ -1,6 +1,6 @@
import pytest import pytest
import vcr.persist from vcr.persisters.filesystem import FilesystemPersister
from vcr.serializers import jsonserializer, yamlserializer 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): def test_load_cassette_with_old_cassettes(cassette_path, serializer):
with pytest.raises(ValueError) as excinfo: 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() 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): def test_load_cassette_with_invalid_cassettes(cassette_path, serializer):
with pytest.raises(Exception) as excinfo: 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() assert "run the migration script" not in excinfo.exconly()

View File

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

View File

@@ -8,8 +8,8 @@ from .compat import contextlib, collections
from .errors import UnhandledHTTPRequestError from .errors import UnhandledHTTPRequestError
from .matchers import requests_match, uri, method from .matchers import requests_match, uri, method
from .patch import CassettePatcherBuilder from .patch import CassettePatcherBuilder
from .persist import load_cassette, save_cassette
from .serializers import yamlserializer from .serializers import yamlserializer
from .persisters.filesystem import FilesystemPersister
from .util import partition_dict from .util import partition_dict
@@ -163,11 +163,11 @@ class Cassette(object):
def use(cls, **kwargs): def use(cls, **kwargs):
return CassetteContextDecorator.from_args(cls, **kwargs) return CassetteContextDecorator.from_args(cls, **kwargs)
def __init__(self, path, serializer=yamlserializer, record_mode='once', def __init__(self, path, serializer=yamlserializer, persister=FilesystemPersister, record_mode='once',
match_on=(uri, method), before_record_request=None, match_on=(uri, method), before_record_request=None,
before_record_response=None, custom_patches=(), before_record_response=None, custom_patches=(),
inject=False): inject=False):
self._persister = persister
self._path = path self._path = path
self._serializer = serializer self._serializer = serializer
self._match_on = match_on self._match_on = match_on
@@ -271,24 +271,24 @@ class Cassette(object):
def _save(self, force=False): def _save(self, force=False):
if force or self.dirty: if force or self.dirty:
save_cassette( self._persister.save_cassette(
self._path, self._path,
self._as_dict(), self._as_dict(),
serializer=self._serializer serializer=self._serializer,
) )
self.dirty = False self.dirty = False
def _load(self): def _load(self):
try: try:
requests, responses = load_cassette( requests, responses = self._persister.load_cassette(
self._path, self._path,
serializer=self._serializer serializer=self._serializer,
) )
for request, response in zip(requests, responses): for request, response in zip(requests, responses):
self.append(request, response) self.append(request, response)
self.dirty = False self.dirty = False
self.rewound = True self.rewound = True
except IOError: except ValueError:
pass pass
def __str__(self): def __str__(self):

View File

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

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 import os
from ..serialize import serialize, deserialize
class FilesystemPersister(object): class FilesystemPersister(object):
@classmethod @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) dirname, filename = os.path.split(cassette_path)
if dirname and not os.path.exists(dirname): if dirname and not os.path.exists(dirname):
os.makedirs(dirname) os.makedirs(dirname)