diff --git a/README.rst b/README.rst index b24ea6f..23910bb 100644 --- a/README.rst +++ b/README.rst @@ -1,14 +1,16 @@ +|Build Status| |Stories in Ready| |Gitter| + VCR.py ====== -.. image:: vcr.png +.. figure:: https://raw.github.com/kevin1024/vcrpy/master/vcr.png :alt: vcr.py + vcr.py + This is a Python version of `Ruby's VCR library `__. -|Build Status| |Stories in Ready| |Gitter Chat| - What it does ------------ @@ -607,7 +609,7 @@ new API in version 1.0.x Changelog --------- -- 1.7.3 [#188] ``additional_matchers`` kwarg on ``use_cassette``. +- 1.7.3 [#188] ``additional_matchers`` kwarg on ``use_casstte``. [#191] Actually support passing multiple before_record_request functions (thanks @agriffis). - 1.7.2 [#186] Get effective_url in tornado (thanks @mvschaik), [#187] @@ -756,6 +758,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 Chat| image:: https://badges.gitter.im/Join%20Chat.svg +.. |Gitter| 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/tests/unit/test_vcr.py b/tests/unit/test_vcr.py index cbdcde4..7150f15 100644 --- a/tests/unit/test_vcr.py +++ b/tests/unit/test_vcr.py @@ -1,11 +1,13 @@ 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(): @@ -243,6 +245,7 @@ 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__), @@ -274,3 +277,29 @@ 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 diff --git a/vcr/config.py b/vcr/config.py index e2389db..455d074 100644 --- a/vcr/config.py +++ b/vcr/config.py @@ -2,19 +2,25 @@ 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 +from .util import compose, auto_decorate 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): @@ -202,7 +208,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( @@ -250,3 +256,7 @@ 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 8c5bd94..dd66320 100644 --- a/vcr/util.py +++ b/vcr/util.py @@ -1,4 +1,6 @@ import collections +import types + # Shamelessly stolen from https://github.com/kennethreitz/requests/blob/master/requests/structures.py class CaseInsensitiveDict(collections.MutableMapping): @@ -90,3 +92,30 @@ 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