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:
@@ -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
|
||||||
--------------------------------------
|
--------------------------------------
|
||||||
|
|
||||||
|
|||||||
40
tests/integration/test_register_persister.py
Normal file
40
tests/integration/test_register_persister.py
Normal 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
|
||||||
@@ -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):
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
@@ -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)
|
|
||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user