diff --git a/.travis.yml b/.travis.yml index 2f7fac7..da6f9e8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,10 +32,7 @@ matrix: - env: TOX_SUFFIX="boto" - env: TOX_SUFFIX="boto3" exclude: - - env: TOX_SUFFIX="flakes" - python: 2.6 - - env: TOX_SUFFIX="flakes" - python: 2.7 + # Only run flakes on a single Python 2.x and a single 3.x - env: TOX_SUFFIX="flakes" python: 3.4 - env: TOX_SUFFIX="flakes" @@ -43,30 +40,28 @@ matrix: - env: TOX_SUFFIX="flakes" python: pypy - env: TOX_SUFFIX="flakes" - python: pypy3 + python: "pypy3.5-5.9.0" - env: TOX_SUFFIX="boto" python: 3.6 + # Requests 1.x only supports Python <= 3.3 - env: TOX_SUFFIX="requests1" python: 3.4 - env: TOX_SUFFIX="requests1" python: 3.5 - env: TOX_SUFFIX="requests1" python: 3.6 - - env: TOX_SUFFIX="aiohttp" - python: 2.6 + - env: TOX_SUFFIX="requests1" + python: "pypy3.5-5.9.0" - env: TOX_SUFFIX="aiohttp" python: 2.7 - env: TOX_SUFFIX="aiohttp" python: pypy - - env: TOX_SUFFIX="aiohttp" - python: pypy3 python: -- 2.6 - 2.7 - 3.5 - 3.6 - pypy -- pypy3 +- "pypy3.5-5.9.0" install: - pip install tox-travis - if [[ $TOX_SUFFIX != 'flakes' ]]; then python setup.py install ; fi diff --git a/README.rst b/README.rst index 85854f5..5aebd90 100644 --- a/README.rst +++ b/README.rst @@ -1,4 +1,4 @@ -|PyPI| |Build Status| |Waffle Ready| |Gitter| +|PyPI| |Python versions| |Build Status| |Waffle Ready| |Gitter| VCR.py ====== @@ -63,6 +63,8 @@ more details .. |PyPI| image:: https://img.shields.io/pypi/v/vcrpy.svg :target: https://pypi.python.org/pypi/vcrpy-unittest +.. |Python versions| image:: https://img.shields.io/pypi/pyversions/vcrpy-unittest.svg + :target: https://pypi.python.org/pypi/vcrpy-unittest .. |Build Status| image:: https://secure.travis-ci.org/kevin1024/vcrpy.png?branch=master :target: http://travis-ci.org/kevin1024/vcrpy .. |Waffle Ready| image:: https://badge.waffle.io/kevin1024/vcrpy.png?label=ready&title=waffle diff --git a/docs/installation.rst b/docs/installation.rst index 423ba51..cc99e34 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -9,7 +9,7 @@ with pip:: Compatibility ------------- -VCR.py supports Python 2.6 and 2.7, 3.3, 3.4, and +VCR.py supports Python 2.7 and 3.4+, and `pypy `__. The following http libraries are supported: diff --git a/setup.py b/setup.py index 63b66e2..d49385c 100644 --- a/setup.py +++ b/setup.py @@ -28,9 +28,7 @@ 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'], + ':python_version in "2.7"': ['contextlib2', 'mock'], ':python_version in "3.4, 3.5, 3.6"': ['yarl'], } @@ -66,6 +64,7 @@ setup( author_email='me@kevinmccarthy.org', url='https://github.com/kevin1024/vcrpy', packages=find_packages(exclude=excluded_packages), + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', install_requires=install_requires, extras_require=extras_require, license='MIT', @@ -75,7 +74,14 @@ setup( 'Environment :: Console', 'Intended Audience :: Developers', 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: Implementation :: CPython', + 'Programming Language :: Python :: Implementation :: PyPy', 'Topic :: Software Development :: Testing', 'Topic :: Internet :: WWW/HTTP', 'License :: OSI Approved :: MIT License', diff --git a/tests/integration/test_ignore.py b/tests/integration/test_ignore.py index 164edb8..9cfb586 100644 --- a/tests/integration/test_ignore.py +++ b/tests/integration/test_ignore.py @@ -26,9 +26,9 @@ def test_ignore_localhost(tmpdir, httpbin): with overridden_dns({'httpbin.org': '127.0.0.1'}): cass_file = str(tmpdir.join('filter_qs.yaml')) with vcr.use_cassette(cass_file, ignore_localhost=True) as cass: - urlopen('http://localhost:{0}/'.format(httpbin.port)) + urlopen('http://localhost:{}/'.format(httpbin.port)) assert len(cass) == 0 - urlopen('http://httpbin.org:{0}/'.format(httpbin.port)) + urlopen('http://httpbin.org:{}/'.format(httpbin.port)) assert len(cass) == 1 @@ -39,9 +39,9 @@ def test_ignore_httpbin(tmpdir, httpbin): cass_file, ignore_hosts=['httpbin.org'] ) as cass: - urlopen('http://httpbin.org:{0}/'.format(httpbin.port)) + urlopen('http://httpbin.org:{}/'.format(httpbin.port)) assert len(cass) == 0 - urlopen('http://localhost:{0}/'.format(httpbin.port)) + urlopen('http://localhost:{}/'.format(httpbin.port)) assert len(cass) == 1 @@ -53,8 +53,8 @@ def test_ignore_localhost_and_httpbin(tmpdir, httpbin): ignore_hosts=['httpbin.org'], ignore_localhost=True ) as cass: - urlopen('http://httpbin.org:{0}'.format(httpbin.port)) - urlopen('http://localhost:{0}'.format(httpbin.port)) + urlopen('http://httpbin.org:{}'.format(httpbin.port)) + urlopen('http://localhost:{}'.format(httpbin.port)) assert len(cass) == 0 @@ -62,12 +62,12 @@ def test_ignore_localhost_twice(tmpdir, httpbin): with overridden_dns({'httpbin.org': '127.0.0.1'}): cass_file = str(tmpdir.join('filter_qs.yaml')) with vcr.use_cassette(cass_file, ignore_localhost=True) as cass: - urlopen('http://localhost:{0}'.format(httpbin.port)) + urlopen('http://localhost:{}'.format(httpbin.port)) assert len(cass) == 0 - urlopen('http://httpbin.org:{0}'.format(httpbin.port)) + urlopen('http://httpbin.org:{}'.format(httpbin.port)) assert len(cass) == 1 with vcr.use_cassette(cass_file, ignore_localhost=True) as cass: assert len(cass) == 1 - urlopen('http://localhost:{0}'.format(httpbin.port)) - urlopen('http://httpbin.org:{0}'.format(httpbin.port)) + urlopen('http://localhost:{}'.format(httpbin.port)) + urlopen('http://httpbin.org:{}'.format(httpbin.port)) assert len(cass) == 1 diff --git a/tests/integration/test_requests.py b/tests/integration/test_requests.py index 4229a0f..6e1511d 100644 --- a/tests/integration/test_requests.py +++ b/tests/integration/test_requests.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- '''Test requests' interaction with vcr''' +import platform import pytest +import sys import vcr from assertions import assert_cassette_empty, assert_is_json @@ -115,6 +117,9 @@ def test_post_chunked_binary(tmpdir, httpbin): @pytest.mark.xfail('sys.version_info >= (3, 6)', strict=True, raises=ConnectionError) +@pytest.mark.xfail((3, 5) < sys.version_info < (3, 6) and + platform.python_implementation() == 'CPython', + reason='Fails on CPython 3.5') def test_post_chunked_binary_secure(tmpdir, httpbin_secure): '''Ensure that we can send chunked binary without breaking while trying to concatenate bytes with str.''' data1 = iter([b'data', b'to', b'send']) @@ -249,10 +254,8 @@ def test_nested_cassettes_with_session_created_before_nesting(httpbin_both, tmpd def test_post_file(tmpdir, httpbin_both): '''Ensure that we handle posting a file.''' url = httpbin_both + '/post' - with vcr.use_cassette(str(tmpdir.join('post_file.yaml'))) as cass: - # Don't use 2.7+ only style ',' separated with here because we support python 2.6 - with open('tox.ini') as f: - original_response = requests.post(url, f).content + with vcr.use_cassette(str(tmpdir.join('post_file.yaml'))) as cass, open('tox.ini') as f: + original_response = requests.post(url, f).content # This also tests that we do the right thing with matching the body when they are files. with vcr.use_cassette(str(tmpdir.join('post_file.yaml')), diff --git a/tests/integration/test_urllib3.py b/tests/integration/test_urllib3.py index 51618c7..d187f7b 100644 --- a/tests/integration/test_urllib3.py +++ b/tests/integration/test_urllib3.py @@ -56,7 +56,7 @@ def test_body(tmpdir, httpbin_both, verify_pool_mgr): def test_auth(tmpdir, httpbin_both, verify_pool_mgr): '''Ensure that we can handle basic auth''' auth = ('user', 'passwd') - headers = urllib3.util.make_headers(basic_auth='{0}:{1}'.format(*auth)) + headers = urllib3.util.make_headers(basic_auth='{}:{}'.format(*auth)) url = httpbin_both.url + '/basic-auth/user/passwd' with vcr.use_cassette(str(tmpdir.join('auth.yaml'))): one = verify_pool_mgr.request('GET', url, headers=headers) @@ -70,7 +70,7 @@ def test_auth(tmpdir, httpbin_both, verify_pool_mgr): def test_auth_failed(tmpdir, httpbin_both, verify_pool_mgr): '''Ensure that we can save failed auth statuses''' auth = ('user', 'wrongwrongwrong') - headers = urllib3.util.make_headers(basic_auth='{0}:{1}'.format(*auth)) + headers = urllib3.util.make_headers(basic_auth='{}:{}'.format(*auth)) url = httpbin_both.url + '/basic-auth/user/passwd' with vcr.use_cassette(str(tmpdir.join('auth-failed.yaml'))) as cass: # Ensure that this is empty to begin with diff --git a/tests/integration/test_wild.py b/tests/integration/test_wild.py index a0a5a9a..65c76e2 100644 --- a/tests/integration/test_wild.py +++ b/tests/integration/test_wild.py @@ -1,5 +1,6 @@ +import multiprocessing import pytest -from six.moves import xmlrpc_client +from six.moves import xmlrpc_client, xmlrpc_server requests = pytest.importorskip("requests") @@ -80,13 +81,27 @@ def test_amazon_doctype(tmpdir): assert 'html' in r.text -def test_xmlrpclib(tmpdir): +@pytest.yield_fixture(scope='session') +def rpc_server(): + httpd = xmlrpc_server.SimpleXMLRPCServer(('', 0)) + httpd.register_function(pow) + proxy_process = multiprocessing.Process( + target=httpd.serve_forever, + ) + try: + proxy_process.start() + yield 'http://{}:{}'.format(*httpd.server_address) + finally: + proxy_process.terminate() + + +def test_xmlrpclib(tmpdir, rpc_server): with vcr.use_cassette(str(tmpdir.join('xmlrpcvideo.yaml'))): - roundup_server = xmlrpc_client.ServerProxy('http://bugs.python.org/xmlrpc', allow_none=True) - original_schema = roundup_server.schema() + roundup_server = xmlrpc_client.ServerProxy(rpc_server, allow_none=True) + original_schema = roundup_server.pow(2, 4) with vcr.use_cassette(str(tmpdir.join('xmlrpcvideo.yaml'))): - roundup_server = xmlrpc_client.ServerProxy('http://bugs.python.org/xmlrpc', allow_none=True) - second_schema = roundup_server.schema() + roundup_server = xmlrpc_client.ServerProxy(rpc_server, allow_none=True) + second_schema = roundup_server.pow(2, 4) assert original_schema == second_schema diff --git a/tests/unit/test_matchers.py b/tests/unit/test_matchers.py index 5efc507..9889b4f 100644 --- a/tests/unit/test_matchers.py +++ b/tests/unit/test_matchers.py @@ -22,7 +22,7 @@ def assert_matcher(matcher_name): matcher = getattr(matchers, matcher_name) for k1, k2 in itertools.permutations(REQUESTS, 2): matched = matcher(REQUESTS[k1], REQUESTS[k2]) - if matcher_name in set((k1, k2)): + if matcher_name in {k1, k2}: assert not matched else: assert matched @@ -31,7 +31,7 @@ def assert_matcher(matcher_name): def test_uri_matcher(): for k1, k2 in itertools.permutations(REQUESTS, 2): matched = matchers.uri(REQUESTS[k1], REQUESTS[k2]) - if set((k1, k2)) != set(('base', 'method')): + if {k1, k2} != {'base', 'method'}: assert not matched else: assert matched diff --git a/tests/unit/test_vcr.py b/tests/unit/test_vcr.py index 3592ef0..5a6bb6e 100644 --- a/tests/unit/test_vcr.py +++ b/tests/unit/test_vcr.py @@ -319,11 +319,11 @@ def test_additional_matchers(): @vcr.use_cassette def function_defaults(cassette): - assert set(cassette._match_on) == set([vcr.matchers['uri']]) + assert set(cassette._match_on) == {vcr.matchers['uri']} @vcr.use_cassette(additional_matchers=('body',)) def function_additional(cassette): - assert set(cassette._match_on) == set([vcr.matchers['uri'], vcr.matchers['body']]) + assert set(cassette._match_on) == {vcr.matchers['uri'], vcr.matchers['body']} function_defaults() function_additional() diff --git a/tox.ini b/tox.ini index 93ebad5..51e3aae 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = {py26,py27,py35,py36,pypy,pypy3}-{flakes,requests218,requests216,requests213,requests211,requests27,requests26,requests25,requests24,requests23,requests22,requests1,httplib2,urllib319,urllib3110,urllib3121,tornado3,tornado4,boto,boto3,aiohttp} +envlist = {py27,py35,py36,pypy}-{flakes,requests218,requests216,requests213,requests211,requests27,requests26,requests25,requests24,requests23,requests22,requests1,httplib2,urllib319,urllib3110,urllib3121,tornado3,tornado4,boto,boto3,aiohttp} [testenv:flakes] skipsdist = True @@ -13,8 +13,7 @@ deps = flake8 commands = ./runtests.sh {posargs} deps = - # httpbin fails with latest Flask, so we pin it - Flask==0.10.1 + Flask mock pytest pytest-httpbin @@ -34,12 +33,12 @@ deps = urllib319: urllib3==1.9.1 urllib3110: urllib3==1.10.2 urllib3121: urllib3==1.21.1 - {py26,py27,py35,py36,pypy}-tornado3: tornado>=3,<4 - {py26,py27,py35,py36,pypy}-tornado4: tornado>=4,<5 - {py26,py27,py35,py36,pypy}-tornado3: pytest-tornado - {py26,py27,py35,py36,pypy}-tornado4: pytest-tornado - {py26,py27,py35,py36}-tornado3: pycurl - {py26,py27,py35,py36}-tornado4: pycurl + {py27,py35,py36,pypy}-tornado3: tornado>=3,<4 + {py27,py35,py36,pypy}-tornado4: tornado>=4,<5 + {py27,py35,py36,pypy}-tornado3: pytest-tornado + {py27,py35,py36,pypy}-tornado4: pytest-tornado + {py27,py35,py36}-tornado3: pycurl + {py27,py35,py36}-tornado4: pycurl boto: boto boto3: boto3 aiohttp: aiohttp diff --git a/vcr/cassette.py b/vcr/cassette.py index fc3f0fa..4c95877 100644 --- a/vcr/cassette.py +++ b/vcr/cassette.py @@ -1,10 +1,11 @@ +import collections import sys import inspect import logging import wrapt -from .compat import contextlib, collections +from .compat import contextlib from .errors import UnhandledHTTPRequestError from .matchers import requests_match, uri, method from .patch import CassettePatcherBuilder @@ -303,7 +304,7 @@ class Cassette(object): pass def __str__(self): - return "".format( + return "".format( len(self) ) diff --git a/vcr/compat.py b/vcr/compat.py index e76c68f..6709feb 100644 --- a/vcr/compat.py +++ b/vcr/compat.py @@ -11,8 +11,4 @@ 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'] +__all__ = ['mock', 'contextlib'] diff --git a/vcr/config.py b/vcr/config.py index cfc890d..48fb7fe 100644 --- a/vcr/config.py +++ b/vcr/config.py @@ -1,4 +1,5 @@ import copy +import collections import functools import inspect import os @@ -6,7 +7,6 @@ import types import six -from .compat import collections from .cassette import Cassette from .serializers import yamlserializer, jsonserializer from .persisters.filesystem import FilesystemPersister @@ -78,7 +78,7 @@ class VCR(object): serializer = self.serializers[serializer_name] except KeyError: raise KeyError( - "Serializer {0} doesn't exist or isn't registered".format( + "Serializer {} doesn't exist or isn't registered".format( serializer_name ) ) @@ -91,7 +91,7 @@ class VCR(object): matchers.append(self.matchers[m]) except KeyError: raise KeyError( - "Matcher {0} doesn't exist or isn't registered".format(m) + "Matcher {} doesn't exist or isn't registered".format(m) ) return matchers diff --git a/vcr/matchers.py b/vcr/matchers.py index 8fe334e..ad04a45 100644 --- a/vcr/matchers.py +++ b/vcr/matchers.py @@ -90,12 +90,12 @@ def _log_matches(r1, r2, matches): differences = [m for m in matches if not m[0]] if differences: log.debug( - "Requests {0} and {1} differ according to " - "the following matchers: {2}".format(r1, r2, differences) + "Requests {} and {} differ according to " + "the following matchers: {}".format(r1, r2, differences) ) def requests_match(r1, r2, matchers): matches = [(m(r1, r2), m) for m in matchers] _log_matches(r1, r2, matches) - return all([m[0] for m in matches]) + return all(m[0] for m in matches) diff --git a/vcr/migration.py b/vcr/migration.py index 23b5e5e..ba5ae2c 100644 --- a/vcr/migration.py +++ b/vcr/migration.py @@ -59,7 +59,7 @@ def build_uri(**parts): port = parts['port'] scheme = parts['protocol'] default_port = {'https': 443, 'http': 80}[scheme] - parts['port'] = ':{0}'.format(port) if port != default_port else '' + parts['port'] = ':{}'.format(port) if port != default_port else '' return "{protocol}://{host}{port}{path}".format(**parts) @@ -161,7 +161,7 @@ def main(): for file_path in files: migrated = try_migrate(file_path) status = 'OK' if migrated else 'FAIL' - sys.stderr.write("[{0}] {1}\n".format(status, file_path)) + sys.stderr.write("[{}] {}\n".format(status, file_path)) sys.stderr.write("Done.\n") diff --git a/vcr/patch.py b/vcr/patch.py index 9de12a3..ac8ef29 100644 --- a/vcr/patch.py +++ b/vcr/patch.py @@ -169,7 +169,7 @@ class CassettePatcherBuilder(object): bases = (base_class,) if not issubclass(base_class, object): # Check for old style class bases += (object,) - return type('{0}{1}'.format(base_class.__name__, self._cassette._path), + return type('{}{}'.format(base_class.__name__, self._cassette._path), bases, dict(cassette=self._cassette)) @_build_patchers_from_mock_triples_decorator diff --git a/vcr/request.py b/vcr/request.py index 63e451e..882db69 100644 --- a/vcr/request.py +++ b/vcr/request.py @@ -81,7 +81,7 @@ class Request(object): return self.scheme def __str__(self): - return "".format(self.method, self.uri) + return "".format(self.method, self.uri) def __repr__(self): return self.__str__() diff --git a/vcr/stubs/__init__.py b/vcr/stubs/__init__.py index 67cb5e7..5ab819d 100644 --- a/vcr/stubs/__init__.py +++ b/vcr/stubs/__init__.py @@ -132,11 +132,11 @@ class VCRConnection(object): """ port = self.real_connection.port default_port = {'https': 443, 'http': 80}[self._protocol] - return ':{0}'.format(port) if port != default_port else '' + return ':{}'.format(port) if port != default_port else '' def _uri(self, url): """Returns request absolute URI""" - uri = "{0}://{1}{2}{3}".format( + uri = "{}://{}{}{}".format( self._protocol, self.real_connection.host, self._port_postfix(), @@ -146,7 +146,7 @@ class VCRConnection(object): def _url(self, uri): """Returns request selector url from absolute URI""" - prefix = "{0}://{1}{2}".format( + prefix = "{}://{}{}".format( self._protocol, self.real_connection.host, self._port_postfix(), @@ -161,7 +161,7 @@ class VCRConnection(object): body=body, headers=headers or {} ) - log.debug('Got {0}'.format(self._vcr_request)) + log.debug('Got {}'.format(self._vcr_request)) # Note: The request may not actually be finished at this point, so # I'm not sending the actual request until getresponse(). This @@ -180,7 +180,7 @@ class VCRConnection(object): body="", headers={} ) - log.debug('Got {0}'.format(self._vcr_request)) + log.debug('Got {}'.format(self._vcr_request)) def putheader(self, header, *values): self._vcr_request.headers[header] = values @@ -214,7 +214,7 @@ class VCRConnection(object): # then return it if self.cassette.can_play_response_for(self._vcr_request): log.info( - "Playing response for {0} from cassette".format( + "Playing response for {} from cassette".format( self._vcr_request ) ) @@ -236,7 +236,7 @@ class VCRConnection(object): # and return it. log.info( - "{0} not in cassette, sending to real server".format( + "{} not in cassette, sending to real server".format( self._vcr_request ) ) diff --git a/vcr/stubs/httplib2_stubs.py b/vcr/stubs/httplib2_stubs.py index b08f357..4f31537 100644 --- a/vcr/stubs/httplib2_stubs.py +++ b/vcr/stubs/httplib2_stubs.py @@ -13,9 +13,7 @@ class VCRHTTPConnectionWithTimeout(VCRHTTPConnection, HTTPConnection.__init__.''' # Delete the keyword arguments that HTTPConnection would not recognize - safe_keys = set( - ('host', 'port', 'strict', 'timeout', 'source_address') - ) + safe_keys = {'host', 'port', 'strict', 'timeout', 'source_address'} unknown_keys = set(kwargs.keys()) - safe_keys safe_kwargs = kwargs.copy() for kw in unknown_keys: @@ -33,7 +31,7 @@ class VCRHTTPSConnectionWithTimeout(VCRHTTPSConnection, def __init__(self, *args, **kwargs): # Delete the keyword arguments that HTTPSConnection would not recognize - safe_keys = set(( + safe_keys = { 'host', 'port', 'key_file', @@ -42,7 +40,7 @@ class VCRHTTPSConnectionWithTimeout(VCRHTTPSConnection, 'timeout', 'source_address', 'ca_certs', - )) + } unknown_keys = set(kwargs.keys()) - safe_keys safe_kwargs = kwargs.copy() for kw in unknown_keys: