Repository: kevin1024/vcrpy

Issue:
path_transformer doesn't default properly
In the code, we have

```python
class VCR(object):
    def __init__(self, path_transformer=lambda x: x, before_record_request=None,
```

and then later

```python
    def get_merged_config(self, **kwargs):
        path_transformer = kwargs.get(
            'path_transformer',
            self.path_transformer or self.ensure_suffix('.yaml')
        )
```

and then later

```python
        if 'path_transformer' in other_kwargs:
            transformer = other_kwargs['path_transformer']
            cassette_kwargs['path'] = transformer(cassette_kwargs['path'])
```

So there are two problems with this code:
  1. The default `ensure_suffix` doesn't fire because the parameter default is already the identity function which is truthy. The only way this fires is if the user specifies `path_transformer=None` which is surely not the intent.
  2. If the context manager is called with `path_transformer=None` then it will blow up.

Discussion:

Patch:
diff --git a/README.rst b/README.rst
index 23910bb..768e850 100644
--- a/README.rst
+++ b/README.rst
@@ -1,16 +1,14 @@
-|Build Status| |Stories in Ready| |Gitter|
-
 VCR.py
 ======
 
-.. figure:: https://raw.github.com/kevin1024/vcrpy/master/vcr.png
+.. image:: vcr.png
    :alt: vcr.py
 
-   vcr.py
-
 This is a Python version of `Ruby's VCR
 library <https://github.com/vcr/vcr>`__.
 
+|Build Status| |Stories in Ready| |Gitter Chat|
+
 What it does
 ------------
 
@@ -758,6 +756,6 @@ more details
    :target: http://travis-ci.org/kevin1024/vcrpy
 .. |Stories in Ready| image:: https://badge.waffle.io/kevin1024/vcrpy.png?label=ready&title=Ready
    :target: https://waffle.io/kevin1024/vcrpy
-.. |Gitter| image:: https://badges.gitter.im/Join%20Chat.svg
+.. |Gitter Chat| image:: https://badges.gitter.im/Join%20Chat.svg
    :alt: Join the chat at https://gitter.im/kevin1024/vcrpy
    :target: https://gitter.im/kevin1024/vcrpy?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
diff --git a/vcr/cassette.py b/vcr/cassette.py
index bc7410f..6dc6c40 100644
--- a/vcr/cassette.py
+++ b/vcr/cassette.py
@@ -75,7 +75,7 @@ class CassetteContextDecorator(object):
             lambda key, _: key in self._non_cassette_arguments,
             self._args_getter()
         )
-        if 'path_transformer' in other_kwargs:
+        if other_kwargs.get('path_transformer'):
             transformer = other_kwargs['path_transformer']
             cassette_kwargs['path'] = transformer(cassette_kwargs['path'])
         self.__finish = self._patch_generator(self.cls.load(**cassette_kwargs))
diff --git a/vcr/config.py b/vcr/config.py
index 455d074..99e4cf7 100644
--- a/vcr/config.py
+++ b/vcr/config.py
@@ -2,25 +2,19 @@ import copy
 import functools
 import inspect
 import os
-import types
 
 import six
 
 from .compat import collections
 from .cassette import Cassette
 from .serializers import yamlserializer, jsonserializer
-from .util import compose, auto_decorate
+from .util import compose
 from . import matchers
 from . import filters
 
 
 class VCR(object):
 
-    @staticmethod
-    def is_test_method(method_name, function):
-        return method_name.startswith('test') and \
-            isinstance(function, types.FunctionType)
-
     @staticmethod
     def ensure_suffix(suffix):
         def ensure(path):
@@ -29,7 +23,7 @@ class VCR(object):
             return path
         return ensure
 
-    def __init__(self, path_transformer=lambda x: x, before_record_request=None,
+    def __init__(self, path_transformer=None, before_record_request=None,
                  custom_patches=(), filter_query_parameters=(), ignore_hosts=(),
                  record_mode="once", ignore_localhost=False, filter_headers=(),
                  before_record_response=None, filter_post_data_parameters=(),
@@ -114,7 +108,7 @@ class VCR(object):
         matcher_names = kwargs.get('match_on', self.match_on)
         path_transformer = kwargs.get(
             'path_transformer',
-            self.path_transformer or self.ensure_suffix('.yaml')
+            self.path_transformer
         )
         func_path_generator = kwargs.get(
             'func_path_generator',
@@ -208,7 +202,7 @@ class VCR(object):
         if filter_query_parameters:
             filter_functions.append(functools.partial(
                 filters.remove_query_parameters,
-               query_parameters_to_remove=filter_query_parameters
+                query_parameters_to_remove=filter_query_parameters
             ))
         if filter_post_data_parameters:
             filter_functions.append(
@@ -256,7 +250,3 @@ class VCR(object):
 
     def register_matcher(self, name, matcher):
         self.matchers[name] = matcher
-
-    def test_case(self, predicate=None):
-        predicate = predicate or self.is_test_method
-        return six.with_metaclass(auto_decorate(self.use_cassette, predicate))
diff --git a/vcr/util.py b/vcr/util.py
index dd66320..b36ff75 100644
--- a/vcr/util.py
+++ b/vcr/util.py
@@ -1,6 +1,4 @@
 import collections
-import types
-
 
 # Shamelessly stolen from https://github.com/kennethreitz/requests/blob/master/requests/structures.py
 class CaseInsensitiveDict(collections.MutableMapping):
@@ -83,8 +81,9 @@ def partition_dict(predicate, dictionary):
 def compose(*functions):
     def composed(incoming):
         res = incoming
-        for function in functions[::-1]:
-            res = function(res)
+        for function in reversed(functions):
+            if function:
+                res = function(res)
         return res
     return composed
 
@@ -92,30 +91,3 @@ def read_body(request):
     if hasattr(request.body, 'read'):
         return request.body.read()
     return request.body
-
-
-def auto_decorate(
-    decorator,
-    predicate=lambda name, value: isinstance(value, types.FunctionType)
-):
-    def maybe_decorate(attribute, value):
-        if predicate(attribute, value):
-            value = decorator(value)
-        return value
-
-    class DecorateAll(type):
-
-        def __setattr__(cls, attribute, value):
-            return super(DecorateAll, cls).__setattr__(
-                attribute, maybe_decorate(attribute, value)
-            )
-
-        def __new__(cls, name, bases, attributes_dict):
-            new_attributes_dict = dict(
-                (attribute, maybe_decorate(attribute, value))
-                for attribute, value in attributes_dict.items()
-            )
-            return super(DecorateAll, cls).__new__(
-                cls, name, bases, new_attributes_dict
-            )
-    return DecorateAll


Test patch:
diff --git a/tests/unit/test_cassettes.py b/tests/unit/test_cassettes.py
index e1e9fb9..413b154 100644
--- a/tests/unit/test_cassettes.py
+++ b/tests/unit/test_cassettes.py
@@ -245,6 +245,13 @@ def test_path_transformer_with_context_manager():
         assert cassette._path == 'a'
 
 
+def test_path_transformer_None():
+    with Cassette.use(
+        path='a', path_transformer=None,
+    ) as cassette:
+        assert cassette._path == 'a'
+
+
 def test_func_path_generator():
     def generator(function):
         return os.path.join(os.path.dirname(inspect.getfile(function)),
diff --git a/tests/unit/test_vcr.py b/tests/unit/test_vcr.py
index 7150f15..6cbf21b 100644
--- a/tests/unit/test_vcr.py
+++ b/tests/unit/test_vcr.py
@@ -1,13 +1,11 @@
 import os
 
 import pytest
-from six.moves import http_client as httplib
 
 from vcr import VCR, use_cassette
 from vcr.compat import mock
 from vcr.request import Request
 from vcr.stubs import VCRHTTPSConnection
-from vcr.patch import _HTTPConnection, force_reset
 
 
 def test_vcr_use_cassette():
@@ -100,6 +98,28 @@ def test_vcr_before_record_response_iterable():
             assert mock_filter.call_count == 1
 
 
+def test_vcr_path_transformer():
+    # Regression test for #199
+
+    # Prevent actually saving the cassette
+    with mock.patch('vcr.cassette.save_cassette'):
+
+        # Baseline: path should be unchanged
+        vcr = VCR()
+        with vcr.use_cassette('test') as cassette:
+            assert cassette._path == 'test'
+
+        # Regression test: path_transformer=None should do the same.
+        vcr = VCR(path_transformer=None)
+        with vcr.use_cassette('test') as cassette:
+            assert cassette._path == 'test'
+
+        # and it should still work with cassette_library_dir
+        vcr = VCR(cassette_library_dir='/foo')
+        with vcr.use_cassette('test') as cassette:
+            assert cassette._path == '/foo/test'
+
+
 @pytest.fixture
 def random_fixture():
     return 1
@@ -245,7 +265,6 @@ def test_path_transformer():
 
 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__),
@@ -277,29 +296,3 @@ def test_additional_matchers():
 
     function_defaults()
     function_additional()
-
-
-class TestVCRClass(VCR().test_case()):
-
-    def no_decoration(self):
-        assert httplib.HTTPConnection == _HTTPConnection
-        self.test_dynamically_added()
-        assert httplib.HTTPConnection == _HTTPConnection
-
-    def test_one(self):
-        with force_reset():
-            self.no_decoration()
-        with force_reset():
-            self.test_two()
-        assert httplib.HTTPConnection != _HTTPConnection
-
-    def test_two(self):
-        assert httplib.HTTPConnection != _HTTPConnection
-
-
-def test_dynamically_added(self):
-    assert httplib.HTTPConnection != _HTTPConnection
-
-
-TestVCRClass.test_dynamically_added = test_dynamically_added
-del test_dynamically_added