Skip to content

Commit

Permalink
Automatically generate cassette names from function names. Add
Browse files Browse the repository at this point in the history
`path_transformer` and `func_path_generator`. Closes #151.
  • Loading branch information
colonelpanic8 committed May 10, 2015
1 parent 0bbbc69 commit 2323b9d
Show file tree
Hide file tree
Showing 6 changed files with 279 additions and 48 deletions.
37 changes: 37 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,43 @@ my_vcr = config.VCR(custom_patches=((where_the_custom_https_connection_lives, 'C
@my_vcr.use_cassette(...)
```

## Automatic Cassette Naming

VCR.py now allows the omission of the path argument to the
use_cassette function. Both of the following are now legal/should work

``` python
@my_vcr.use_cassette
def my_test_function():
...
```

``` python
@my_vcr.use_cassette()
def my_test_function():
...
```

In both cases, VCR.py will use a path that is generated from the
provided test function's name. If no `cassette_library_dir` has been
set, the cassette will be in a file with the name of the test function
in directory of the file in which the test function is declared. If a
`cassette_library_dir` is set, has been set, the cassette will appear
in that directory in a file with the name of the decorated function.

It is possible to control the path produced by the automatic naming
machinery by customizing the `path_transformer` and
`func_path_generator` vcr variables. To add an extension to all
cassette names, use `VCR.ensure_suffix` as follows:

``` python
my_vcr = VCR(path_transformer=VCR.ensure_suffix('.yaml'))

@my_vcr.use_cassette
def my_test_function():

```

## Installation

VCR.py is a package on PyPI, so you can `pip install vcrpy` (first you may need
Expand Down
75 changes: 56 additions & 19 deletions tests/unit/test_cassettes.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import copy
import inspect
import os

from six.moves import http_client as httplib
import contextlib2
Expand All @@ -12,14 +14,13 @@
from vcr.stubs import VCRHTTPSConnection



def test_cassette_load(tmpdir):
a_file = tmpdir.join('test_cassette.yml')
a_file.write(yaml.dump({'interactions': [
{'request': {'body': '', 'uri': 'foo', 'method': 'GET', 'headers': {}},
'response': 'bar'}
]}))
a_cassette = Cassette.load(str(a_file))
a_cassette = Cassette.load(path=str(a_file))
assert len(a_cassette) == 1


Expand Down Expand Up @@ -87,33 +88,35 @@ def make_get_request():
@mock.patch('vcr.cassette.Cassette.can_play_response_for', return_value=True)
@mock.patch('vcr.stubs.VCRHTTPResponse')
def test_function_decorated_with_use_cassette_can_be_invoked_multiple_times(*args):
decorated_function = Cassette.use('test')(make_get_request)
for i in range(2):
decorated_function = Cassette.use(path='test')(make_get_request)
for i in range(4):
decorated_function()


def test_arg_getter_functionality():
arg_getter = mock.Mock(return_value=('test', {}))
arg_getter = mock.Mock(return_value={'path': 'test'})
context_decorator = Cassette.use_arg_getter(arg_getter)

with context_decorator as cassette:
assert cassette._path == 'test'

arg_getter.return_value = ('other', {})
arg_getter.return_value = {'path': 'other'}

with context_decorator as cassette:
assert cassette._path == 'other'

arg_getter.return_value = ('', {'filter_headers': ('header_name',)})
arg_getter.return_value = {'path': 'other', 'filter_headers': ('header_name',)}

@context_decorator
def function():
pass

with mock.patch.object(Cassette, 'load', return_value=mock.MagicMock(inject=False)) as cassette_load:
with mock.patch.object(
Cassette, 'load',
return_value=mock.MagicMock(inject=False)
) as cassette_load:
function()
cassette_load.assert_called_once_with(arg_getter.return_value[0],
**arg_getter.return_value[1])
cassette_load.assert_called_once_with(**arg_getter.return_value)


def test_cassette_not_all_played():
Expand Down Expand Up @@ -156,13 +159,13 @@ def test_nesting_cassette_context_managers(*args):
second_response['body']['string'] = b'second_response'

with contextlib2.ExitStack() as exit_stack:
first_cassette = exit_stack.enter_context(Cassette.use('test'))
first_cassette = exit_stack.enter_context(Cassette.use(path='test'))
exit_stack.enter_context(mock.patch.object(first_cassette, 'play_response',
return_value=first_response))
assert_get_response_body_is('first_response')

# Make sure a second cassette can supercede the first
with Cassette.use('test') as second_cassette:
with Cassette.use(path='test') as second_cassette:
with mock.patch.object(second_cassette, 'play_response', return_value=second_response):
assert_get_response_body_is('second_response')

Expand All @@ -172,12 +175,12 @@ def test_nesting_cassette_context_managers(*args):

def test_nesting_context_managers_by_checking_references_of_http_connection():
original = httplib.HTTPConnection
with Cassette.use('test'):
with Cassette.use(path='test'):
first_cassette_HTTPConnection = httplib.HTTPConnection
with Cassette.use('test'):
with Cassette.use(path='test'):
second_cassette_HTTPConnection = httplib.HTTPConnection
assert second_cassette_HTTPConnection is not first_cassette_HTTPConnection
with Cassette.use('test'):
with Cassette.use(path='test'):
assert httplib.HTTPConnection is not second_cassette_HTTPConnection
with force_reset():
assert httplib.HTTPConnection is original
Expand All @@ -188,12 +191,14 @@ def test_nesting_context_managers_by_checking_references_of_http_connection():
def test_custom_patchers():
class Test(object):
attribute = None
with Cassette.use('custom_patches', custom_patches=((Test, 'attribute', VCRHTTPSConnection),)):
with Cassette.use(path='custom_patches',
custom_patches=((Test, 'attribute', VCRHTTPSConnection),)):
assert issubclass(Test.attribute, VCRHTTPSConnection)
assert VCRHTTPSConnection is not Test.attribute
old_attribute = Test.attribute

with Cassette.use('custom_patches', custom_patches=((Test, 'attribute', VCRHTTPSConnection),)):
with Cassette.use(path='custom_patches',
custom_patches=((Test, 'attribute', VCRHTTPSConnection),)):
assert issubclass(Test.attribute, VCRHTTPSConnection)
assert VCRHTTPSConnection is not Test.attribute
assert Test.attribute is not old_attribute
Expand All @@ -203,10 +208,10 @@ class Test(object):
assert Test.attribute is old_attribute


def test_use_cassette_decorated_functions_are_reentrant():
def test_decorated_functions_are_reentrant():
info = {"second": False}
original_conn = httplib.HTTPConnection
@Cassette.use('whatever', inject=True)
@Cassette.use(path='whatever', inject=True)
def test_function(cassette):
if info['second']:
assert httplib.HTTPConnection is not info['first_conn']
Expand All @@ -217,3 +222,35 @@ def test_function(cassette):
assert httplib.HTTPConnection is info['first_conn']
test_function()
assert httplib.HTTPConnection is original_conn


def test_cassette_use_called_without_path_uses_function_to_generate_path():
@Cassette.use(inject=True)
def function_name(cassette):
assert cassette._path == 'function_name'
function_name()


def test_path_transformer_with_function_path():
path_transformer = lambda path: os.path.join('a', path)
@Cassette.use(inject=True, path_transformer=path_transformer)
def function_name(cassette):
assert cassette._path == os.path.join('a', 'function_name')
function_name()


def test_path_transformer_with_context_manager():
with Cassette.use(
path='b', path_transformer=lambda *args: 'a'
) as cassette:
assert cassette._path == 'a'


def test_func_path_generator():
def generator(function):
return os.path.join(os.path.dirname(inspect.getfile(function)),
function.__name__)
@Cassette.use(inject=True, func_path_generator=generator)
def function_name(cassette):
assert cassette._path == os.path.join(os.path.dirname(__file__), 'function_name')
function_name()
66 changes: 64 additions & 2 deletions tests/unit/test_vcr.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import os

import mock
import pytest

Expand All @@ -9,7 +11,10 @@
def test_vcr_use_cassette():
record_mode = mock.Mock()
test_vcr = VCR(record_mode=record_mode)
with mock.patch('vcr.cassette.Cassette.load', return_value=mock.MagicMock(inject=False)) as mock_cassette_load:
with mock.patch(
'vcr.cassette.Cassette.load',
return_value=mock.MagicMock(inject=False)
) as mock_cassette_load:
@test_vcr.use_cassette('test')
def function():
pass
Expand Down Expand Up @@ -87,7 +92,10 @@ class Test(object):
assert issubclass(Test.attribute, VCRHTTPSConnection)
assert VCRHTTPSConnection is not Test.attribute

with test_vcr.use_cassette('custom_patches', custom_patches=((Test, 'attribute2', VCRHTTPSConnection),)):
with test_vcr.use_cassette(
'custom_patches',
custom_patches=((Test, 'attribute2', VCRHTTPSConnection),)
):
assert issubclass(Test.attribute, VCRHTTPSConnection)
assert VCRHTTPSConnection is not Test.attribute
assert Test.attribute is Test.attribute2
Expand Down Expand Up @@ -128,3 +136,57 @@ def assert_record_mode_all(cassette):
vcr.record_mode = 'all'
changing_defaults(assert_record_mode_all)
current_defaults(assert_record_mode_once)


def test_cassette_library_dir_with_decoration_and_no_explicit_path():
library_dir = '/libary_dir'
vcr = VCR(inject_cassette=True, cassette_library_dir=library_dir)
@vcr.use_cassette()
def function_name(cassette):
assert cassette._path == os.path.join(library_dir, 'function_name')
function_name()


def test_cassette_library_dir_with_path_transformer():
library_dir = '/libary_dir'
vcr = VCR(inject_cassette=True, cassette_library_dir=library_dir,
path_transformer=lambda path: path + '.json')
@vcr.use_cassette()
def function_name(cassette):
assert cassette._path == os.path.join(library_dir, 'function_name.json')
function_name()


def test_use_cassette_with_no_extra_invocation():
vcr = VCR(inject_cassette=True, cassette_library_dir='/')
@vcr.use_cassette
def function_name(cassette):
assert cassette._path == os.path.join('/', 'function_name')
function_name()


def test_path_transformer():
vcr = VCR(inject_cassette=True, cassette_library_dir='/',
path_transformer=lambda x: x + '_test')
@vcr.use_cassette
def function_name(cassette):
assert cassette._path == os.path.join('/', 'function_name_test')
function_name()


def test_cassette_name_generator_defaults_to_using_module_function_defined_in():
vcr = VCR(inject_cassette=True)
@vcr.use_cassette
def function_name(cassette):
assert cassette._path == os.path.join(os.path.dirname(__file__),
'function_name')
function_name()


def test_ensure_suffix():
vcr = VCR(inject_cassette=True, path_transformer=VCR.ensure_suffix('.yaml'))
@vcr.use_cassette
def function_name(cassette):
assert cassette._path == os.path.join(os.path.dirname(__file__),
'function_name.yaml')
function_name()
Loading

0 comments on commit 2323b9d

Please sign in to comment.