1
0
mirror of https://github.com/kevin1024/vcrpy.git synced 2025-12-08 16:53:23 +00:00

Compare commits

...

15 Commits

Author SHA1 Message Date
Sebastian Pipping
a72d81cc9d [Revert me] Experiment with Python-Ubuntu combinations 2023-05-21 23:20:21 +02:00
Sebastian Pipping
470dd96c68 [Revert me] Debug Python SSL version 2023-05-21 23:20:21 +02:00
Sebastian Pipping
612f597aa9 [Revert me] Trigger on push to any branch 2023-05-21 23:12:23 +02:00
Sebastian Pipping
47be90add8 Stop installing libgnutls28-dev 2023-05-21 23:12:23 +02:00
Sebastian Pipping
ab3d8bf7c9 test_vcr.py: Clarify that test_vcr_before_record_request_params is an offline test 2023-05-20 16:21:01 +02:00
Sebastian Pipping
ec4fb9b0b3 tox.ini: Cover both urllib3 v1 and v2 2023-05-20 16:21:01 +02:00
Sebastian Pipping
f83f83a0c4 tox.ini: Drop needless "boto3: urllib3"
boto3 depends on botocore which in turn depends on urllib3.
2023-05-20 16:21:01 +02:00
Sebastian Pipping
ef2e1d895a Fix VCRHTTPResponse for requests.cookies.extract_cookies_to_jar 2023-05-20 16:21:01 +02:00
Sebastian Pipping
384d47714e Make test "test_cookies" more mean and helpful 2023-05-20 16:21:01 +02:00
Sebastian Pipping
3547ed966f Make VCRHTTPResponse interface satisfy urllib3.response.HTTPResponse 2023-05-20 16:21:01 +02:00
Sebastian Pipping
f1b921c211 Respect urllib3.response.HTTPResponse.data 2023-05-20 16:21:01 +02:00
Sebastian Pipping
ea5e20edc7 Make test_headers robust with regard to order of headers 2023-05-20 16:21:01 +02:00
Sebastian Pipping
b09c271a76 Tolerate urllib3.response.HTTPResponse.msg being None 2023-05-20 16:21:01 +02:00
Sonny V
ef7cb8cf50 build(tox.ini): revert pinning urllib to <2
In #690 a quick fix was introduced to get a green ci, this change should no longer be required.
2023-05-20 16:21:01 +02:00
Sonny V
c78b0c81e9 fix: use urllib3.connection where needed.
Since urllib3 v2 the re-export of connection.HTTPConnection in
urllib3.connectionpool was removed.

In this commit we use urllib3.connection where needed. Some references
to connectionpool.HTTPConnection are still there for backward
compatibility.

Closes #688
2023-05-20 16:21:01 +02:00
9 changed files with 100 additions and 50 deletions

View File

@@ -2,25 +2,38 @@ name: Test
on:
push:
branches:
- master
pull_request:
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
runs-on: ${{ matrix.runs-on }}
strategy:
fail-fast: false
matrix:
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "pypy-3.8"]
include:
- python-version: "3.7"
runs-on: ubuntu-20.04
- python-version: "3.8"
runs-on: ubuntu-20.04
- python-version: "3.9"
runs-on: ubuntu-20.04
- python-version: "3.10"
runs-on: ubuntu-22.04
- python-version: "3.11"
runs-on: ubuntu-22.04
- python-version: "pypy-3.7"
runs-on: ubuntu-20.04
- python-version: "pypy-3.8"
runs-on: ubuntu-20.04
- python-version: "pypy-3.9"
runs-on: ubuntu-20.04
- python-version: "pypy-3.10"
runs-on: ubuntu-22.04
- python-version: "pypy-3.11"
runs-on: ubuntu-22.04
steps:
- name: Install libgnutls28-dev
run: |
sudo apt update -q
sudo apt install -q -y libgnutls28-dev libcurl4-gnutls-dev
- uses: actions/checkout@v3.5.2
- name: Set up Python ${{ matrix.python-version }}
@@ -33,6 +46,12 @@ jobs:
pip install --upgrade pip
pip install codecov tox tox-gh-actions
- name: "Debug OpenSSL version used with Python ${{ matrix.python-version }}"
run: |
which python
python --version
python -c 'import ssl; print(ssl.OPENSSL_VERSION_INFO)'
- name: Run tests with tox
run: tox

View File

@@ -8,6 +8,7 @@ from assertions import assert_cassette_empty, assert_is_json
import vcr
from vcr.patch import force_reset
from vcr.stubs.compat import get_headers
urllib3 = pytest.importorskip("urllib3")
@@ -41,7 +42,8 @@ def test_headers(tmpdir, httpbin_both, verify_pool_mgr):
headers = verify_pool_mgr.request("GET", url).headers
with vcr.use_cassette(str(tmpdir.join("headers.yaml"))):
assert headers == verify_pool_mgr.request("GET", url).headers
new_headers = verify_pool_mgr.request("GET", url).headers
assert sorted(get_headers(headers)) == sorted(get_headers(new_headers))
def test_body(tmpdir, httpbin_both, verify_pool_mgr):
@@ -145,18 +147,18 @@ def test_https_with_cert_validation_disabled(tmpdir, httpbin_secure, pool_mgr):
def test_urllib3_force_reset():
cpool = urllib3.connectionpool
http_original = cpool.HTTPConnection
https_original = cpool.HTTPSConnection
verified_https_original = cpool.VerifiedHTTPSConnection
conn = urllib3.connection
http_original = conn.HTTPConnection
https_original = conn.HTTPSConnection
verified_https_original = conn.VerifiedHTTPSConnection
with vcr.use_cassette(path="test"):
first_cassette_HTTPConnection = cpool.HTTPConnection
first_cassette_HTTPSConnection = cpool.HTTPSConnection
first_cassette_VerifiedHTTPSConnection = cpool.VerifiedHTTPSConnection
first_cassette_HTTPConnection = conn.HTTPConnection
first_cassette_HTTPSConnection = conn.HTTPSConnection
first_cassette_VerifiedHTTPSConnection = conn.VerifiedHTTPSConnection
with force_reset():
assert cpool.HTTPConnection is http_original
assert cpool.HTTPSConnection is https_original
assert cpool.VerifiedHTTPSConnection is verified_https_original
assert cpool.HTTPConnection is first_cassette_HTTPConnection
assert cpool.HTTPSConnection is first_cassette_HTTPSConnection
assert cpool.VerifiedHTTPSConnection is first_cassette_VerifiedHTTPSConnection
assert conn.HTTPConnection is http_original
assert conn.HTTPSConnection is https_original
assert conn.VerifiedHTTPSConnection is verified_https_original
assert conn.HTTPConnection is first_cassette_HTTPConnection
assert conn.HTTPSConnection is first_cassette_HTTPSConnection
assert conn.VerifiedHTTPSConnection is first_cassette_VerifiedHTTPSConnection

View File

@@ -64,9 +64,10 @@ def test_cookies(tmpdir, httpbin):
with vcr.use_cassette(testfile):
s = requests.Session()
s.get(httpbin.url + "/cookies/set?k1=v1&k2=v2")
assert s.cookies.keys() == ["k1", "k2"]
r2 = s.get(httpbin.url + "/cookies")
assert len(r2.json()["cookies"]) == 2
assert sorted(r2.json()["cookies"].keys()) == ["k1", "k2"]
def test_amazon_doctype(tmpdir):

View File

@@ -41,7 +41,7 @@ def test_vcr_use_cassette():
def test_vcr_before_record_request_params():
base_path = "http://httpbin.org/"
base_path = "http://whatever.test/"
def before_record_cb(request):
if request.path != "/get":

13
tox.ini
View File

@@ -3,8 +3,8 @@ skip_missing_interpreters=true
envlist =
cov-clean,
lint,
{py37,py38,py39,py310,py311}-{requests,httplib2,urllib3,tornado4,boto3,aiohttp,httpx},
{pypy3}-{requests,httplib2,urllib3,tornado4,boto3},
{py37,py38,py39,py310,py311}-{requests-urllib3-1,requests-urllib3-2,httplib2,urllib3-1,urllib3-2,tornado4,boto3,aiohttp,httpx},
{pypy3}-{requests-urllib3-1,requests-urllib3-2,httplib2,urllib3-1,urllib3-2,tornado4,boto3},
{py310}-httpx019,
cov-report
@@ -85,11 +85,10 @@ deps =
PyYAML
ipaddress
requests: requests>=2.22.0
requests: urllib3<2
httplib2: httplib2
urllib3: urllib3<2
urllib3-1: urllib3<2
urllib3-2: urllib3<3
boto3: boto3
boto3: urllib3
aiohttp: aiohttp
aiohttp: pytest-asyncio
aiohttp: pytest-aiohttp
@@ -101,8 +100,8 @@ deps =
httpx019: httpx==0.19
{py37,py38,py39,py310}-{httpx}: pytest-asyncio
depends =
lint,{py37,py38,py39,py310,py311,pypy3}-{requests,httplib2,urllib3,tornado4,boto3},{py37,py38,py39,py310,py311}-{aiohttp},{py37,py38,py39,py310,py311}-{httpx}: cov-clean
cov-report: lint,{py37,py38,py39,py310,py311,pypy3}-{requests,httplib2,urllib3,tornado4,boto3},{py37,py38,py39,py310,py311}-{aiohttp}
lint,{py37,py38,py39,py310,py311,pypy3}-{requests-urllib3-1,requests-urllib3-2,httplib2,urllib3-1,urllib3-2,tornado4,boto3},{py37,py38,py39,py310,py311}-{aiohttp},{py37,py38,py39,py310,py311}-{httpx}: cov-clean
cov-report: lint,{py37,py38,py39,py310,py311,pypy3}-{requests-urllib3-1,requests-urllib3-2,httplib2,urllib3-1,urllib3-2,tornado4,boto3},{py37,py38,py39,py310,py311}-{aiohttp}
passenv =
AWS_ACCESS_KEY_ID
AWS_DEFAULT_REGION

View File

@@ -32,15 +32,17 @@ else:
_cpoolBoto3HTTPSConnection = AWSHTTPSConnection
cpool = None
conn = None
# Try to save the original types for urllib3
try:
import urllib3.connection as conn
import urllib3.connectionpool as cpool
except ImportError: # pragma: no cover
pass
else:
_VerifiedHTTPSConnection = cpool.VerifiedHTTPSConnection
_cpoolHTTPConnection = cpool.HTTPConnection
_cpoolHTTPSConnection = cpool.HTTPSConnection
_VerifiedHTTPSConnection = conn.VerifiedHTTPSConnection
_connHTTPConnection = conn.HTTPConnection
_connHTTPSConnection = conn.HTTPSConnection
# Try to save the original types for requests
try:
@@ -198,7 +200,7 @@ class CassettePatcherBuilder:
from .stubs import requests_stubs
except ImportError: # pragma: no cover
return ()
return self._urllib3_patchers(cpool, requests_stubs)
return self._urllib3_patchers(cpool, conn, requests_stubs)
@_build_patchers_from_mock_triples_decorator
def _boto3(self):
@@ -248,12 +250,13 @@ class CassettePatcherBuilder:
def _urllib3(self):
try:
import urllib3.connection as conn
import urllib3.connectionpool as cpool
except ImportError: # pragma: no cover
return ()
from .stubs import urllib3_stubs
return self._urllib3_patchers(cpool, urllib3_stubs)
return self._urllib3_patchers(cpool, conn, urllib3_stubs)
@_build_patchers_from_mock_triples_decorator
def _httplib2(self):
@@ -330,7 +333,7 @@ class CassettePatcherBuilder:
new_sync_client_send = sync_vcr_send(self._cassette, _HttpxSyncClient_send)
yield httpx.Client, "send", new_sync_client_send
def _urllib3_patchers(self, cpool, stubs):
def _urllib3_patchers(self, cpool, conn, stubs):
http_connection_remover = ConnectionRemover(
self._get_cassette_subclass(stubs.VCRRequestsHTTPConnection)
)
@@ -338,9 +341,9 @@ class CassettePatcherBuilder:
self._get_cassette_subclass(stubs.VCRRequestsHTTPSConnection)
)
mock_triples = (
(cpool, "VerifiedHTTPSConnection", stubs.VCRRequestsHTTPSConnection),
(cpool, "HTTPConnection", stubs.VCRRequestsHTTPConnection),
(cpool, "HTTPSConnection", stubs.VCRRequestsHTTPSConnection),
(conn, "VerifiedHTTPSConnection", stubs.VCRRequestsHTTPSConnection),
(conn, "HTTPConnection", stubs.VCRRequestsHTTPConnection),
(conn, "HTTPSConnection", stubs.VCRRequestsHTTPSConnection),
(cpool, "is_connection_dropped", mock.Mock(return_value=False)), # Needed on Windows only
(cpool.HTTPConnectionPool, "ConnectionCls", stubs.VCRRequestsHTTPConnection),
(cpool.HTTPSConnectionPool, "ConnectionCls", stubs.VCRRequestsHTTPSConnection),
@@ -410,16 +413,17 @@ def reset_patchers():
yield mock.patch.object(httplib, "HTTPSConnection", _HTTPSConnection)
try:
import urllib3.connection as conn
import urllib3.connectionpool as cpool
except ImportError: # pragma: no cover
pass
else:
yield mock.patch.object(cpool, "VerifiedHTTPSConnection", _VerifiedHTTPSConnection)
yield mock.patch.object(cpool, "HTTPConnection", _cpoolHTTPConnection)
yield mock.patch.object(cpool, "HTTPSConnection", _cpoolHTTPSConnection)
yield mock.patch.object(conn, "VerifiedHTTPSConnection", _VerifiedHTTPSConnection)
yield mock.patch.object(conn, "HTTPConnection", _connHTTPConnection)
yield mock.patch.object(conn, "HTTPSConnection", _connHTTPSConnection)
if hasattr(cpool.HTTPConnectionPool, "ConnectionCls"):
yield mock.patch.object(cpool.HTTPConnectionPool, "ConnectionCls", _cpoolHTTPConnection)
yield mock.patch.object(cpool.HTTPSConnectionPool, "ConnectionCls", _cpoolHTTPSConnection)
yield mock.patch.object(cpool.HTTPConnectionPool, "ConnectionCls", _connHTTPConnection)
yield mock.patch.object(cpool.HTTPSConnectionPool, "ConnectionCls", _connHTTPSConnection)
try:
# unpatch botocore with awsrequest

View File

@@ -47,8 +47,9 @@ def parse_headers(header_list):
def serialize_headers(response):
headers = response.headers if response.msg is None else response.msg
out = {}
for key, values in compat.get_headers(response.msg):
for key, values in compat.get_headers(headers):
out.setdefault(key, [])
out[key].extend(values)
return out
@@ -67,6 +68,7 @@ class VCRHTTPResponse(HTTPResponse):
self.version = None
self._content = BytesIO(self.recorded_response["body"]["string"])
self._closed = False
self._original_response = self # for requests.session.Session cookie extraction
headers = self.recorded_response["headers"]
# Since we are loading a response that has already been serialized, our
@@ -143,6 +145,28 @@ class VCRHTTPResponse(HTTPResponse):
def readable(self):
return self._content.readable()
@property
def length_remaining(self):
return self._content.getbuffer().nbytes - self._content.tell()
def get_redirect_location(self):
"""
Returns (a) redirect location string if we got a redirect
status code and valid location, (b) None if redirect status and
no location, (c) False if not a redirect status code.
See https://urllib3.readthedocs.io/en/stable/reference/urllib3.response.html .
"""
if not (300 <= self.status <= 399):
return False
return self.getheader("Location")
@property
def data(self):
return self._content.getbuffer().tobytes()
def drain_conn(self):
pass
class VCRConnection:
# A reference to the cassette that's currently being patched in
@@ -248,12 +272,13 @@ class VCRConnection:
# get the response
response = self.real_connection.getresponse()
response_data = response.data if hasattr(response, "data") else response.read()
# put the response into the cassette
response = {
"status": {"code": response.status, "message": response.reason},
"headers": serialize_headers(response),
"body": {"string": response.read()},
"body": {"string": response_data},
}
self.cassette.append(self._vcr_request, response)
return VCRHTTPResponse(response)

View File

@@ -1,6 +1,6 @@
"""Stubs for requests"""
from urllib3.connectionpool import HTTPConnection, VerifiedHTTPSConnection
from urllib3.connection import HTTPConnection, VerifiedHTTPSConnection
from ..stubs import VCRHTTPConnection, VCRHTTPSConnection

View File

@@ -1,6 +1,6 @@
"""Stubs for urllib3"""
from urllib3.connectionpool import HTTPConnection, VerifiedHTTPSConnection
from urllib3.connection import HTTPConnection, VerifiedHTTPSConnection
from ..stubs import VCRHTTPConnection, VCRHTTPSConnection