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
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

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)
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
 

pip install -e .
parse_log_pytest
requirements.txt
pytest pytest-localserver mock
apt-get update apt-get install -y gcc
3.9
requirements/base.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
backport_collections==0.1
contextlib2==21.6.0
exceptiongroup==1.2.2
iniconfig==2.1.0
MarkupSafe==3.0.2
mock==5.2.0
packaging==24.2
pluggy==1.5.0
pytest==8.3.5
pytest-localserver==0.9.0.post0
PyYAML==6.0.2
six==1.17.0
tomli==2.2.1
-e git+https://github.com/kevin1024/vcrpy.git@d14888ccd87ea82ed4252958393a45eb05aea866#egg=vcrpy
Werkzeug==3.1.3
wrapt==1.17.2

name: vcrpy
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - backport-collections==0.1
      - contextlib2==21.6.0
      - exceptiongroup==1.2.2
      - iniconfig==2.1.0
      - markupsafe==3.0.2
      - mock==5.2.0
      - packaging==24.2
      - pluggy==1.5.0
      - pytest==8.3.5
      - pytest-localserver==0.9.0.post0
      - pyyaml==6.0.2
      - six==1.17.0
      - tomli==2.2.1
      - werkzeug==3.1.3
      - wrapt==1.17.2
prefix: /opt/conda/envs/vcrpy

tests/unit/test_cassettes.py::test_cassette_load tests/unit/test_cassettes.py::test_cassette_not_played tests/unit/test_cassettes.py::test_cassette_append tests/unit/test_cassettes.py::test_cassette_len tests/unit/test_cassettes.py::test_cassette_contains tests/unit/test_cassettes.py::test_cassette_responses_of tests/unit/test_cassettes.py::test_cassette_get_missing_response tests/unit/test_cassettes.py::test_cassette_cant_read_same_request_twice tests/unit/test_cassettes.py::test_function_decorated_with_use_cassette_can_be_invoked_multiple_times tests/unit/test_cassettes.py::test_arg_getter_functionality tests/unit/test_cassettes.py::test_cassette_not_all_played tests/unit/test_cassettes.py::test_cassette_all_played tests/unit/test_cassettes.py::test_before_record_response tests/unit/test_cassettes.py::test_nesting_cassette_context_managers tests/unit/test_cassettes.py::test_nesting_context_managers_by_checking_references_of_http_connection tests/unit/test_cassettes.py::test_custom_patchers tests/unit/test_cassettes.py::test_decorated_functions_are_reentrant tests/unit/test_cassettes.py::test_cassette_use_called_without_path_uses_function_to_generate_path tests/unit/test_cassettes.py::test_path_transformer_with_function_path tests/unit/test_cassettes.py::test_path_transformer_with_context_manager tests/unit/test_cassettes.py::test_func_path_generator tests/unit/test_serialize.py::test_deserialize_old_yaml_cassette tests/unit/test_serialize.py::test_deserialize_old_json_cassette tests/unit/test_serialize.py::test_deserialize_new_yaml_cassette tests/unit/test_serialize.py::test_deserialize_new_json_cassette tests/unit/test_serialize.py::test_serialize_constructs_UnicodeDecodeError tests/unit/test_vcr.py::test_vcr_use_cassette tests/unit/test_vcr.py::test_vcr_before_record_request_params tests/unit/test_vcr.py::test_fixtures_with_use_cassette tests/unit/test_vcr.py::test_custom_patchers tests/unit/test_vcr.py::test_inject_cassette tests/unit/test_vcr.py::test_with_current_defaults tests/unit/test_vcr.py::test_cassette_library_dir_with_decoration_and_no_explicit_path tests/unit/test_vcr.py::test_cassette_library_dir_with_decoration_and_explicit_path tests/unit/test_vcr.py::test_cassette_library_dir_with_decoration_and_super_explicit_path tests/unit/test_vcr.py::test_cassette_library_dir_with_path_transformer tests/unit/test_vcr.py::test_use_cassette_with_no_extra_invocation tests/unit/test_vcr.py::test_path_transformer tests/unit/test_vcr.py::test_cassette_name_generator_defaults_to_using_module_function_defined_in tests/unit/test_vcr.py::test_ensure_suffix
vcr/config.py setup.py vcr/cassette.py .travis.yml vcr/filters.py vcr/patch.py vcr/compat.py
vcr/config.py setup.py vcr/cassette.py .travis.yml vcr/filters.py vcr/patch.py vcr/compat.py