Repository: kevin1024/vcrpy

Issue:
Dependency that is only compatible/needed with/in python 2 in setup.py
this causes pip install to fail (at least with my py3.4 system)

Discussion:
IvanMalison: installs are failing with
error in vcrpy setup command: Invalid environment marker: python_version<=2.4
IvanMalison: pkg_resources.DistributionNotFound: contextlib2
graingert: I've arsed this up pretty bad. But I know how to fix this. Please bare with me 😻
IvanMalison: Hah no worries. Thanks for the pr. This issue was annoying me.
IvanMalison: closes #147

Patch:
diff --git a/.travis.yml b/.travis.yml
index 594b492..93fea8b 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -33,7 +33,7 @@ python:
 - 3.4
 - pypy
 install:
-- pip install PyYAML pytest pytest-localserver --use-mirrors
+- pip install .
 - if [ $WITH_LIB = "requests1.x" ] ; then pip install requests==1.2.3; fi
 - if [ $WITH_LIB = "requests2.2" ] ; then pip install requests==2.2.1; fi
 - if [ $WITH_LIB = "requests2.3" ] ; then pip install requests==2.3.0; fi
diff --git a/setup.py b/setup.py
index 1c6ec59..aeeb415 100644
--- a/setup.py
+++ b/setup.py
@@ -19,6 +19,7 @@ class PyTest(TestCommand):
         errno = pytest.main(self.test_args)
         sys.exit(errno)
 
+
 setup(
     name='vcrpy',
     version='1.5.2',
@@ -31,8 +32,12 @@ setup(
     author_email='me@kevinmccarthy.org',
     url='https://github.com/kevin1024/vcrpy',
     packages=find_packages(exclude=("tests*",)),
-    install_requires=['PyYAML', 'mock', 'six>=1.5', 'contextlib2',
-                      'wrapt', 'backport_collections'],
+    install_requires=['PyYAML', 'wrapt', 'six>=1.5'],
+    extras_require = {
+        ':python_version in "2.4, 2.5, 2.6"':
+            ['contextlib2', 'backport_collections', 'mock'],
+        ':python_version in "2.7, 3.1, 3.2"': ['contextlib2', 'mock'],
+    },
     license='MIT',
     tests_require=['pytest', 'mock', 'pytest-localserver'],
     cmdclass={'test': PyTest},
diff --git a/vcr/cassette.py b/vcr/cassette.py
index 77b5395..87d2598 100644
--- a/vcr/cassette.py
+++ b/vcr/cassette.py
@@ -2,14 +2,11 @@
 import functools
 import logging
 
-import contextlib2
+
 import wrapt
-try:
-    from collections import Counter
-except ImportError:
-    from backport_collections import Counter
 
 # Internal imports
+from .compat import contextlib, collections
 from .errors import UnhandledHTTPRequestError
 from .matchers import requests_match, uri, method
 from .patch import CassettePatcherBuilder
@@ -43,7 +40,7 @@ class CassetteContextDecorator(object):
         self.__finish = None
 
     def _patch_generator(self, cassette):
-        with contextlib2.ExitStack() as exit_stack:
+        with contextlib.ExitStack() as exit_stack:
             for patcher in CassettePatcherBuilder(cassette).build():
                 exit_stack.enter_context(patcher)
             log.debug('Entered context for cassette at {0}.'.format(cassette._path))
@@ -148,7 +145,7 @@ class Cassette(object):
 
         # self.data is the list of (req, resp) tuples
         self.data = []
-        self.play_counts = Counter()
+        self.play_counts = collections.Counter()
         self.dirty = False
         self.rewound = False
 
diff --git a/vcr/compat.py b/vcr/compat.py
new file mode 100644
index 0000000..e76c68f
--- /dev/null
+++ b/vcr/compat.py
@@ -0,0 +1,18 @@
+try:
+    from unittest import mock
+except ImportError:
+    import mock
+
+try:
+    import contextlib
+except ImportError:
+    import contextlib2 as contextlib
+else:
+    if not hasattr(contextlib, 'ExitStack'):
+        import contextlib2 as contextlib
+
+import collections
+if not hasattr(collections, 'Counter'):
+    import backport_collections as collections
+
+__all__ = ['mock', 'contextlib', 'collections']
diff --git a/vcr/config.py b/vcr/config.py
index fb03136..1faef3b 100644
--- a/vcr/config.py
+++ b/vcr/config.py
@@ -1,4 +1,3 @@
-import collections
 import copy
 import functools
 import inspect
@@ -6,6 +5,7 @@ import os
 
 import six
 
+from .compat import collections
 from .cassette import Cassette
 from .serializers import yamlserializer, jsonserializer
 from .util import compose
diff --git a/vcr/filters.py b/vcr/filters.py
index 84f06c8..14159d0 100644
--- a/vcr/filters.py
+++ b/vcr/filters.py
@@ -1,12 +1,10 @@
 from six import BytesIO, text_type
 from six.moves.urllib.parse import urlparse, urlencode, urlunparse
-try:
-    from collections import OrderedDict
-except ImportError:
-    from backport_collections import OrderedDict
 import copy
 import json
 
+from .compat import collections
+
 
 def remove_headers(request, headers_to_remove):
     headers = copy.copy(request.headers)
@@ -40,7 +38,7 @@ def remove_post_data_parameters(request, post_data_parameters_to_remove):
                     del json_data[k]
             request.body = json.dumps(json_data).encode('utf-8')
         else:
-            post_data = OrderedDict()
+            post_data = collections.OrderedDict()
             if isinstance(request.body, text_type):
                 request.body = request.body.encode('utf-8')
 
diff --git a/vcr/patch.py b/vcr/patch.py
index 1d9edb5..caed9fa 100644
--- a/vcr/patch.py
+++ b/vcr/patch.py
@@ -2,9 +2,7 @@
 import functools
 import itertools
 
-import contextlib2
-import mock
-
+from .compat import contextlib, mock
 from .stubs import VCRHTTPConnection, VCRHTTPSConnection
 from six.moves import http_client as httplib
 
@@ -323,9 +321,9 @@ def reset_patchers():
                                 _CertValidatingHTTPSConnection)
 
 
-@contextlib2.contextmanager
+@contextlib.contextmanager
 def force_reset():
-    with contextlib2.ExitStack() as exit_stack:
+    with contextlib.ExitStack() as exit_stack:
         for patcher in reset_patchers():
             exit_stack.enter_context(patcher)
         yield


Test patch:
diff --git a/tests/unit/test_cassettes.py b/tests/unit/test_cassettes.py
index 947ffa4..e7d8b7c 100644
--- a/tests/unit/test_cassettes.py
+++ b/tests/unit/test_cassettes.py
@@ -3,11 +3,10 @@ import inspect
 import os
 
 from six.moves import http_client as httplib
-import contextlib2
-import mock
 import pytest
 import yaml
 
+from vcr.compat import mock, contextlib
 from vcr.cassette import Cassette
 from vcr.errors import UnhandledHTTPRequestError
 from vcr.patch import force_reset
@@ -158,7 +157,7 @@ def test_nesting_cassette_context_managers(*args):
     second_response = copy.deepcopy(first_response)
     second_response['body']['string'] = b'second_response'
 
-    with contextlib2.ExitStack() as exit_stack:
+    with contextlib.ExitStack() as exit_stack:
         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))
diff --git a/tests/unit/test_serialize.py b/tests/unit/test_serialize.py
index 5f2a9aa..41d97bd 100644
--- a/tests/unit/test_serialize.py
+++ b/tests/unit/test_serialize.py
@@ -1,6 +1,6 @@
-import mock
 import pytest
 
+from vcr.compat import mock
 from vcr.serialize import deserialize
 from vcr.serializers import yamlserializer, jsonserializer
 
diff --git a/tests/unit/test_vcr.py b/tests/unit/test_vcr.py
index d58d96c..8b1de97 100644
--- a/tests/unit/test_vcr.py
+++ b/tests/unit/test_vcr.py
@@ -1,9 +1,9 @@
 import os
 
-import mock
 import pytest
 
 from vcr import VCR, use_cassette
+from vcr.compat import mock
 from vcr.request import Request
 from vcr.stubs import VCRHTTPSConnection