Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Custom tooling to ease VCR.py management.
import vcrutils
VCR_CASSETTE_PATH = APPROOT + '/venmo_tests/cassettes/' # eg
MAKE_EXTERNAL_REQUESTS = os.environ.get('MAKE_EXTERNAL_REQUESTS') == 'TRUE'
@dual_decorator # convert a paramaterized decorator for no-arg use (https://gist.github.com/simon-weber/9956622).
def external_call(*args, **kwargs):
"""Enable vcrpy to store/mock http requests.
The most basic use looks like::
@external_call
def test_foo(self):
# urllib2, urllib3, and requests will be recorded/mocked.
...
By default, the matching strategy is very restrictive.
To customize it, this decorator's params are passed to vcr.VCR().
For example, to customize the matching strategy, do::
@external_call(match_on=['url', 'host'])
def test_foo(self):
...
If it's easier to match requests by introducing subcassettes,
the decorator can provide a context manager::
@external_call(use_namespaces=True)
def test_foo(self, vcr_namespace): # this argument must be present
# do some work with the base cassette
with vcr_namespace('do_other_work'):
# this uses a separate cassette namespaced under the parent
# we're now using the base cassette again
To force decorated tests to make external requests, set
the MAKE_EXTERNAL_REQUESTS envvar to TRUE.
Class method tests are also supported.
"""
use_namespaces = kwargs.pop('use_namespaces', False)
vcr_args = args
vcr_kwargs = kwargs
default_vcr_kwargs = {
'cassette_library_dir': VCR_CASSETTE_PATH,
'record_mode': 'none',
'match_on': ['url',
'method',
'body',
'path',
'host']
}
default_vcr_kwargs.update(vcr_kwargs)
match_on = default_vcr_kwargs.pop('match_on')
def decorator(f, vcr_args=vcr_args, vcr_kwargs=default_vcr_kwargs,
match_on=match_on, use_namespaces=use_namespaces):
# this is used as a nose attribute:
# http://nose.readthedocs.org/en/latest/plugins/attrib.html
f.external_api = True
@wraps(f)
def wrapper(*args, **kwargs):
my_vcr = vcrutils.get_vcr(*vcr_args, **vcr_kwargs)
cassette_filename = vcrutils.get_filename_from_method(f, args[0])
if use_namespaces:
kwargs['vcr_namespace'] = vcrutils.get_namespace_cm(
my_vcr, cassette_filename, MAKE_EXTERNAL_REQUESTS)
if MAKE_EXTERNAL_REQUESTS:
return f(*args, **kwargs)
else:
with my_vcr.use_cassette(cassette_filename, match_on=match_on):
return f(*args, **kwargs)
return wrapper
return decorator
from contextlib import contextmanager
import inspect
import vcr
def get_vcr(*args, **kwargs):
"""Return a VCR, with our custom matchers registered.
Params are passed to VCR init."""
v = vcr.VCR(*args, **kwargs)
# register custom matchers here
return v
def get_filename_from_method(func, receiver):
"""Return an unambigious filename built from a test method invocation.
The method is assumed to be declared inside venmo_tests.
:attr func: the method's function object.
:attr receiver: the first argument to the method, i.e. self or cls.
"""
# Omit the module path above and including venmo_tests.
# This can include eg a jenkins workspace dir, which
# would make naming inconsistent. Eg::
# before - <jenkins workspace>.venmo_tests.testfile.test_foo
# after - testfile.test_foo
mod_name = func.__module__
mod_name = mod_name[mod_name.index('venmo_tests') + len('venmo_tests') + 1:]
if inspect.isclass(receiver):
class_name = receiver.__name__
else:
class_name = receiver.__class__.__name__
return "%s.%s.%s.yaml" % (mod_name, class_name, func.__name__)
def _get_subcassette_filename(name, parent_filename):
"""Return a cassette namespaced by a parent cassette filename.
For example::
>>> _get_subcassette_filename('foo', 'mytests.test_bar.yaml')
'mytests.test_bar.foo.yaml'
"""
parent_components = parent_filename.split('.')
parent_components.insert(len(parent_components) - 1, name)
return '.'.join(parent_components)
def get_namespace_cm(my_vcr, parent_filename, make_external_requests):
"""Return a context manager that uses a cassette namespaced under the parent.
The context manager takes two arguments:
* name: a string that names the cassette.
* match_on: (optional), passed to use_cassette to override the default.
"""
@contextmanager
def namespace_cm(name, match_on=None,
my_vr=my_vcr, parent_filename=parent_filename,
make_external_requests=make_external_requests):
if make_external_requests:
yield
else:
kwargs = {
'path': _get_subcassette_filename(name, parent_filename),
'match_on': match_on
}
if match_on is None:
# vcr doesn't use a sentinel for match_on;
# it just shouldn't be present to default it.
del kwargs['match_on']
with my_vcr.use_cassette(**kwargs):
yield
return namespace_cm
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.