Last active
November 24, 2018 21:52
Revisions
-
simon-weber revised this gist
Apr 3, 2014 . 1 changed file with 84 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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 -
simon-weber created this gist
Apr 3, 2014 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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