mirror of
https://github.com/kevin1024/vcrpy.git
synced 2025-12-09 01:03:24 +00:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a46616ba6 | ||
|
|
57df0c6921 | ||
|
|
ddb29745a9 | ||
|
|
ac7c9244cc | ||
|
|
6da7cd0ea5 | ||
|
|
24df79b75f | ||
|
|
0800b99214 | ||
|
|
3dad89df3f | ||
|
|
5c9b0b4ccb | ||
|
|
5a848d277e | ||
|
|
c88c738df9 | ||
|
|
9a8067d8e7 | ||
|
|
787c6bdb77 | ||
|
|
c3298c25a3 | ||
|
|
2f4c803678 | ||
|
|
60145983bf | ||
|
|
b5c27f99d1 |
@@ -16,6 +16,7 @@ env:
|
||||
- TOX_SUFFIX="requests1"
|
||||
- TOX_SUFFIX="httplib2"
|
||||
- TOX_SUFFIX="boto"
|
||||
- TOX_SUFFIX="boto3"
|
||||
- TOX_SUFFIX="urllib317"
|
||||
- TOX_SUFFIX="urllib319"
|
||||
- TOX_SUFFIX="urllib3110"
|
||||
@@ -24,6 +25,7 @@ env:
|
||||
matrix:
|
||||
allow_failures:
|
||||
- env: TOX_SUFFIX="boto"
|
||||
- env: TOX_SUFFIX="boto3"
|
||||
exclude:
|
||||
- env: TOX_SUFFIX="boto"
|
||||
python: 3.3
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
Changelog
|
||||
---------
|
||||
- 1.9.0 Add support for boto3 (thanks @desdm, @foorbarna). Fix deepcopy issue
|
||||
for response headers when `decode_compressed_response` is enabled (thanks
|
||||
@nickdirienzo)
|
||||
- 1.8.0 Fix for Serialization errors with JSON adapter (thanks
|
||||
@aliaksandrb). Avoid concatenating bytes with strings (thanks
|
||||
@jaysonsantos). Exclude __pycache__ dirs & compiled files in sdist
|
||||
|
||||
2
setup.py
2
setup.py
@@ -51,7 +51,7 @@ except Exception:
|
||||
|
||||
setup(
|
||||
name='vcrpy',
|
||||
version='1.8.0',
|
||||
version='1.9.0',
|
||||
description=(
|
||||
"Automatically mock your HTTP interactions to simplify and "
|
||||
"speed up testing"
|
||||
|
||||
@@ -5,8 +5,12 @@ import boto # NOQA
|
||||
import boto.iam # NOQA
|
||||
from boto.s3.connection import S3Connection # NOQA
|
||||
from boto.s3.key import Key # NOQA
|
||||
from ConfigParser import DuplicateSectionError # NOQA
|
||||
import vcr # NOQA
|
||||
try: # NOQA
|
||||
from ConfigParser import DuplicateSectionError # NOQA
|
||||
except ImportError: # NOQA
|
||||
# python3
|
||||
from configparser import DuplicateSectionError # NOQA
|
||||
|
||||
|
||||
def test_boto_stubs(tmpdir):
|
||||
|
||||
67
tests/integration/test_boto3.py
Normal file
67
tests/integration/test_boto3.py
Normal file
@@ -0,0 +1,67 @@
|
||||
import pytest
|
||||
boto3 = pytest.importorskip("boto3")
|
||||
|
||||
import boto3 # NOQA
|
||||
import vcr # NOQA
|
||||
|
||||
bucket = 'boto3-demo-1337' # a bucket you can access
|
||||
key = 'test/my_test.txt' # key with r+w access
|
||||
content = 'hello world i am a string' # content to put in the test file
|
||||
|
||||
|
||||
def test_boto_stubs(tmpdir):
|
||||
with vcr.use_cassette(str(tmpdir.join('boto3-stubs.yml'))):
|
||||
# Perform the imports within the patched context so that
|
||||
# HTTPConnection, VerifiedHTTPSConnection refers to the patched version.
|
||||
from botocore.vendored.requests.packages.urllib3.connectionpool import \
|
||||
HTTPConnection, VerifiedHTTPSConnection
|
||||
from vcr.stubs.boto3_stubs import VCRRequestsHTTPConnection, VCRRequestsHTTPSConnection
|
||||
# Prove that the class was patched by the stub and that we can instantiate it.
|
||||
assert issubclass(HTTPConnection, VCRRequestsHTTPConnection)
|
||||
assert issubclass(VerifiedHTTPSConnection, VCRRequestsHTTPSConnection)
|
||||
HTTPConnection('hostname.does.not.matter')
|
||||
VerifiedHTTPSConnection('hostname.does.not.matter')
|
||||
|
||||
|
||||
def test_boto3_without_vcr():
|
||||
s3_resource = boto3.resource('s3')
|
||||
b = s3_resource.Bucket(bucket)
|
||||
b.put_object(Key=key, Body=content)
|
||||
|
||||
# retrieve content to check it
|
||||
o = s3_resource.Object(bucket, key).get()
|
||||
|
||||
# decode for python3
|
||||
assert content == o['Body'].read().decode('utf-8')
|
||||
|
||||
|
||||
def test_boto_medium_difficulty(tmpdir):
|
||||
s3_resource = boto3.resource('s3')
|
||||
b = s3_resource.Bucket(bucket)
|
||||
with vcr.use_cassette(str(tmpdir.join('boto3-medium.yml'))):
|
||||
b.put_object(Key=key, Body=content)
|
||||
o = s3_resource.Object(bucket, key).get()
|
||||
assert content == o['Body'].read().decode('utf-8')
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('boto3-medium.yml'))) as cass:
|
||||
b.put_object(Key=key, Body=content)
|
||||
o = s3_resource.Object(bucket, key).get()
|
||||
assert content == o['Body'].read().decode('utf-8')
|
||||
assert cass.all_played
|
||||
|
||||
|
||||
def test_boto_hardcore_mode(tmpdir):
|
||||
with vcr.use_cassette(str(tmpdir.join('boto3-hardcore.yml'))):
|
||||
s3_resource = boto3.resource('s3')
|
||||
b = s3_resource.Bucket(bucket)
|
||||
b.put_object(Key=key, Body=content)
|
||||
o = s3_resource.Object(bucket, key).get()
|
||||
assert content == o['Body'].read().decode('utf-8')
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join('boto3-hardcore.yml'))) as cass:
|
||||
s3_resource = boto3.resource('s3')
|
||||
b = s3_resource.Bucket(bucket)
|
||||
b.put_object(Key=key, Body=content)
|
||||
o = s3_resource.Object(bucket, key).get()
|
||||
assert content == o['Body'].read().decode('utf-8')
|
||||
assert cass.all_played
|
||||
@@ -1,6 +1,9 @@
|
||||
import vcr
|
||||
import zlib
|
||||
import six.moves.http_client as httplib
|
||||
|
||||
from assertions import assert_is_json
|
||||
|
||||
|
||||
def _headers_are_case_insensitive(host, port):
|
||||
conn = httplib.HTTPConnection(host, port)
|
||||
@@ -44,3 +47,39 @@ def test_multiple_headers(tmpdir, httpbin):
|
||||
inside = _multiple_header_value(httpbin)
|
||||
|
||||
assert outside == inside
|
||||
|
||||
|
||||
def test_original_decoded_response_is_not_modified(tmpdir, httpbin):
|
||||
testfile = str(tmpdir.join('decoded_response.yml'))
|
||||
host, port = httpbin.host, httpbin.port
|
||||
|
||||
conn = httplib.HTTPConnection(host, port)
|
||||
conn.request('GET', '/gzip')
|
||||
outside = conn.getresponse()
|
||||
|
||||
with vcr.use_cassette(testfile, decode_compressed_response=True):
|
||||
conn = httplib.HTTPConnection(host, port)
|
||||
conn.request('GET', '/gzip')
|
||||
inside = conn.getresponse()
|
||||
|
||||
# Assert that we do not modify the original response while appending
|
||||
# to the casssette.
|
||||
assert 'gzip' == inside.headers['content-encoding']
|
||||
|
||||
# They should effectively be the same response.
|
||||
inside_headers = (h for h in inside.headers.items() if h[0] != 'Date')
|
||||
outside_headers = (h for h in outside.getheaders() if h[0] != 'Date')
|
||||
assert set(inside_headers) == set(outside_headers)
|
||||
inside = zlib.decompress(inside.read(), 16+zlib.MAX_WBITS)
|
||||
outside = zlib.decompress(outside.read(), 16+zlib.MAX_WBITS)
|
||||
assert inside == outside
|
||||
|
||||
# Even though the above are raw bytes, the JSON data should have been
|
||||
# decoded and saved to the cassette.
|
||||
with vcr.use_cassette(testfile):
|
||||
conn = httplib.HTTPConnection(host, port)
|
||||
conn.request('GET', '/gzip')
|
||||
inside = conn.getresponse()
|
||||
|
||||
assert 'content-encoding' not in inside.headers
|
||||
assert_is_json(inside.read())
|
||||
|
||||
@@ -76,7 +76,7 @@ def test_amazon_doctype(tmpdir):
|
||||
# amazon gzips its homepage. For some reason, in requests 2.7, it's not
|
||||
# getting gunzipped.
|
||||
with vcr.use_cassette(str(tmpdir.join('amz.yml'))):
|
||||
r = requests.get('http://www.amazon.com')
|
||||
r = requests.get('http://www.amazon.com', verify=False)
|
||||
assert 'html' in r.text
|
||||
|
||||
|
||||
|
||||
5
tox.ini
5
tox.ini
@@ -1,5 +1,5 @@
|
||||
[tox]
|
||||
envlist = {py26,py27,py33,py34,pypy,pypy3}-{flakes,requests27,requests26,requests25,requests24,requests23,requests22,requests1,httplib2,urllib317,urllib319,urllib3110,tornado3,tornado4,boto}
|
||||
envlist = {py26,py27,py33,py34,pypy,pypy3}-{flakes,requests27,requests26,requests25,requests24,requests23,requests22,requests1,httplib2,urllib317,urllib319,urllib3110,tornado3,tornado4,boto,boto3}
|
||||
|
||||
[testenv:flakes]
|
||||
skipsdist = True
|
||||
@@ -13,6 +13,8 @@ deps = flake8
|
||||
commands =
|
||||
./runtests.sh {posargs}
|
||||
deps =
|
||||
# httpbin fails with latest Flask, so we pin it
|
||||
Flask==0.10.1
|
||||
mock
|
||||
pytest
|
||||
pytest-httpbin
|
||||
@@ -35,6 +37,7 @@ deps =
|
||||
{py26,py27,py33,py34}-tornado3: pycurl
|
||||
{py26,py27,py33,py34}-tornado4: pycurl
|
||||
boto: boto
|
||||
boto3: boto3
|
||||
|
||||
[flake8]
|
||||
max_line_length = 110
|
||||
|
||||
@@ -146,9 +146,11 @@ def decode_response(response):
|
||||
else: # encoding == 'deflate'
|
||||
return zlib.decompress(body)
|
||||
|
||||
# Deepcopy here in case `headers` contain objects that could
|
||||
# be mutated by a shallow copy and corrupt the real response.
|
||||
response = copy.deepcopy(response)
|
||||
headers = CaseInsensitiveDict(response['headers'])
|
||||
if is_compressed(headers):
|
||||
response = copy.deepcopy(response)
|
||||
encoding = headers['content-encoding'][0]
|
||||
headers['content-encoding'].remove(encoding)
|
||||
if not headers['content-encoding']:
|
||||
|
||||
38
vcr/patch.py
38
vcr/patch.py
@@ -22,6 +22,16 @@ else:
|
||||
_cpoolHTTPConnection = cpool.HTTPConnection
|
||||
_cpoolHTTPSConnection = cpool.HTTPSConnection
|
||||
|
||||
# Try to save the original types for boto3
|
||||
try:
|
||||
import botocore.vendored.requests.packages.urllib3.connectionpool as cpool
|
||||
except ImportError: # pragma: no cover
|
||||
pass
|
||||
else:
|
||||
_Boto3VerifiedHTTPSConnection = cpool.VerifiedHTTPSConnection
|
||||
_cpoolBoto3HTTPConnection = cpool.HTTPConnection
|
||||
_cpoolBoto3HTTPSConnection = cpool.HTTPSConnection
|
||||
|
||||
|
||||
# Try to save the original types for urllib3
|
||||
try:
|
||||
@@ -87,7 +97,7 @@ class CassettePatcherBuilder(object):
|
||||
|
||||
def build(self):
|
||||
return itertools.chain(
|
||||
self._httplib(), self._requests(), self._urllib3(),
|
||||
self._httplib(), self._requests(), self._boto3(), self._urllib3(),
|
||||
self._httplib2(), self._boto(), self._tornado(),
|
||||
self._build_patchers_from_mock_triples(
|
||||
self._cassette.custom_patches
|
||||
@@ -165,6 +175,14 @@ class CassettePatcherBuilder(object):
|
||||
from .stubs import requests_stubs
|
||||
return self._urllib3_patchers(cpool, requests_stubs)
|
||||
|
||||
def _boto3(self):
|
||||
try:
|
||||
import botocore.vendored.requests.packages.urllib3.connectionpool as cpool
|
||||
except ImportError: # pragma: no cover
|
||||
return ()
|
||||
from .stubs import boto3_stubs
|
||||
return self._urllib3_patchers(cpool, boto3_stubs)
|
||||
|
||||
def _patched_get_conn(self, connection_pool_class, connection_class_getter):
|
||||
get_conn = connection_pool_class._get_conn
|
||||
|
||||
@@ -353,6 +371,24 @@ def reset_patchers():
|
||||
yield mock.patch.object(cpool.HTTPConnectionPool, 'ConnectionCls', _HTTPConnection)
|
||||
yield mock.patch.object(cpool.HTTPSConnectionPool, 'ConnectionCls', _HTTPSConnection)
|
||||
|
||||
try:
|
||||
import botocore.vendored.requests.packages.urllib3.connectionpool as cpool
|
||||
except ImportError: # pragma: no cover
|
||||
pass
|
||||
else:
|
||||
# unpatch requests v1.x
|
||||
yield mock.patch.object(cpool, 'VerifiedHTTPSConnection', _Boto3VerifiedHTTPSConnection)
|
||||
yield mock.patch.object(cpool, 'HTTPConnection', _cpoolBoto3HTTPConnection)
|
||||
# unpatch requests v2.x
|
||||
if hasattr(cpool.HTTPConnectionPool, 'ConnectionCls'):
|
||||
yield mock.patch.object(cpool.HTTPConnectionPool, 'ConnectionCls',
|
||||
_cpoolBoto3HTTPConnection)
|
||||
yield mock.patch.object(cpool.HTTPSConnectionPool, 'ConnectionCls',
|
||||
_cpoolBoto3HTTPSConnection)
|
||||
|
||||
if hasattr(cpool, 'HTTPSConnection'):
|
||||
yield mock.patch.object(cpool, 'HTTPSConnection', _cpoolBoto3HTTPSConnection)
|
||||
|
||||
try:
|
||||
import httplib2 as cpool
|
||||
except ImportError: # pragma: no cover
|
||||
|
||||
15
vcr/stubs/boto3_stubs.py
Normal file
15
vcr/stubs/boto3_stubs.py
Normal file
@@ -0,0 +1,15 @@
|
||||
'''Stubs for boto3'''
|
||||
|
||||
from botocore.vendored.requests.packages.urllib3.connectionpool import HTTPConnection, VerifiedHTTPSConnection
|
||||
from ..stubs import VCRHTTPConnection, VCRHTTPSConnection
|
||||
|
||||
# urllib3 defines its own HTTPConnection classes, which boto3 goes ahead and assumes
|
||||
# you're using. It includes some polyfills for newer features missing in older pythons.
|
||||
|
||||
|
||||
class VCRRequestsHTTPConnection(VCRHTTPConnection, HTTPConnection):
|
||||
_baseclass = HTTPConnection
|
||||
|
||||
|
||||
class VCRRequestsHTTPSConnection(VCRHTTPSConnection, VerifiedHTTPSConnection):
|
||||
_baseclass = VerifiedHTTPSConnection
|
||||
Reference in New Issue
Block a user