Skip to content

Instantly share code, notes, and snippets.

@simon-weber
Last active November 24, 2018 21:52

Revisions

  1. simon-weber revised this gist Apr 3, 2014. 1 changed file with 84 additions and 0 deletions.
    84 changes: 84 additions & 0 deletions vcrutils.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,84 @@
    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
  2. simon-weber created this gist Apr 3, 2014.
    81 changes: 81 additions & 0 deletions externalcall.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,81 @@
    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