From c9da7a102fe5b260c0341a5fdf693123f19845a2 Mon Sep 17 00:00:00 2001 From: Harmon Date: Mon, 7 Aug 2023 01:01:11 -0500 Subject: [PATCH 01/35] Configure Read the Docs to install the library --- .readthedocs.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 9712e40..68c4590 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -20,3 +20,5 @@ sphinx: python: install: - requirements: docs/requirements.txt + - method: pip + path: . From d90cea02604d35faf37ed9ba40b780ae28ab24b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Wed, 2 Aug 2023 20:36:05 +0200 Subject: [PATCH 02/35] Enable testing on Python 3.12 --- .github/workflows/main.yml | 2 +- tox.ini | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 75833d4..65eb72e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,7 +13,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.8", "3.9", "3.10", "3.11", "pypy-3.8"] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12-dev", "pypy-3.8"] steps: - uses: actions/checkout@v3.5.2 diff --git a/tox.ini b/tox.ini index 51ad0a4..b209bd9 100644 --- a/tox.ini +++ b/tox.ini @@ -3,8 +3,8 @@ skip_missing_interpreters=true envlist = cov-clean, lint, - {py38,py39,py310,py311}-{requests-urllib3-1,httplib2,urllib3-1,tornado4,boto3,aiohttp,httpx}, - {py310,py311}-{requests-urllib3-2,urllib3-2}, + {py38,py39,py310,py311,py312}-{requests-urllib3-1,httplib2,urllib3-1,tornado4,boto3,aiohttp,httpx}, + {py310,py311,py312}-{requests-urllib3-2,urllib3-2}, {pypy3}-{requests-urllib3-1,httplib2,urllib3-1,tornado4,boto3}, {py310}-httpx019, cov-report @@ -16,6 +16,7 @@ python = 3.9: py39 3.10: py310, lint 3.11: py311 + 3.12: py312 pypy-3: pypy3 # Coverage environment tasks: cov-clean and cov-report @@ -74,8 +75,8 @@ deps = httpx019: httpx==0.19 {py38,py39,py310}-{httpx}: pytest-asyncio depends = - lint,{py38,py39,py310,py311,pypy3}-{requests-urllib3-1,httplib2,urllib3-1,tornado4,boto3},{py310,py311}-{requests-urllib3-2,urllib3-2},{py38,py39,py310,py311}-{aiohttp},{py38,py39,py310,py311}-{httpx}: cov-clean - cov-report: lint,{py38,py39,py310,py311,pypy3}-{requests-urllib3-1,httplib2,urllib3-1,tornado4,boto3},{py310,py311}-{requests-urllib3-2,urllib3-2},{py38,py39,py310,py311}-{aiohttp} + lint,{py38,py39,py310,py311,py312,pypy3}-{requests-urllib3-1,httplib2,urllib3-1,tornado4,boto3},{py310,py311,py312}-{requests-urllib3-2,urllib3-2},{py38,py39,py310,py311,py312}-{aiohttp},{py38,py39,py310,py311,py312}-{httpx}: cov-clean + cov-report: lint,{py38,py39,py310,py311,py312,pypy3}-{requests-urllib3-1,httplib2,urllib3-1,tornado4,boto3},{py310,py311,py312}-{requests-urllib3-2,urllib3-2},{py38,py39,py310,py311,py312}-{aiohttp} passenv = AWS_ACCESS_KEY_ID AWS_DEFAULT_REGION From 469a10b980d48b469f6b431e542fa0fd5e39ca2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Wed, 2 Aug 2023 20:36:35 +0200 Subject: [PATCH 03/35] Enable testing on pypy-3.9 & 3.10 --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 65eb72e..66797bb 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,7 +13,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12-dev", "pypy-3.8"] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12-dev", "pypy-3.8", "pypy-3.9", "pypy-3.10"] steps: - uses: actions/checkout@v3.5.2 From 69621c67fb29dedd9ece4a7bdbf50380fbe4c5ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Thu, 3 Aug 2023 06:51:45 +0200 Subject: [PATCH 04/35] Copy `debuglevel` and `_http_vsn` attrs into response classes Copy the `debuglevel` and `_http_vsn` attributes from base connection class into response classes, in order to fix compatibility with Python 3.12. For reasons I don't comprehend, these end up being called on the class rather than instance, so regular proxying logic does not work. Fixes #707 --- vcr/stubs/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/vcr/stubs/__init__.py b/vcr/stubs/__init__.py index dafaec7..4d4bb39 100644 --- a/vcr/stubs/__init__.py +++ b/vcr/stubs/__init__.py @@ -389,6 +389,8 @@ class VCRHTTPConnection(VCRConnection): _baseclass = HTTPConnection _protocol = "http" + debuglevel = _baseclass.debuglevel + _http_vsn = _baseclass._http_vsn class VCRHTTPSConnection(VCRConnection): @@ -397,3 +399,5 @@ class VCRHTTPSConnection(VCRConnection): _baseclass = HTTPSConnection _protocol = "https" is_verified = True + debuglevel = _baseclass.debuglevel + _http_vsn = _baseclass._http_vsn From defad28771d99434a1fdb3b46994b21b14c36cc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Thu, 3 Aug 2023 13:00:12 +0200 Subject: [PATCH 05/35] Disable C extension in aiohttp to fix Python 3.12 install Disable the C extension in aiohttp that's incompatible with Python 3.12 as of 3.8.5, in order to make it possible to install it (in pure Python version) for testing. --- tox.ini | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tox.ini b/tox.ini index b209bd9..e147690 100644 --- a/tox.ini +++ b/tox.ini @@ -81,3 +81,7 @@ passenv = AWS_ACCESS_KEY_ID AWS_DEFAULT_REGION AWS_SECRET_ACCESS_KEY +setenv = + # workaround for broken C extension in aiohttp + # see: https://github.com/aio-libs/aiohttp/issues/7229 + py312: AIOHTTP_NO_EXTENSIONS=1 From 7bf8f6581579364e6b5e8b9d25455e72dcfbe674 Mon Sep 17 00:00:00 2001 From: Parker Hancock <633163+parkerhancock@users.noreply.github.com> Date: Thu, 7 Dec 2023 21:51:48 -0600 Subject: [PATCH 06/35] fixes for httpx --- tests/integration/test_httpx.py | 157 ++++++++++++++------------------ vcr/patch.py | 12 +-- vcr/stubs/httpx_stubs.py | 29 +----- 3 files changed, 77 insertions(+), 121 deletions(-) diff --git a/tests/integration/test_httpx.py b/tests/integration/test_httpx.py index 4425e02..45415a0 100644 --- a/tests/integration/test_httpx.py +++ b/tests/integration/test_httpx.py @@ -9,6 +9,14 @@ httpx = pytest.importorskip("httpx") from vcr.stubs.httpx_stubs import HTTPX_REDIRECT_PARAM # noqa: E402 +@pytest.fixture(params=["https", "http"]) +def scheme(request): + """Fixture that returns both http and https.""" + return request.param + +@pytest.fixture +def httpbin(scheme): + return scheme + "://httpbin.org" class BaseDoRequest: _client_class = None @@ -16,6 +24,7 @@ class BaseDoRequest: def __init__(self, *args, **kwargs): self._client_args = args self._client_kwargs = kwargs + self._client_kwargs['follow_redirects'] = self._client_kwargs.get('follow_redirects', True) def _make_client(self): return self._client_class(*self._client_args, **self._client_kwargs) @@ -40,7 +49,10 @@ class DoSyncRequest(BaseDoRequest): def __call__(self, *args, **kwargs): return self.client.request(*args, timeout=60, **kwargs) - + + def stream(self, *args, **kwargs): + with self.client.stream(*args, **kwargs) as response: + return b"".join(response.iter_bytes()) class DoAsyncRequest(BaseDoRequest): _client_class = httpx.AsyncClient @@ -75,8 +87,22 @@ class DoAsyncRequest(BaseDoRequest): # Use one-time context and dispose of the loop/client afterwards with self: - return self(*args, **kwargs) + return self._loop.run_until_complete(self.client.request(*args, **kwargs)) + + async def _get_stream(self, *args, **kwargs): + async with self.client.stream(*args, **kwargs) as response: + content = b"" + async for c in response.aiter_bytes(): + content += c + return content + + def stream(self, *args, **kwargs): + if hasattr(self, "_loop"): + return self._loop.run_until_complete(self._get_stream(*args, **kwargs)) + # Use one-time context and dispose of the loop/client afterwards + with self: + return self._loop.run_until_complete(self._get_stream(*args, **kwargs)) def pytest_generate_tests(metafunc): if "do_request" in metafunc.fixturenames: @@ -89,8 +115,8 @@ def yml(tmpdir, request): @pytest.mark.online -def test_status(tmpdir, mockbin, do_request): - url = mockbin +def test_status(tmpdir, httpbin, do_request): + url = httpbin with vcr.use_cassette(str(tmpdir.join("status.yaml"))): response = do_request()("GET", url) @@ -102,8 +128,8 @@ def test_status(tmpdir, mockbin, do_request): @pytest.mark.online -def test_case_insensitive_headers(tmpdir, mockbin, do_request): - url = mockbin +def test_case_insensitive_headers(tmpdir, httpbin, do_request): + url = httpbin with vcr.use_cassette(str(tmpdir.join("whatever.yaml"))): do_request()("GET", url) @@ -116,8 +142,8 @@ def test_case_insensitive_headers(tmpdir, mockbin, do_request): @pytest.mark.online -def test_content(tmpdir, mockbin, do_request): - url = mockbin +def test_content(tmpdir, httpbin, do_request): + url = httpbin with vcr.use_cassette(str(tmpdir.join("cointent.yaml"))): response = do_request()("GET", url) @@ -129,23 +155,21 @@ def test_content(tmpdir, mockbin, do_request): @pytest.mark.online -def test_json(tmpdir, mockbin, do_request): - url = mockbin + "/request" - - headers = {"content-type": "application/json"} +def test_json(tmpdir, httpbin, do_request): + url = httpbin + "/json" with vcr.use_cassette(str(tmpdir.join("json.yaml"))): - response = do_request(headers=headers)("GET", url) + response = do_request()("GET", url) with vcr.use_cassette(str(tmpdir.join("json.yaml"))) as cassette: - cassette_response = do_request(headers=headers)("GET", url) + cassette_response = do_request()("GET", url) assert cassette_response.json() == response.json() assert cassette.play_count == 1 @pytest.mark.online -def test_params_same_url_distinct_params(tmpdir, mockbin, do_request): - url = mockbin + "/request" +def test_params_same_url_distinct_params(tmpdir, httpbin, do_request): + url = httpbin + "/get" headers = {"Content-Type": "application/json"} params = {"a": 1, "b": False, "c": "c"} @@ -165,22 +189,20 @@ def test_params_same_url_distinct_params(tmpdir, mockbin, do_request): @pytest.mark.online -def test_redirect(mockbin, yml, do_request): - url = mockbin + "/redirect/303/2" +def test_redirect(httpbin, yml, do_request): + url = httpbin + "/redirect-to" - redirect_kwargs = {HTTPX_REDIRECT_PARAM.name: True} - - response = do_request()("GET", url, **redirect_kwargs) + response = do_request()("GET", url) with vcr.use_cassette(yml): - response = do_request()("GET", url, **redirect_kwargs) + response = do_request()("GET", url, params={"url": "./get", "status_code": 302}) with vcr.use_cassette(yml) as cassette: - cassette_response = do_request()("GET", url, **redirect_kwargs) + cassette_response = do_request()("GET", url, params={"url": "./get", "status_code": 302}) assert cassette_response.status_code == response.status_code assert len(cassette_response.history) == len(response.history) - assert len(cassette) == 3 - assert cassette.play_count == 3 + assert len(cassette) == 2 + assert cassette.play_count == 2 # Assert that the real response and the cassette response have a similar # looking request_info. @@ -190,8 +212,8 @@ def test_redirect(mockbin, yml, do_request): @pytest.mark.online -def test_work_with_gzipped_data(mockbin, do_request, yml): - url = mockbin + "/gzip?foo=bar" +def test_work_with_gzipped_data(httpbin, do_request, yml): + url = httpbin + "/gzip?foo=bar" headers = {"accept-encoding": "deflate, gzip"} with vcr.use_cassette(yml): @@ -216,56 +238,32 @@ def test_simple_fetching(do_request, yml, url): assert str(cassette_response.request.url) == url assert cassette.play_count == 1 - -def test_behind_proxy(do_request): - # This is recorded because otherwise we should have a live proxy somewhere. - yml = ( - os.path.dirname(os.path.realpath(__file__)) + "/cassettes/" + "test_httpx_test_test_behind_proxy.yml" - ) - url = "https://mockbin.org/headers" - proxy = "http://localhost:8080" - proxies = {"http://": proxy, "https://": proxy} - - with vcr.use_cassette(yml): - response = do_request(proxies=proxies, verify=False)("GET", url) - - with vcr.use_cassette(yml) as cassette: - cassette_response = do_request(proxies=proxies, verify=False)("GET", url) - assert str(cassette_response.request.url) == url - assert cassette.play_count == 1 - - assert cassette_response.headers["Via"] == "my_own_proxy", str(cassette_response.headers) - assert cassette_response.request.url == response.request.url - - @pytest.mark.online -def test_cookies(tmpdir, mockbin, do_request): +def test_cookies(tmpdir, httpbin, do_request): def client_cookies(client): return list(client.client.cookies) def response_cookies(response): return list(response.cookies) - url = mockbin + "/bin/26148652-fe25-4f21-aaf5-689b5b4bf65f" - headers = {"cookie": "k1=v1;k2=v2"} + url = httpbin + "/cookies/set" + params = {"k1": "v1", "k2": "v2"} - with do_request(headers=headers) as client: + with do_request(params=params, follow_redirects=False) as client: assert client_cookies(client) == [] - redirect_kwargs = {HTTPX_REDIRECT_PARAM.name: True} - testfile = str(tmpdir.join("cookies.yml")) with vcr.use_cassette(testfile): - r1 = client("GET", url, **redirect_kwargs) + r1 = client("GET", url) assert response_cookies(r1) == ["k1", "k2"] - r2 = client("GET", url, **redirect_kwargs) + r2 = client("GET", url) assert response_cookies(r2) == ["k1", "k2"] assert client_cookies(client) == ["k1", "k2"] - with do_request(headers=headers) as new_client: + with do_request(params=params, follow_redirects=False) as new_client: assert client_cookies(new_client) == [] with vcr.use_cassette(testfile) as cassette: @@ -275,42 +273,19 @@ def test_cookies(tmpdir, mockbin, do_request): assert response_cookies(cassette_response) == ["k1", "k2"] assert client_cookies(new_client) == ["k1", "k2"] - @pytest.mark.online -def test_relative_redirects(tmpdir, scheme, do_request, mockbin): - redirect_kwargs = {HTTPX_REDIRECT_PARAM.name: True} +def test_stream(tmpdir, httpbin, do_request): + url = httpbin + "/stream-bytes/512" + testfile = str(tmpdir.join("stream.yml")) - url = mockbin + "/redirect/301?to=/redirect/301?to=/request" - testfile = str(tmpdir.join("relative_redirects.yml")) with vcr.use_cassette(testfile): - response = do_request()("GET", url, **redirect_kwargs) - assert len(response.history) == 2, response - assert response.json()["url"].endswith("request") + response_content = do_request().stream("GET", url) + assert len(response_content) == 512 + with vcr.use_cassette(testfile) as cassette: - response = do_request()("GET", url, **redirect_kwargs) - assert len(response.history) == 2 - assert response.json()["url"].endswith("request") - - assert cassette.play_count == 3 - - -@pytest.mark.online -def test_redirect_wo_allow_redirects(do_request, mockbin, yml): - url = mockbin + "/redirect/308/5" - - redirect_kwargs = {HTTPX_REDIRECT_PARAM.name: False} - - with vcr.use_cassette(yml): - response = do_request()("GET", url, **redirect_kwargs) - - assert str(response.url).endswith("308/5") - assert response.status_code == 308 - - with vcr.use_cassette(yml) as cassette: - response = do_request()("GET", url, **redirect_kwargs) - - assert str(response.url).endswith("308/5") - assert response.status_code == 308 - + cassette_content = do_request().stream("GET", url) + assert cassette_content == response_content + assert len(cassette_content) == 512 assert cassette.play_count == 1 + \ No newline at end of file diff --git a/vcr/patch.py b/vcr/patch.py index afcaab5..f69ae76 100644 --- a/vcr/patch.py +++ b/vcr/patch.py @@ -95,8 +95,8 @@ try: except ImportError: # pragma: no cover pass else: - _HttpxSyncClient_send = httpx.Client.send - _HttpxAsyncClient_send = httpx.AsyncClient.send + _HttpxSyncClient_send_single_request = httpx.Client._send_single_request + _HttpxAsyncClient_send_single_request = httpx.AsyncClient._send_single_request class CassettePatcherBuilder: @@ -307,11 +307,11 @@ class CassettePatcherBuilder: else: from .stubs.httpx_stubs import async_vcr_send, sync_vcr_send - new_async_client_send = async_vcr_send(self._cassette, _HttpxAsyncClient_send) - yield httpx.AsyncClient, "send", new_async_client_send + new_async_client_send = async_vcr_send(self._cassette, _HttpxAsyncClient_send_single_request) + yield httpx.AsyncClient, "_send_single_request", new_async_client_send - new_sync_client_send = sync_vcr_send(self._cassette, _HttpxSyncClient_send) - yield httpx.Client, "send", new_sync_client_send + new_sync_client_send = sync_vcr_send(self._cassette, _HttpxSyncClient_send_single_request) + yield httpx.Client, "_send_single_request", new_sync_client_send def _urllib3_patchers(self, cpool, conn, stubs): http_connection_remover = ConnectionRemover( diff --git a/vcr/stubs/httpx_stubs.py b/vcr/stubs/httpx_stubs.py index 515855e..453d329 100644 --- a/vcr/stubs/httpx_stubs.py +++ b/vcr/stubs/httpx_stubs.py @@ -38,7 +38,7 @@ def _to_serialized_response(httpx_response): "status_code": httpx_response.status_code, "http_version": httpx_response.http_version, "headers": _transform_headers(httpx_response), - "content": httpx_response.content.decode("utf-8", "ignore"), + "content": httpx_response.content#.decode("utf-8", "ignore"), } @@ -57,7 +57,7 @@ def _from_serialized_headers(headers): @patch("httpx.Response.close", MagicMock()) @patch("httpx.Response.read", MagicMock()) def _from_serialized_response(request, serialized_response, history=None): - content = serialized_response.get("content").encode() + content = serialized_response.get("content") response = httpx.Response( status_code=serialized_response.get("status_code"), request=request, @@ -106,33 +106,12 @@ def _record_responses(cassette, vcr_request, real_response): def _play_responses(cassette, request, vcr_request, client, kwargs): - history = [] - - allow_redirects = kwargs.get( - HTTPX_REDIRECT_PARAM.name, - HTTPX_REDIRECT_PARAM.default, - ) vcr_response = cassette.play_response(vcr_request) response = _from_serialized_response(request, vcr_response) - - while allow_redirects and 300 <= response.status_code <= 399: - next_url = response.headers.get("location") - if not next_url: - break - - vcr_request = VcrRequest("GET", next_url, None, dict(response.headers)) - vcr_request = cassette.find_requests_with_most_matches(vcr_request)[0][0] - - history.append(response) - # add cookies from response to session cookie store - client.cookies.extract_cookies(response) - - vcr_response = cassette.play_response(vcr_request) - response = _from_serialized_response(vcr_request, vcr_response, history) - return response + async def _async_vcr_send(cassette, real_send, *args, **kwargs): vcr_request, response = _shared_vcr_send(cassette, real_send, *args, **kwargs) if response: @@ -141,6 +120,7 @@ async def _async_vcr_send(cassette, real_send, *args, **kwargs): return response real_response = await real_send(*args, **kwargs) + await real_response.aread() return _record_responses(cassette, vcr_request, real_response) @@ -160,6 +140,7 @@ def _sync_vcr_send(cassette, real_send, *args, **kwargs): return response real_response = real_send(*args, **kwargs) + real_response.read() return _record_responses(cassette, vcr_request, real_response) From e8e9a4af9fb3917e58c3a6ee7fcf6c4dfb70dca1 Mon Sep 17 00:00:00 2001 From: Parker Hancock <633163+parkerhancock@users.noreply.github.com> Date: Fri, 8 Dec 2023 09:30:42 -0600 Subject: [PATCH 07/35] remove unnecssary comment --- vcr/stubs/httpx_stubs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vcr/stubs/httpx_stubs.py b/vcr/stubs/httpx_stubs.py index 453d329..0bb3b4a 100644 --- a/vcr/stubs/httpx_stubs.py +++ b/vcr/stubs/httpx_stubs.py @@ -38,7 +38,7 @@ def _to_serialized_response(httpx_response): "status_code": httpx_response.status_code, "http_version": httpx_response.http_version, "headers": _transform_headers(httpx_response), - "content": httpx_response.content#.decode("utf-8", "ignore"), + "content": httpx_response.content, } From f5fc7aac226d9db8520ff47cf657db326e056306 Mon Sep 17 00:00:00 2001 From: Parker Hancock <633163+parkerhancock@users.noreply.github.com> Date: Fri, 8 Dec 2023 10:49:29 -0600 Subject: [PATCH 08/35] fix tests --- tests/integration/conftest.py | 16 ----- tests/integration/test_aiohttp.py | 66 ++++++++++---------- tests/integration/test_basic.py | 2 +- tests/integration/test_config.py | 24 +++---- tests/integration/test_disksaver.py | 14 ++--- tests/integration/test_httplib2.py | 6 +- tests/integration/test_httpx.py | 36 +++++------ tests/integration/test_proxy.py | 13 ++-- tests/integration/test_register_matcher.py | 16 ++--- tests/integration/test_register_persister.py | 2 +- tests/integration/test_tornado.py | 6 +- tests/integration/test_urllib2.py | 6 +- tests/integration/test_urllib3.py | 4 +- tox.ini | 2 +- 14 files changed, 101 insertions(+), 112 deletions(-) diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index a01f3c3..05908e5 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -4,22 +4,6 @@ import ssl import pytest -@pytest.fixture(params=["https", "http"]) -def scheme(request): - """Fixture that returns both http and https.""" - return request.param - - -@pytest.fixture -def mockbin(scheme): - return scheme + "://mockbin.org" - - -@pytest.fixture -def mockbin_request_url(mockbin): - return mockbin + "/request" - - @pytest.fixture def httpbin_ssl_context(): ssl_ca_location = os.environ["REQUESTS_CA_BUNDLE"] diff --git a/tests/integration/test_aiohttp.py b/tests/integration/test_aiohttp.py index 49cf993..a740ef2 100644 --- a/tests/integration/test_aiohttp.py +++ b/tests/integration/test_aiohttp.py @@ -36,8 +36,8 @@ def post(url, output="text", **kwargs): @pytest.mark.online -def test_status(tmpdir, mockbin_request_url): - url = mockbin_request_url +def test_status(tmpdir, httpbin): + url = httpbin.url with vcr.use_cassette(str(tmpdir.join("status.yaml"))): response, _ = get(url) @@ -50,8 +50,8 @@ def test_status(tmpdir, mockbin_request_url): @pytest.mark.online @pytest.mark.parametrize("auth", [None, aiohttp.BasicAuth("vcrpy", "test")]) -def test_headers(tmpdir, auth, mockbin_request_url): - url = mockbin_request_url +def test_headers(tmpdir, auth, httpbin): + url = httpbin.url with vcr.use_cassette(str(tmpdir.join("headers.yaml"))): response, _ = get(url, auth=auth) @@ -67,8 +67,8 @@ def test_headers(tmpdir, auth, mockbin_request_url): @pytest.mark.online -def test_case_insensitive_headers(tmpdir, mockbin_request_url): - url = mockbin_request_url +def test_case_insensitive_headers(tmpdir, httpbin): + url = httpbin.url with vcr.use_cassette(str(tmpdir.join("whatever.yaml"))): _, _ = get(url) @@ -81,8 +81,8 @@ def test_case_insensitive_headers(tmpdir, mockbin_request_url): @pytest.mark.online -def test_text(tmpdir, mockbin_request_url): - url = mockbin_request_url +def test_text(tmpdir, httpbin): + url = httpbin.url with vcr.use_cassette(str(tmpdir.join("text.yaml"))): _, response_text = get(url) @@ -94,8 +94,8 @@ def test_text(tmpdir, mockbin_request_url): @pytest.mark.online -def test_json(tmpdir, mockbin_request_url): - url = mockbin_request_url +def test_json(tmpdir, httpbin): + url = httpbin.url + "/json" headers = {"Content-Type": "application/json"} with vcr.use_cassette(str(tmpdir.join("json.yaml"))): @@ -108,8 +108,8 @@ def test_json(tmpdir, mockbin_request_url): @pytest.mark.online -def test_binary(tmpdir, mockbin_request_url): - url = mockbin_request_url + "/image/png" +def test_binary(tmpdir, httpbin): + url = httpbin.url + "/image/png" with vcr.use_cassette(str(tmpdir.join("binary.yaml"))): _, response_binary = get(url, output="raw") @@ -120,8 +120,8 @@ def test_binary(tmpdir, mockbin_request_url): @pytest.mark.online -def test_stream(tmpdir, mockbin_request_url): - url = mockbin_request_url +def test_stream(tmpdir, httpbin): + url = httpbin.url with vcr.use_cassette(str(tmpdir.join("stream.yaml"))): _, body = get(url, output="raw") # Do not use stream here, as the stream is exhausted by vcr @@ -134,10 +134,10 @@ def test_stream(tmpdir, mockbin_request_url): @pytest.mark.online @pytest.mark.parametrize("body", ["data", "json"]) -def test_post(tmpdir, body, caplog, mockbin_request_url): +def test_post(tmpdir, body, caplog, httpbin): caplog.set_level(logging.INFO) data = {"key1": "value1", "key2": "value2"} - url = mockbin_request_url + url = httpbin.url with vcr.use_cassette(str(tmpdir.join("post.yaml"))): _, response_json = post(url, **{body: data}) @@ -159,14 +159,14 @@ def test_post(tmpdir, body, caplog, mockbin_request_url): @pytest.mark.online -def test_params(tmpdir, mockbin_request_url): - url = mockbin_request_url + "?d=d" +def test_params(tmpdir, httpbin): + url = httpbin.url + "/get?d=d" headers = {"Content-Type": "application/json"} params = {"a": 1, "b": 2, "c": "c"} with vcr.use_cassette(str(tmpdir.join("get.yaml"))) as cassette: _, response_json = get(url, output="json", params=params, headers=headers) - assert response_json["queryString"] == {"a": "1", "b": "2", "c": "c", "d": "d"} + assert response_json["args"] == {"a": "1", "b": "2", "c": "c", "d": "d"} with vcr.use_cassette(str(tmpdir.join("get.yaml"))) as cassette: _, cassette_response_json = get(url, output="json", params=params, headers=headers) @@ -175,8 +175,8 @@ def test_params(tmpdir, mockbin_request_url): @pytest.mark.online -def test_params_same_url_distinct_params(tmpdir, mockbin_request_url): - url = mockbin_request_url +def test_params_same_url_distinct_params(tmpdir, httpbin): + url = httpbin.url + "/json" headers = {"Content-Type": "application/json"} params = {"a": 1, "b": 2, "c": "c"} @@ -195,8 +195,8 @@ def test_params_same_url_distinct_params(tmpdir, mockbin_request_url): @pytest.mark.online -def test_params_on_url(tmpdir, mockbin_request_url): - url = mockbin_request_url + "?a=1&b=foo" +def test_params_on_url(tmpdir, httpbin): + url = httpbin.url + "/get?a=1&b=foo" headers = {"Content-Type": "application/json"} with vcr.use_cassette(str(tmpdir.join("get.yaml"))) as cassette: @@ -261,8 +261,8 @@ def test_aiohttp_test_client_json(aiohttp_client, tmpdir): @pytest.mark.online -def test_redirect(tmpdir, mockbin): - url = mockbin + "/redirect/302/2" +def test_redirect(tmpdir, httpbin): + url = httpbin.url + "/redirect/2" with vcr.use_cassette(str(tmpdir.join("redirect.yaml"))): response, _ = get(url) @@ -284,9 +284,9 @@ def test_redirect(tmpdir, mockbin): @pytest.mark.online -def test_not_modified(tmpdir, mockbin): +def test_not_modified(tmpdir, httpbin): """It doesn't try to redirect on 304""" - url = mockbin + "/status/304" + url = httpbin.url + "/status/304" with vcr.use_cassette(str(tmpdir.join("not_modified.yaml"))): response, _ = get(url) @@ -302,13 +302,13 @@ def test_not_modified(tmpdir, mockbin): @pytest.mark.online -def test_double_requests(tmpdir, mockbin_request_url): +def test_double_requests(tmpdir, httpbin): """We should capture, record, and replay all requests and response chains, even if there are duplicate ones. We should replay in the order we saw them. """ - url = mockbin_request_url + url = httpbin.url with vcr.use_cassette(str(tmpdir.join("text.yaml"))): _, response_text1 = get(url, output="text") @@ -426,18 +426,18 @@ def test_cookies_redirect(httpbin_both, httpbin_ssl_context, tmpdir): @pytest.mark.online -def test_not_allow_redirects(tmpdir, mockbin): - url = mockbin + "/redirect/308/5" +def test_not_allow_redirects(tmpdir, httpbin): + url = httpbin + "/redirect-to?url=.%2F&status_code=308" path = str(tmpdir.join("redirects.yaml")) with vcr.use_cassette(path): response, _ = get(url, allow_redirects=False) - assert response.url.path == "/redirect/308/5" + assert response.url.path == "/redirect-to" assert response.status == 308 with vcr.use_cassette(path) as cassette: response, _ = get(url, allow_redirects=False) - assert response.url.path == "/redirect/308/5" + assert response.url.path == "/redirect-to" assert response.status == 308 assert cassette.play_count == 1 diff --git a/tests/integration/test_basic.py b/tests/integration/test_basic.py index fba9fe2..bfad615 100644 --- a/tests/integration/test_basic.py +++ b/tests/integration/test_basic.py @@ -39,7 +39,7 @@ def test_basic_json_use(tmpdir, httpbin): test_fixture = str(tmpdir.join("synopsis.json")) with vcr.use_cassette(test_fixture, serializer="json"): response = urlopen(httpbin.url).read() - assert b"difficult sometimes" in response + assert b"A simple HTTP Request & Response Service." in response def test_patched_content(tmpdir, httpbin): diff --git a/tests/integration/test_config.py b/tests/integration/test_config.py index 274dbd0..6be1043 100644 --- a/tests/integration/test_config.py +++ b/tests/integration/test_config.py @@ -8,12 +8,12 @@ import vcr @pytest.mark.online -def test_set_serializer_default_config(tmpdir, mockbin_request_url): +def test_set_serializer_default_config(tmpdir, httpbin): my_vcr = vcr.VCR(serializer="json") with my_vcr.use_cassette(str(tmpdir.join("test.json"))): assert my_vcr.serializer == "json" - urlopen(mockbin_request_url) + urlopen(httpbin.url) with open(str(tmpdir.join("test.json"))) as f: file_content = f.read() @@ -22,37 +22,37 @@ def test_set_serializer_default_config(tmpdir, mockbin_request_url): @pytest.mark.online -def test_default_set_cassette_library_dir(tmpdir, mockbin_request_url): +def test_default_set_cassette_library_dir(tmpdir, httpbin): my_vcr = vcr.VCR(cassette_library_dir=str(tmpdir.join("subdir"))) with my_vcr.use_cassette("test.json"): - urlopen(mockbin_request_url) + urlopen(httpbin.url) assert os.path.exists(str(tmpdir.join("subdir").join("test.json"))) @pytest.mark.online -def test_override_set_cassette_library_dir(tmpdir, mockbin_request_url): +def test_override_set_cassette_library_dir(tmpdir, httpbin): my_vcr = vcr.VCR(cassette_library_dir=str(tmpdir.join("subdir"))) cld = str(tmpdir.join("subdir2")) with my_vcr.use_cassette("test.json", cassette_library_dir=cld): - urlopen(mockbin_request_url) + urlopen(httpbin.url) assert os.path.exists(str(tmpdir.join("subdir2").join("test.json"))) assert not os.path.exists(str(tmpdir.join("subdir").join("test.json"))) @pytest.mark.online -def test_override_match_on(tmpdir, mockbin_request_url): +def test_override_match_on(tmpdir, httpbin): my_vcr = vcr.VCR(match_on=["method"]) with my_vcr.use_cassette(str(tmpdir.join("test.json"))): - urlopen(mockbin_request_url) + urlopen(httpbin.url) with my_vcr.use_cassette(str(tmpdir.join("test.json"))) as cass: - urlopen(mockbin_request_url) + urlopen(httpbin.url) assert len(cass) == 1 assert cass.play_count == 1 @@ -67,12 +67,12 @@ def test_missing_matcher(): @pytest.mark.online -def test_dont_record_on_exception(tmpdir, mockbin_request_url): +def test_dont_record_on_exception(tmpdir, httpbin): my_vcr = vcr.VCR(record_on_exception=False) @my_vcr.use_cassette(str(tmpdir.join("dontsave.yml"))) def some_test(): - assert b"Not in content" in urlopen(mockbin_request_url) + assert b"Not in content" in urlopen(httpbin.url) with pytest.raises(AssertionError): some_test() @@ -82,6 +82,6 @@ def test_dont_record_on_exception(tmpdir, mockbin_request_url): # Make sure context decorator has the same behavior with pytest.raises(AssertionError): with my_vcr.use_cassette(str(tmpdir.join("dontsave2.yml"))): - assert b"Not in content" in urlopen(mockbin_request_url).read() + assert b"Not in content" in urlopen(httpbin.url).read() assert not os.path.exists(str(tmpdir.join("dontsave2.yml"))) diff --git a/tests/integration/test_disksaver.py b/tests/integration/test_disksaver.py index f9127d9..985762e 100644 --- a/tests/integration/test_disksaver.py +++ b/tests/integration/test_disksaver.py @@ -12,19 +12,19 @@ import vcr @pytest.mark.online -def test_disk_saver_nowrite(tmpdir, mockbin_request_url): +def test_disk_saver_nowrite(tmpdir, httpbin): """ Ensure that when you close a cassette without changing it it doesn't rewrite the file """ fname = str(tmpdir.join("synopsis.yaml")) with vcr.use_cassette(fname) as cass: - urlopen(mockbin_request_url).read() + urlopen(httpbin.url).read() assert cass.play_count == 0 last_mod = os.path.getmtime(fname) with vcr.use_cassette(fname) as cass: - urlopen(mockbin_request_url).read() + urlopen(httpbin.url).read() assert cass.play_count == 1 assert cass.dirty is False last_mod2 = os.path.getmtime(fname) @@ -33,14 +33,14 @@ def test_disk_saver_nowrite(tmpdir, mockbin_request_url): @pytest.mark.online -def test_disk_saver_write(tmpdir, mockbin_request_url): +def test_disk_saver_write(tmpdir, httpbin): """ Ensure that when you close a cassette after changing it it does rewrite the file """ fname = str(tmpdir.join("synopsis.yaml")) with vcr.use_cassette(fname) as cass: - urlopen(mockbin_request_url).read() + urlopen(httpbin.url).read() assert cass.play_count == 0 last_mod = os.path.getmtime(fname) @@ -49,8 +49,8 @@ def test_disk_saver_write(tmpdir, mockbin_request_url): time.sleep(1) with vcr.use_cassette(fname, record_mode=vcr.mode.ANY) as cass: - urlopen(mockbin_request_url).read() - urlopen(mockbin_request_url + "/get").read() + urlopen(httpbin.url).read() + urlopen(httpbin.url + "/get").read() assert cass.play_count == 1 assert cass.dirty last_mod2 = os.path.getmtime(fname) diff --git a/tests/integration/test_httplib2.py b/tests/integration/test_httplib2.py index 2cafb39..7fecd72 100644 --- a/tests/integration/test_httplib2.py +++ b/tests/integration/test_httplib2.py @@ -56,14 +56,14 @@ def test_response_headers(tmpdir, httpbin_both): @pytest.mark.online -def test_effective_url(tmpdir): +def test_effective_url(tmpdir, httpbin): """Ensure that the effective_url is captured""" - url = "http://mockbin.org/redirect/301" + url = httpbin.url + "/redirect-to?url=.%2F&status_code=301" with vcr.use_cassette(str(tmpdir.join("headers.yaml"))): resp, _ = http().request(url) effective_url = resp["content-location"] - assert effective_url == "http://mockbin.org/redirect/301/0" + assert effective_url == httpbin.url + "/" with vcr.use_cassette(str(tmpdir.join("headers.yaml"))): resp, _ = http().request(url) diff --git a/tests/integration/test_httpx.py b/tests/integration/test_httpx.py index 45415a0..cf1f6c2 100644 --- a/tests/integration/test_httpx.py +++ b/tests/integration/test_httpx.py @@ -9,14 +9,12 @@ httpx = pytest.importorskip("httpx") from vcr.stubs.httpx_stubs import HTTPX_REDIRECT_PARAM # noqa: E402 + @pytest.fixture(params=["https", "http"]) def scheme(request): """Fixture that returns both http and https.""" return request.param -@pytest.fixture -def httpbin(scheme): - return scheme + "://httpbin.org" class BaseDoRequest: _client_class = None @@ -24,7 +22,7 @@ class BaseDoRequest: def __init__(self, *args, **kwargs): self._client_args = args self._client_kwargs = kwargs - self._client_kwargs['follow_redirects'] = self._client_kwargs.get('follow_redirects', True) + self._client_kwargs["follow_redirects"] = self._client_kwargs.get("follow_redirects", True) def _make_client(self): return self._client_class(*self._client_args, **self._client_kwargs) @@ -49,11 +47,12 @@ class DoSyncRequest(BaseDoRequest): def __call__(self, *args, **kwargs): return self.client.request(*args, timeout=60, **kwargs) - + def stream(self, *args, **kwargs): with self.client.stream(*args, **kwargs) as response: return b"".join(response.iter_bytes()) + class DoAsyncRequest(BaseDoRequest): _client_class = httpx.AsyncClient @@ -88,14 +87,14 @@ class DoAsyncRequest(BaseDoRequest): # Use one-time context and dispose of the loop/client afterwards with self: return self._loop.run_until_complete(self.client.request(*args, **kwargs)) - + async def _get_stream(self, *args, **kwargs): async with self.client.stream(*args, **kwargs) as response: content = b"" async for c in response.aiter_bytes(): content += c return content - + def stream(self, *args, **kwargs): if hasattr(self, "_loop"): return self._loop.run_until_complete(self._get_stream(*args, **kwargs)) @@ -104,6 +103,7 @@ class DoAsyncRequest(BaseDoRequest): with self: return self._loop.run_until_complete(self._get_stream(*args, **kwargs)) + def pytest_generate_tests(metafunc): if "do_request" in metafunc.fixturenames: metafunc.parametrize("do_request", [DoAsyncRequest, DoSyncRequest]) @@ -116,7 +116,7 @@ def yml(tmpdir, request): @pytest.mark.online def test_status(tmpdir, httpbin, do_request): - url = httpbin + url = httpbin.url with vcr.use_cassette(str(tmpdir.join("status.yaml"))): response = do_request()("GET", url) @@ -129,7 +129,7 @@ def test_status(tmpdir, httpbin, do_request): @pytest.mark.online def test_case_insensitive_headers(tmpdir, httpbin, do_request): - url = httpbin + url = httpbin.url with vcr.use_cassette(str(tmpdir.join("whatever.yaml"))): do_request()("GET", url) @@ -143,7 +143,7 @@ def test_case_insensitive_headers(tmpdir, httpbin, do_request): @pytest.mark.online def test_content(tmpdir, httpbin, do_request): - url = httpbin + url = httpbin.url with vcr.use_cassette(str(tmpdir.join("cointent.yaml"))): response = do_request()("GET", url) @@ -156,7 +156,7 @@ def test_content(tmpdir, httpbin, do_request): @pytest.mark.online def test_json(tmpdir, httpbin, do_request): - url = httpbin + "/json" + url = httpbin.url + "/json" with vcr.use_cassette(str(tmpdir.join("json.yaml"))): response = do_request()("GET", url) @@ -169,7 +169,7 @@ def test_json(tmpdir, httpbin, do_request): @pytest.mark.online def test_params_same_url_distinct_params(tmpdir, httpbin, do_request): - url = httpbin + "/get" + url = httpbin.url + "/get" headers = {"Content-Type": "application/json"} params = {"a": 1, "b": False, "c": "c"} @@ -190,7 +190,7 @@ def test_params_same_url_distinct_params(tmpdir, httpbin, do_request): @pytest.mark.online def test_redirect(httpbin, yml, do_request): - url = httpbin + "/redirect-to" + url = httpbin.url + "/redirect-to" response = do_request()("GET", url) with vcr.use_cassette(yml): @@ -213,7 +213,7 @@ def test_redirect(httpbin, yml, do_request): @pytest.mark.online def test_work_with_gzipped_data(httpbin, do_request, yml): - url = httpbin + "/gzip?foo=bar" + url = httpbin.url + "/gzip?foo=bar" headers = {"accept-encoding": "deflate, gzip"} with vcr.use_cassette(yml): @@ -238,6 +238,7 @@ def test_simple_fetching(do_request, yml, url): assert str(cassette_response.request.url) == url assert cassette.play_count == 1 + @pytest.mark.online def test_cookies(tmpdir, httpbin, do_request): def client_cookies(client): @@ -246,7 +247,7 @@ def test_cookies(tmpdir, httpbin, do_request): def response_cookies(response): return list(response.cookies) - url = httpbin + "/cookies/set" + url = httpbin.url + "/cookies/set" params = {"k1": "v1", "k2": "v2"} with do_request(params=params, follow_redirects=False) as client: @@ -273,19 +274,18 @@ def test_cookies(tmpdir, httpbin, do_request): assert response_cookies(cassette_response) == ["k1", "k2"] assert client_cookies(new_client) == ["k1", "k2"] + @pytest.mark.online def test_stream(tmpdir, httpbin, do_request): - url = httpbin + "/stream-bytes/512" + url = httpbin.url + "/stream-bytes/512" testfile = str(tmpdir.join("stream.yml")) with vcr.use_cassette(testfile): response_content = do_request().stream("GET", url) assert len(response_content) == 512 - with vcr.use_cassette(testfile) as cassette: cassette_content = do_request().stream("GET", url) assert cassette_content == response_content assert len(cassette_content) == 512 assert cassette.play_count == 1 - \ No newline at end of file diff --git a/tests/integration/test_proxy.py b/tests/integration/test_proxy.py index e437643..c450ab2 100644 --- a/tests/integration/test_proxy.py +++ b/tests/integration/test_proxy.py @@ -2,6 +2,7 @@ import http.server import multiprocessing +import threading import socketserver from urllib.request import urlopen @@ -29,7 +30,8 @@ class Proxy(http.server.SimpleHTTPRequestHandler): # In Python 2 the response is an addinfourl instance. status = upstream_response.code headers = upstream_response.info().items() - self.send_response(status, upstream_response.msg) + self.log_request(status) + self.send_response_only(status, upstream_response.msg) for header in headers: self.send_header(*header) self.end_headers() @@ -39,10 +41,11 @@ class Proxy(http.server.SimpleHTTPRequestHandler): @pytest.fixture(scope="session") def proxy_server(): httpd = socketserver.ThreadingTCPServer(("", 0), Proxy) - proxy_process = multiprocessing.Process(target=httpd.serve_forever) + proxy_process = threading.Thread(target=httpd.serve_forever) proxy_process.start() yield "http://{}:{}".format(*httpd.server_address) - proxy_process.terminate() + httpd.shutdown() + proxy_process.join() def test_use_proxy(tmpdir, httpbin, proxy_server): @@ -50,8 +53,10 @@ def test_use_proxy(tmpdir, httpbin, proxy_server): with vcr.use_cassette(str(tmpdir.join("proxy.yaml"))): response = requests.get(httpbin.url, proxies={"http": proxy_server}) - with vcr.use_cassette(str(tmpdir.join("proxy.yaml"))) as cassette: + with vcr.use_cassette(str(tmpdir.join("proxy.yaml")), mode="once") as cassette: cassette_response = requests.get(httpbin.url, proxies={"http": proxy_server}) + for key in set(cassette_response.headers.keys()) & set(response.headers.keys()): + assert cassette_response.headers[key] == response.headers[key] assert cassette_response.headers == response.headers assert cassette.play_count == 1 diff --git a/tests/integration/test_register_matcher.py b/tests/integration/test_register_matcher.py index b6e2098..9b9b5ad 100644 --- a/tests/integration/test_register_matcher.py +++ b/tests/integration/test_register_matcher.py @@ -14,28 +14,28 @@ def false_matcher(r1, r2): @pytest.mark.online -def test_registered_true_matcher(tmpdir, mockbin_request_url): +def test_registered_true_matcher(tmpdir, httpbin): my_vcr = vcr.VCR() my_vcr.register_matcher("true", true_matcher) testfile = str(tmpdir.join("test.yml")) with my_vcr.use_cassette(testfile, match_on=["true"]): # These 2 different urls are stored as the same request - urlopen(mockbin_request_url) - urlopen(mockbin_request_url + "/get") + urlopen(httpbin.url) + urlopen(httpbin.url + "/get") with my_vcr.use_cassette(testfile, match_on=["true"]): # I can get the response twice even though I only asked for it once - urlopen(mockbin_request_url) - urlopen(mockbin_request_url) + urlopen(httpbin.url) + urlopen(httpbin.url) @pytest.mark.online -def test_registered_false_matcher(tmpdir, mockbin_request_url): +def test_registered_false_matcher(tmpdir, httpbin): my_vcr = vcr.VCR() my_vcr.register_matcher("false", false_matcher) testfile = str(tmpdir.join("test.yml")) with my_vcr.use_cassette(testfile, match_on=["false"]) as cass: # These 2 different urls are stored as different requests - urlopen(mockbin_request_url) - urlopen(mockbin_request_url + "/get") + urlopen(httpbin.url) + urlopen(httpbin.url + "/get") assert len(cass) == 2 diff --git a/tests/integration/test_register_persister.py b/tests/integration/test_register_persister.py index 31d54db..e904197 100644 --- a/tests/integration/test_register_persister.py +++ b/tests/integration/test_register_persister.py @@ -66,7 +66,7 @@ def test_load_cassette_with_custom_persister(tmpdir, httpbin): with my_vcr.use_cassette(test_fixture, serializer="json"): response = urlopen(httpbin.url).read() - assert b"difficult sometimes" in response + assert b"A simple HTTP Request & Response Service." in response def test_load_cassette_persister_exception_handling(tmpdir, httpbin): diff --git a/tests/integration/test_tornado.py b/tests/integration/test_tornado.py index e5c7569..2013088 100644 --- a/tests/integration/test_tornado.py +++ b/tests/integration/test_tornado.py @@ -81,12 +81,12 @@ def test_body(get_client, tmpdir, scheme): @pytest.mark.gen_test -def test_effective_url(get_client, scheme, tmpdir): +def test_effective_url(get_client, tmpdir, httpbin): """Ensure that the effective_url is captured""" - url = scheme + "://mockbin.org/redirect/301?url=/html" + url = httpbin.url + "/redirect/1" with vcr.use_cassette(str(tmpdir.join("url.yaml"))): effective_url = (yield get(get_client(), url)).effective_url - assert effective_url == scheme + "://mockbin.org/redirect/301/0" + assert effective_url == httpbin.url + "/get" with vcr.use_cassette(str(tmpdir.join("url.yaml"))) as cass: assert effective_url == (yield get(get_client(), url)).effective_url diff --git a/tests/integration/test_urllib2.py b/tests/integration/test_urllib2.py index 9d56556..2e50c3b 100644 --- a/tests/integration/test_urllib2.py +++ b/tests/integration/test_urllib2.py @@ -57,13 +57,13 @@ def test_response_headers(httpbin_both, tmpdir): @mark.online -def test_effective_url(tmpdir): +def test_effective_url(tmpdir, httpbin): """Ensure that the effective_url is captured""" - url = "http://mockbin.org/redirect/301" + url = httpbin.url + "/redirect-to?url=.%2F&status_code=301" with vcr.use_cassette(str(tmpdir.join("headers.yaml"))): effective_url = urlopen_with_cafile(url).geturl() - assert effective_url == "http://mockbin.org/redirect/301/0" + assert effective_url == httpbin.url + "/" with vcr.use_cassette(str(tmpdir.join("headers.yaml"))): assert effective_url == urlopen_with_cafile(url).geturl() diff --git a/tests/integration/test_urllib3.py b/tests/integration/test_urllib3.py index 710ffdf..fdaf620 100644 --- a/tests/integration/test_urllib3.py +++ b/tests/integration/test_urllib3.py @@ -99,9 +99,9 @@ def test_post(tmpdir, httpbin_both, verify_pool_mgr): @pytest.mark.online -def test_redirects(tmpdir, verify_pool_mgr): +def test_redirects(tmpdir, verify_pool_mgr, httpbin): """Ensure that we can handle redirects""" - url = "http://mockbin.org/redirect/301" + url = httpbin.url + "/redirect/1" with vcr.use_cassette(str(tmpdir.join("verify_pool_mgr.yaml"))): content = verify_pool_mgr.request("GET", url).data diff --git a/tox.ini b/tox.ini index e147690..6203513 100644 --- a/tox.ini +++ b/tox.ini @@ -6,7 +6,7 @@ envlist = {py38,py39,py310,py311,py312}-{requests-urllib3-1,httplib2,urllib3-1,tornado4,boto3,aiohttp,httpx}, {py310,py311,py312}-{requests-urllib3-2,urllib3-2}, {pypy3}-{requests-urllib3-1,httplib2,urllib3-1,tornado4,boto3}, - {py310}-httpx019, + #{py310}-httpx019, cov-report From f4467a8d6c8d13710be91241998eb6e51633ec1e Mon Sep 17 00:00:00 2001 From: Parker Hancock <633163+parkerhancock@users.noreply.github.com> Date: Fri, 8 Dec 2023 10:57:32 -0600 Subject: [PATCH 09/35] make linters happy --- tests/integration/test_httpx.py | 2 -- tests/integration/test_proxy.py | 3 +-- vcr/serializers/compat.py | 2 +- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/tests/integration/test_httpx.py b/tests/integration/test_httpx.py index cf1f6c2..4bedc8a 100644 --- a/tests/integration/test_httpx.py +++ b/tests/integration/test_httpx.py @@ -1,4 +1,3 @@ -import os import pytest @@ -7,7 +6,6 @@ import vcr asyncio = pytest.importorskip("asyncio") httpx = pytest.importorskip("httpx") -from vcr.stubs.httpx_stubs import HTTPX_REDIRECT_PARAM # noqa: E402 @pytest.fixture(params=["https", "http"]) diff --git a/tests/integration/test_proxy.py b/tests/integration/test_proxy.py index c450ab2..7366d33 100644 --- a/tests/integration/test_proxy.py +++ b/tests/integration/test_proxy.py @@ -1,9 +1,8 @@ """Test using a proxy.""" import http.server -import multiprocessing -import threading import socketserver +import threading from urllib.request import urlopen import pytest diff --git a/vcr/serializers/compat.py b/vcr/serializers/compat.py index 0ab358d..65ba96f 100644 --- a/vcr/serializers/compat.py +++ b/vcr/serializers/compat.py @@ -56,7 +56,7 @@ def convert_body_to_unicode(resp): If the request or responses body is bytes, decode it to a string (for python3 support) """ - if type(resp) is not dict: + if not isinstance(resp, dict): # Some of the tests just serialize and deserialize a string. return _convert_string_to_unicode(resp) else: From 5532c0b4cfc403d80f5613d5ecacddb1abe83e1a Mon Sep 17 00:00:00 2001 From: Parker Hancock <633163+parkerhancock@users.noreply.github.com> Date: Fri, 8 Dec 2023 13:15:14 -0600 Subject: [PATCH 10/35] more attempts to make the linters happy --- tests/integration/test_httpx.py | 2 -- vcr/stubs/httpx_stubs.py | 1 - 2 files changed, 3 deletions(-) diff --git a/tests/integration/test_httpx.py b/tests/integration/test_httpx.py index 4bedc8a..22b3e69 100644 --- a/tests/integration/test_httpx.py +++ b/tests/integration/test_httpx.py @@ -1,4 +1,3 @@ - import pytest import vcr @@ -7,7 +6,6 @@ asyncio = pytest.importorskip("asyncio") httpx = pytest.importorskip("httpx") - @pytest.fixture(params=["https", "http"]) def scheme(request): """Fixture that returns both http and https.""" diff --git a/vcr/stubs/httpx_stubs.py b/vcr/stubs/httpx_stubs.py index 0bb3b4a..aa5e05a 100644 --- a/vcr/stubs/httpx_stubs.py +++ b/vcr/stubs/httpx_stubs.py @@ -111,7 +111,6 @@ def _play_responses(cassette, request, vcr_request, client, kwargs): return response - async def _async_vcr_send(cassette, real_send, *args, **kwargs): vcr_request, response = _shared_vcr_send(cassette, real_send, *args, **kwargs) if response: From a8545c89a50099bd8e9590197dfd6a1725185aad Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Sep 2023 13:51:44 +0000 Subject: [PATCH 11/35] build(deps): bump actions/checkout from 3 to 4 Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/codespell.yml | 2 +- .github/workflows/docs.yml | 2 +- .github/workflows/main.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml index 7373aff..c5e1604 100644 --- a/.github/workflows/codespell.yml +++ b/.github/workflows/codespell.yml @@ -17,6 +17,6 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Codespell uses: codespell-project/actions-codespell@v2 diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 1cfea59..eae8327 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-python@v4 with: python-version: "3.11" diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 66797bb..0277aa9 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -16,7 +16,7 @@ jobs: python-version: ["3.8", "3.9", "3.10", "3.11", "3.12-dev", "pypy-3.8", "pypy-3.9", "pypy-3.10"] steps: - - uses: actions/checkout@v3.5.2 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 From 2b2935a1e730527b1d22207565a1f4337f165033 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Aug 2023 13:55:53 +0000 Subject: [PATCH 12/35] build(deps): bump sphinx-rtd-theme from 1.2.2 to 1.3.0 Bumps [sphinx-rtd-theme](https://github.com/readthedocs/sphinx_rtd_theme) from 1.2.2 to 1.3.0. - [Changelog](https://github.com/readthedocs/sphinx_rtd_theme/blob/master/docs/changelog.rst) - [Commits](https://github.com/readthedocs/sphinx_rtd_theme/compare/1.2.2...1.3.0) --- updated-dependencies: - dependency-name: sphinx-rtd-theme dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 65b3725..47a5688 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,2 @@ sphinx<8 -sphinx_rtd_theme==1.2.2 +sphinx_rtd_theme==1.3.0 From 2abf1188a92673931e2dfed91dd97ca25ad3cfbc Mon Sep 17 00:00:00 2001 From: Rob Brackett Date: Fri, 29 Sep 2023 12:18:19 -0700 Subject: [PATCH 13/35] Fix list formatting in v5.1.0 changelog The list of changes was not indented enough, and so didn't actually get formatted as a list when rendering HTML, which left it pretty unreadable. This also adds a blank line between the last 4.x version and 5.0.0 to match the extra blank lines between other major versions. --- docs/changelog.rst | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 9382a7b..52876b5 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -8,21 +8,22 @@ For a full list of triaged issues, bugs and PRs and what release they are target All help in providing PRs to close out bug issues is appreciated. Even if that is providing a repo that fully replicates issues. We have very generous contributors that have added these to bug issues which meant another contributor picked up the bug and closed it out. - 5.1.0 - - Use ruff for linting (instead of current flake8/isort/pyflakes) - thanks @jairhenrique - - Enable rule B (flake8-bugbear) on ruff - thanks @jairhenrique - - Configure read the docs V2 - thanks @jairhenrique - - Fix typo in docs - thanks @quasimik - - Make json.loads of Python >=3.6 decode bytes by itself - thanks @hartwork - - Fix body matcher for chunked requests (fixes #734) - thanks @hartwork - - Fix query param filter for aiohttp (fixes #517) - thanks @hartwork and @salomvary - - Remove unnecessary dependency on six. - thanks @charettes - - build(deps): update sphinx requirement from <7 to <8 - thanks @jairhenrique - - Add action to validate docs - thanks @jairhenrique - - Add editorconfig file - thanks @jairhenrique - - Drop iscoroutinefunction fallback function for unsupported python thanks @jairhenrique + - Use ruff for linting (instead of current flake8/isort/pyflakes) - thanks @jairhenrique + - Enable rule B (flake8-bugbear) on ruff - thanks @jairhenrique + - Configure read the docs V2 - thanks @jairhenrique + - Fix typo in docs - thanks @quasimik + - Make json.loads of Python >=3.6 decode bytes by itself - thanks @hartwork + - Fix body matcher for chunked requests (fixes #734) - thanks @hartwork + - Fix query param filter for aiohttp (fixes #517) - thanks @hartwork and @salomvary + - Remove unnecessary dependency on six. - thanks @charettes + - build(deps): update sphinx requirement from <7 to <8 - thanks @jairhenrique + - Add action to validate docs - thanks @jairhenrique + - Add editorconfig file - thanks @jairhenrique + - Drop iscoroutinefunction fallback function for unsupported python thanks @jairhenrique - 5.0.0 - BREAKING CHANGE: Drop support for Python 3.7. 3.7 is EOL as of 6/27/23 Thanks @jairhenrique - BREAKING CHANGE: Custom Cassette persisters no longer catch ValueError. If you have implemented a custom persister (has anyone implemented a custom persister? Let us know!) then you will need to throw a CassetteNotFoundError when unable to find a cassette. See #681 for discussion and reason for this change. Thanks @amosjyng for the PR and the review from @hartwork + - 4.4.0 - HUGE thanks to @hartwork for all the work done on this release! - Bring vcr/unittest in to vcrpy as a full feature of vcr instead of a separate library. Big thanks to @hartwork for doing this and to @agriffis for originally creating the library From c062c9f54c5d8577aba5a16820627171c1016acb Mon Sep 17 00:00:00 2001 From: Rob Brackett Date: Fri, 29 Sep 2023 12:20:24 -0700 Subject: [PATCH 14/35] Remove spaces at end-of-line in changelog This matches the project's `.editorconfig` rules. --- docs/changelog.rst | 110 ++++++++++++++++++++++----------------------- 1 file changed, 55 insertions(+), 55 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 52876b5..17d1457 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -71,8 +71,8 @@ All help in providing PRs to close out bug issues is appreciated. Even if that i - Bugfix: Fix test suite by switching to mockbin (thanks @jairhenrique) - 4.0.2 - Fix mock imports as reported in #504 by @llybin. Thank you. -- 4.0.1 - - Fix logo alignment for PyPI +- 4.0.1 + - Fix logo alignment for PyPI - 4.0.0 - Remove Python2 support (@hugovk) - Add Python 3.8 TravisCI support (@neozenith) @@ -84,7 +84,7 @@ All help in providing PRs to close out bug issues is appreciated. Even if that i - Add support for `request_info` on mocked responses in aiohttp stub #495 (@nickdirienzo) - doc: fixed variable name (a -> cass) in an example for rewind #492 (@yarikoptic) -- 2.1.1 +- 2.1.1 - Format code with black (@neozenith) - Use latest pypy3 in Travis (@hugovk) - Improve documentation about custom matchers (@gward) @@ -92,7 +92,7 @@ All help in providing PRs to close out bug issues is appreciated. Even if that i - Add `pytest-recording` to the documentation as an alternative Pytest plugin (@Stranger6667) - Fix yarl and python3.5 version issue (@neozenith) - Fix header matcher for boto3 - fixes #474 (@simahawk) -- 2.1.0 +- 2.1.0 - Add a `rewind` method to reset a cassette (thanks @khamidou) - New error message with more details on why the cassette failed to play a request (thanks @arthurHamon2, @neozenith) - Handle connect tunnel URI (thanks @jeking3) @@ -104,9 +104,9 @@ All help in providing PRs to close out bug issues is appreciated. Even if that i - Fix bugs on aiohttp integration (thanks @graingert, @steinnes, @stj, @lamenezes, @lmazuel) - Fix Biopython incompatibility (thanks @rishab121) - Fix Boto3 integration (thanks @1oglop1, @arthurHamon2) -- 2.0.1 +- 2.0.1 - Fix bug when using vcrpy with python 3.4 -- 2.0.0 +- 2.0.0 - Support python 3.7 (fix httplib2 and urllib2, thanks @felixonmars) - [#356] Fixes `before_record_response` so the original response isn't changed (thanks @kgraves) - Fix requests stub when using proxy (thanks @samuelfekete @daneoshiga) @@ -116,56 +116,56 @@ All help in providing PRs to close out bug issues is appreciated. Even if that i - Improve docs (thanks @adamchainz) -- 1.13.0 +- 1.13.0 - Fix support to latest aiohttp version (3.3.2). Fix content-type bug in aiohttp stub. Save URL with query params properly when using aiohttp. -- 1.12.0 +- 1.12.0 - Fix support to latest aiohttp version (3.2.1), Adapted setup to PEP508, Support binary responses on aiohttp, Dropped support for EOL python versions (2.6 and 3.3) -- 1.11.1 +- 1.11.1 - Fix compatibility with newest requests and urllib3 releases -- 1.11.0 +- 1.11.0 - Allow injection of persistence methods + bugfixes (thanks @j-funk and @IvanMalison), - Support python 3.6 + CI tests (thanks @derekbekoe and @graingert), - Support pytest-asyncio coroutines (thanks @graingert) -- 1.10.5 +- 1.10.5 - Added a fix to httplib2 (thanks @carlosds730), Fix an issue with - aiohttp (thanks @madninja), Add missing requirement yarl (thanks @lamenezes), - Remove duplicate mock triple (thanks @FooBarQuaxx) -- 1.10.4 +- 1.10.4 - Fix an issue with asyncio aiohttp (thanks @madninja) -- 1.10.3 +- 1.10.3 - Fix some issues with asyncio and params (thanks @anovikov1984 and @lamenezes) - Fix some issues with cassette serialize / deserialize and empty response bodies (thanks @gRoussac and @dz0ny) -- 1.10.2 +- 1.10.2 - Fix 1.10.1 release - add aiohttp support back in -- 1.10.1 +- 1.10.1 - [bad release] Fix build for Fedora package + python2 (thanks @puiterwijk and @lamenezes) -- 1.10.0 +- 1.10.0 - Add support for aiohttp (thanks @lamenezes) -- 1.9.0 - - Add support for boto3 (thanks @desdm, @foorbarna). +- 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 (thanks @koobs). - - Fix Tornado support behavior for Tornado 3 (thanks @abhinav). +- 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 (thanks @koobs). + - Fix Tornado support behavior for Tornado 3 (thanks @abhinav). - decode_compressed_response option and filter (thanks @jayvdb). -- 1.7.4 [#217] - - Make use_cassette decorated functions actually return a value (thanks @bcen). +- 1.7.4 [#217] + - Make use_cassette decorated functions actually return a value (thanks @bcen). - [#199] Fix path transformation defaults. - Better headers dictionary management. -- 1.7.3 [#188] +- 1.7.3 [#188] - ``additional_matchers`` kwarg on ``use_cassette``. - [#191] Actually support passing multiple before_record_request functions (thanks @agriffis). -- 1.7.2 +- 1.7.2 - [#186] Get effective_url in tornado (thanks @mvschaik) - [#187] Set request_time on Response object in tornado (thanks @abhinav). -- 1.7.1 +- 1.7.1 - [#183] Patch ``fetch_impl`` instead of the entire HTTPClient class for Tornado (thanks @abhinav). -- 1.7.0 - - [#177] Properly support coroutine/generator decoration. +- 1.7.0 + - [#177] Properly support coroutine/generator decoration. - [#178] Support distribute (thanks @graingert). [#163] Make compatibility between python2 and python3 recorded cassettes more robust (thanks @gward). -- 1.6.1 +- 1.6.1 - [#169] Support conditional requirements in old versions of pip - Fix RST parse errors generated by pandoc - [Tornado] Fix unsupported features exception not being raised @@ -179,17 +179,17 @@ All help in providing PRs to close out bug issues is appreciated. Even if that i - Fix crash when cassette path contains cassette library directory (thanks @gazpachoking). - 1.5.0 - Automatic cassette naming and 'application/json' post data filtering (thanks @marco-santamaria). -- 1.4.2 +- 1.4.2 - Fix a bug caused by requests 2.7 and chunked transfer encoding -- 1.4.1 +- 1.4.1 - Include README, tests, LICENSE in package. Thanks @ralphbean. -- 1.4.0 +- 1.4.0 - Filter post data parameters (thanks @eadmundo) - Support for posting files through requests, inject\_cassette kwarg to access cassette from ``use_cassette`` decorated function, ``with_current_defaults`` actually works (thanks @samstav). - 1.3.0 - Fix/add support for urllib3 (thanks @aisch) - Fix default port for https (thanks @abhinav). -- 1.2.0 +- 1.2.0 - Add custom\_patches argument to VCR/Cassette objects to allow users to stub custom classes when cassettes become active. - 1.1.4 - Add force reset around calls to actual connection from stubs, to ensure compatibility with the version of httplib/urlib2 in python 2.7.9. @@ -200,22 +200,22 @@ All help in providing PRs to close out bug issues is appreciated. Even if that i - fix Windows connectionpool stub bug (thanks @gazpachoking) - add support for requests 2.5 - 1.1.2 - - Add urllib==1.7.1 support. + - Add urllib==1.7.1 support. - Make json serialize error handling correct - Improve logging of match failures. -- 1.1.1 - - Use function signature preserving ``wrapt.decorator`` to write the decorator version of use\_cassette in order to ensure compatibility with py.test fixtures and python 2. +- 1.1.1 + - Use function signature preserving ``wrapt.decorator`` to write the decorator version of use\_cassette in order to ensure compatibility with py.test fixtures and python 2. - Move all request filtering into the ``before_record_callable``. -- 1.1.0 +- 1.1.0 - Add ``before_record_response``. Fix several bugs related to the context management of cassettes. -- 1.0.3 +- 1.0.3 - Fix an issue with requests 2.4 and make sure case sensitivity is consistent across python versions - 1.0.2 - Fix an issue with requests 2.3 - 1.0.1 - Fix a bug with the new ignore requests feature and the once record mode -- 1.0.0 - - *BACKWARDS INCOMPATIBLE*: Please see the 'upgrade' section in the README. Take a look at the matcher section as well, you might want to update your ``match_on`` settings. +- 1.0.0 + - *BACKWARDS INCOMPATIBLE*: Please see the 'upgrade' section in the README. Take a look at the matcher section as well, you might want to update your ``match_on`` settings. - Add support for filtering sensitive data from requests, matching query strings after the order changes and improving the built-in matchers, (thanks to @mshytikov) - Support for ignoring requests to certain hosts, bump supported Python3 version to 3.4, fix some bugs with Boto support (thanks @marusich) - Fix error with URL field capitalization in README (thanks @simon-weber) @@ -223,27 +223,27 @@ All help in providing PRs to close out bug issues is appreciated. Even if that i - Added ``all_played`` property on cassette (thanks @mshytikov) - 0.7.0 - - VCR.py now supports Python 3! (thanks @asundg) + - VCR.py now supports Python 3! (thanks @asundg) - Also I refactored the stub connections quite a bit to add support for the putrequest and putheader calls. - - This version also adds support for httplib2 (thanks @nilp0inter). + - This version also adds support for httplib2 (thanks @nilp0inter). - I have added a couple tests for boto since it is an http client in its own right. - Finally, this version includes a fix for a bug where requests wasn't being patched properly (thanks @msabramo). - 0.6.0 - Store response headers as a list since a HTTP response can have the same header twice (happens with set-cookie sometimes). - - This has the added benefit of preserving the order of headers. - - Thanks @smallcode for the bug report leading to this change. + - This has the added benefit of preserving the order of headers. + - Thanks @smallcode for the bug report leading to this change. - I have made an effort to ensure backwards compatibility with the old cassettes' header storage mechanism, but if you want to upgrade to the new header storage, you should delete your cassettes and re-record them. - - Also this release adds better error messages (thanks @msabramo) + - Also this release adds better error messages (thanks @msabramo) - and adds support for using VCR as a decorator (thanks @smallcode for the motivation) - 0.5.0 - - Change the ``response_of`` method to ``responses_of`` since cassettes can now contain more than one response for a request. - - Since this changes the API, I'm bumping the version. - - Also includes 2 bugfixes: - - a better error message when attempting to overwrite a cassette file, + - Change the ``response_of`` method to ``responses_of`` since cassettes can now contain more than one response for a request. + - Since this changes the API, I'm bumping the version. + - Also includes 2 bugfixes: + - a better error message when attempting to overwrite a cassette file, - and a fix for a bug with requests sessions (thanks @msabramo) - 0.4.0 - Change default request recording behavior for multiple requests. - - If you make the same request multiple times to the same URL, the response might be different each time (maybe the response has a timestamp in it or something), so this will make the same request multiple times and save them all. + - If you make the same request multiple times to the same URL, the response might be different each time (maybe the response has a timestamp in it or something), so this will make the same request multiple times and save them all. - Then, when you are replaying the cassette, the responses will be played back in the same order in which they were received. - If you were making multiple requests to the same URL in a cassette before version 0.4.0, you might need to regenerate your cassette files. - Also, removes support for the cassette.play\_count counter API, since individual requests aren't unique anymore. @@ -263,7 +263,7 @@ All help in providing PRs to close out bug issues is appreciated. Even if that i - 0.3.0 - *Backwards incompatible release* - Added support for record modes, and changed the default recording behavior to the "once" record mode. Please see the documentation on record modes for more. - - Added support for custom request matching, and changed the default request matching behavior to match only on the URL and method. + - Added support for custom request matching, and changed the default request matching behavior to match only on the URL and method. - Also, improved the httplib mocking to add support for the ``HTTPConnection.send()`` method. - This means that requests won't actually be sent until the response is read, since I need to record the entire request in order to match up the appropriate response. - I don't think this should cause any issues unless you are sending requests without ever loading the response (which none of the standard httplib wrappers do, as far as I know). @@ -271,13 +271,13 @@ All help in providing PRs to close out bug issues is appreciated. Even if that i - 0.2.1 - Fixed missing modules in setup.py - 0.2.0 - - Added configuration API, which lets you configure some settings on VCR (see the README). + - Added configuration API, which lets you configure some settings on VCR (see the README). - Also, VCR no longer saves cassettes if they haven't changed at all and supports JSON as well as YAML (thanks @sirpengi). - Added amazing new skeumorphic logo, thanks @hairarrow. - 0.1.0 - *backwards incompatible release - delete your old cassette files* - This release adds the ability to access the cassette to make assertions on it - - as well as a major code refactor thanks to @dlecocq. + - as well as a major code refactor thanks to @dlecocq. - It also fixes a couple longstanding bugs with redirects and HTTPS. [#3 and #4] - 0.0.4 - If you have libyaml installed, vcrpy will use the c bindings instead. Speed up your tests! Thanks @dlecocq From 0e5718220781ab0ce841aabbdd3d4dede9aa8cff Mon Sep 17 00:00:00 2001 From: Sebastian Pipping Date: Fri, 8 Dec 2023 22:54:58 +0100 Subject: [PATCH 15/35] setup.py: Block urllib3 >=2 for PyPy (including 3.10) It kept failing CI --- setup.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 0afb810..37d1558 100644 --- a/setup.py +++ b/setup.py @@ -46,13 +46,15 @@ install_requires = [ "PyYAML", "wrapt", "yarl", - # Support for urllib3 >=2 needs Python >=3.10 - # so we need to block urllib3 >=2 for Python <3.10 for now. + # Support for urllib3 >=2 needs CPython >=3.10 + # so we need to block urllib3 >=2 for Python <3.10 and PyPy for now. # Note that vcrpy would work fine without any urllib3 around, # so this block and the dependency can be dropped at some point # in the future. For more Details: # https://github.com/kevin1024/vcrpy/pull/699#issuecomment-1551439663 "urllib3 <2; python_version <'3.10'", + # https://github.com/kevin1024/vcrpy/pull/775#issuecomment-1847849962 + "urllib3 <2; platform_python_implementation =='PyPy'", ] tests_require = [ From 954a100dfd65cc689aa16fc1f0c536c947e710be Mon Sep 17 00:00:00 2001 From: Sebastian Pipping Date: Fri, 6 Oct 2023 17:02:42 +0200 Subject: [PATCH 16/35] Finish up on Python 3.12 support --- .github/workflows/docs.yml | 2 +- .github/workflows/main.yml | 2 +- .readthedocs.yaml | 2 +- setup.py | 1 + 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index eae8327..68d8f79 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -13,7 +13,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-python@v4 with: - python-version: "3.11" + python-version: "3.12" - name: Install build dependencies run: pip install -r docs/requirements.txt diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0277aa9..9804d86 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,7 +13,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12-dev", "pypy-3.8", "pypy-3.9", "pypy-3.10"] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "pypy-3.8", "pypy-3.9", "pypy-3.10"] steps: - uses: actions/checkout@v4 diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 68c4590..84d2691 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -9,7 +9,7 @@ version: 2 build: os: ubuntu-22.04 tools: - python: "3.11" + python: "3.12" # Build documentation in the docs/ directory with Sphinx sphinx: diff --git a/setup.py b/setup.py index 37d1558..71ba6a2 100644 --- a/setup.py +++ b/setup.py @@ -100,6 +100,7 @@ setup( "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", From cebdd458492c6f583377f76f18d39e7172472bc0 Mon Sep 17 00:00:00 2001 From: Jair Henrique Date: Sun, 10 Dec 2023 19:54:12 -0300 Subject: [PATCH 17/35] Use ruff to check code format intead of black --- README.rst | 5 +---- pyproject.toml | 9 ++------- tox.ini | 4 +--- vcr/patch.py | 12 ++++++++---- 4 files changed, 12 insertions(+), 18 deletions(-) diff --git a/README.rst b/README.rst index 5a0ed8e..4f351ac 100644 --- a/README.rst +++ b/README.rst @@ -4,7 +4,7 @@ VCR.py 📼 ########### -|PyPI| |Python versions| |Build Status| |CodeCov| |Gitter| |CodeStyleBlack| +|PyPI| |Python versions| |Build Status| |CodeCov| |Gitter| ---- @@ -70,6 +70,3 @@ more details .. |CodeCov| image:: https://codecov.io/gh/kevin1024/vcrpy/branch/master/graph/badge.svg :target: https://codecov.io/gh/kevin1024/vcrpy :alt: Code Coverage Status -.. |CodeStyleBlack| image:: https://img.shields.io/badge/code%20style-black-000000.svg - :target: https://github.com/psf/black - :alt: Code Style: black diff --git a/pyproject.toml b/pyproject.toml index 4830452..7dec861 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,3 @@ -[tool.black] -line-length=110 - [tool.codespell] skip = '.git,*.pdf,*.svg,.tox' ignore-regex = "\\\\[fnrstv]" @@ -8,9 +5,7 @@ ignore-regex = "\\\\[fnrstv]" # ignore-words-list = '' [tool.pytest.ini_options] -markers = [ - "online", -] +markers = ["online"] [tool.ruff] select = [ @@ -30,4 +25,4 @@ line-length = 110 target-version = "py38" [tool.ruff.isort] -known-first-party = [ "vcr" ] +known-first-party = ["vcr"] diff --git a/tox.ini b/tox.ini index 6203513..3d85a1b 100644 --- a/tox.ini +++ b/tox.ini @@ -36,12 +36,10 @@ commands = [testenv:lint] skipsdist = True commands = - black --version - black --check --diff . ruff --version + ruff format --check . ruff check . deps = - black ruff basepython = python3.10 diff --git a/vcr/patch.py b/vcr/patch.py index f69ae76..a9e86bd 100644 --- a/vcr/patch.py +++ b/vcr/patch.py @@ -260,10 +260,14 @@ class CassettePatcherBuilder: yield cpool, "HTTPConnectionWithTimeout", VCRHTTPConnectionWithTimeout yield cpool, "HTTPSConnectionWithTimeout", VCRHTTPSConnectionWithTimeout - yield cpool, "SCHEME_TO_CONNECTION", { - "http": VCRHTTPConnectionWithTimeout, - "https": VCRHTTPSConnectionWithTimeout, - } + yield ( + cpool, + "SCHEME_TO_CONNECTION", + { + "http": VCRHTTPConnectionWithTimeout, + "https": VCRHTTPSConnectionWithTimeout, + }, + ) @_build_patchers_from_mock_triples_decorator def _tornado(self): From ecb5d84f0f4b6c63e7b682496f152791a7399bff Mon Sep 17 00:00:00 2001 From: Sebastian Pipping Date: Fri, 8 Dec 2023 23:42:07 +0100 Subject: [PATCH 18/35] tests: Fix imports to tests/assertions.py --- tests/__init__.py | 0 tests/integration/test_filter.py | 3 ++- tests/integration/test_httplib2.py | 3 ++- tests/integration/test_requests.py | 3 ++- tests/integration/test_stubs.py | 4 ++-- tests/integration/test_tornado.py | 3 ++- tests/integration/test_urllib2.py | 3 ++- tests/integration/test_urllib3.py | 3 ++- 8 files changed, 14 insertions(+), 8 deletions(-) create mode 100644 tests/__init__.py diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/integration/test_filter.py b/tests/integration/test_filter.py index 19d2f69..f036dd4 100644 --- a/tests/integration/test_filter.py +++ b/tests/integration/test_filter.py @@ -5,10 +5,11 @@ from urllib.parse import urlencode from urllib.request import Request, urlopen import pytest -from assertions import assert_cassette_has_one_response, assert_is_json_bytes import vcr +from ..assertions import assert_cassette_has_one_response, assert_is_json_bytes + def _request_with_auth(url, username, password): request = Request(url) diff --git a/tests/integration/test_httplib2.py b/tests/integration/test_httplib2.py index 7fecd72..562c35c 100644 --- a/tests/integration/test_httplib2.py +++ b/tests/integration/test_httplib2.py @@ -3,10 +3,11 @@ from urllib.parse import urlencode import pytest import pytest_httpbin.certs -from assertions import assert_cassette_has_one_response import vcr +from ..assertions import assert_cassette_has_one_response + httplib2 = pytest.importorskip("httplib2") diff --git a/tests/integration/test_requests.py b/tests/integration/test_requests.py index 493df41..48590fc 100644 --- a/tests/integration/test_requests.py +++ b/tests/integration/test_requests.py @@ -1,9 +1,10 @@ """Test requests' interaction with vcr""" import pytest -from assertions import assert_cassette_empty, assert_is_json_bytes import vcr +from ..assertions import assert_cassette_empty, assert_is_json_bytes + requests = pytest.importorskip("requests") diff --git a/tests/integration/test_stubs.py b/tests/integration/test_stubs.py index 6b6a6e4..37a1b47 100644 --- a/tests/integration/test_stubs.py +++ b/tests/integration/test_stubs.py @@ -2,10 +2,10 @@ import http.client as httplib import json import zlib -from assertions import assert_is_json_bytes - import vcr +from ..assertions import assert_is_json_bytes + def _headers_are_case_insensitive(host, port): conn = httplib.HTTPConnection(host, port) diff --git a/tests/integration/test_tornado.py b/tests/integration/test_tornado.py index 2013088..0ba4897 100644 --- a/tests/integration/test_tornado.py +++ b/tests/integration/test_tornado.py @@ -3,11 +3,12 @@ import json import pytest -from assertions import assert_cassette_empty, assert_is_json_bytes import vcr from vcr.errors import CannotOverwriteExistingCassetteException +from ..assertions import assert_cassette_empty, assert_is_json_bytes + tornado = pytest.importorskip("tornado") http = pytest.importorskip("tornado.httpclient") diff --git a/tests/integration/test_urllib2.py b/tests/integration/test_urllib2.py index 2e50c3b..078c14a 100644 --- a/tests/integration/test_urllib2.py +++ b/tests/integration/test_urllib2.py @@ -5,12 +5,13 @@ from urllib.parse import urlencode from urllib.request import urlopen import pytest_httpbin.certs -from assertions import assert_cassette_has_one_response from pytest import mark # Internal imports import vcr +from ..assertions import assert_cassette_has_one_response + def urlopen_with_cafile(*args, **kwargs): context = ssl.create_default_context(cafile=pytest_httpbin.certs.where()) diff --git a/tests/integration/test_urllib3.py b/tests/integration/test_urllib3.py index fdaf620..d159ddb 100644 --- a/tests/integration/test_urllib3.py +++ b/tests/integration/test_urllib3.py @@ -4,12 +4,13 @@ import pytest import pytest_httpbin -from assertions import assert_cassette_empty, assert_is_json_bytes import vcr from vcr.patch import force_reset from vcr.stubs.compat import get_headers +from ..assertions import assert_cassette_empty, assert_is_json_bytes + urllib3 = pytest.importorskip("urllib3") From 796dc8de7e3a0da554d524f49be9aed8e2dd1a53 Mon Sep 17 00:00:00 2001 From: Jair Henrique Date: Sun, 10 Dec 2023 20:31:35 -0300 Subject: [PATCH 19/35] Move lint from tox to gh action --- .github/workflows/lint.yml | 22 ++++++++++++++++++++++ docs/contributing.rst | 17 ++++++++--------- tox.ini | 17 +++-------------- 3 files changed, 33 insertions(+), 23 deletions(-) create mode 100644 .github/workflows/lint.yml diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..88a775e --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,22 @@ +name: Lint + +on: + push: + branches: + - master + pull_request: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v4 + - name: Install dependencies + run: pip install ruff + - name: Lint + run: | + ruff --version + ruff format --check . + ruff check . diff --git a/docs/contributing.rst b/docs/contributing.rst index f8e36d4..6cbc41d 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -24,7 +24,7 @@ So whilst reporting issues are valuable, please consider: - contributing an issue with a toy repo that replicates the issue. - contributing PRs is a more valuable donation of your time and effort. -Thanks again for your interest and support in VCRpy. +Thanks again for your interest and support in VCRpy. We really appreciate it. @@ -57,7 +57,7 @@ Simply adding these three labels for incoming issues means a lot for maintaining - Which library does it affect? ``core``, ``aiohttp``, ``requests``, ``urllib3``, ``tornado4``, ``httplib2`` - If it is a bug, is it ``Verified Can Replicate`` or ``Requires Help Replicating`` - Thanking people for raising issues. Feedback is always appreciated. - - Politely asking if they are able to link to an example repo that replicates the issue if they haven't already. Being able to *clone and go* helps the next person and we like that. 😃 + - Politely asking if they are able to link to an example repo that replicates the issue if they haven't already. Being able to *clone and go* helps the next person and we like that. 😃 **Maintainer:** @@ -68,7 +68,7 @@ This involves creating PRs to address bugs and enhancement requests. It also mea The PR reviewer is a second set of eyes to see if: - Are there tests covering the code paths added/modified? - Do the tests and modifications make sense seem appropriate? - - Add specific feedback, even on approvals, why it is accepted. eg "I like how you use a context manager there. 😄 " + - Add specific feedback, even on approvals, why it is accepted. eg "I like how you use a context manager there. 😄 " - Also make sure they add a line to `docs/changelog.rst` to claim credit for their contribution. **Release Manager:** @@ -86,10 +86,10 @@ Running VCR's test suite The tests are all run automatically on `Travis CI `__, but you can also run them yourself using `pytest `__ and -`Tox `__. +`Tox `__. -Tox will automatically run them in all environments VCR.py supports if they are available on your `PATH`. Alternatively you can use `tox-pyenv `_ with -`pyenv `_. +Tox will automatically run them in all environments VCR.py supports if they are available on your `PATH`. Alternatively you can use `tox-pyenv `_ with +`pyenv `_. We recommend you read the documentation for each and see the section further below. The test suite is pretty big and slow, but you can tell tox to only run specific tests like this:: @@ -114,8 +114,8 @@ Using PyEnv with VCR's test suite --------------------------------- PyEnv is a tool for managing multiple installation of python on your system. -See the full documentation at their `github `_ -but we are also going to use `tox-pyenv `_ +See the full documentation at their `github `_ +but we are also going to use `tox-pyenv `_ in this example:: git clone https://github.com/pyenv/pyenv ~/.pyenv @@ -139,7 +139,6 @@ in this example:: tox # Run the whole test suite or just part of it - tox -e lint tox -e py38-requests diff --git a/tox.ini b/tox.ini index 3d85a1b..2992315 100644 --- a/tox.ini +++ b/tox.ini @@ -2,7 +2,6 @@ skip_missing_interpreters=true envlist = cov-clean, - lint, {py38,py39,py310,py311,py312}-{requests-urllib3-1,httplib2,urllib3-1,tornado4,boto3,aiohttp,httpx}, {py310,py311,py312}-{requests-urllib3-2,urllib3-2}, {pypy3}-{requests-urllib3-1,httplib2,urllib3-1,tornado4,boto3}, @@ -14,7 +13,7 @@ envlist = python = 3.8: py38 3.9: py39 - 3.10: py310, lint + 3.10: py310 3.11: py311 3.12: py312 pypy-3: pypy3 @@ -33,16 +32,6 @@ commands = coverage html coverage report --fail-under=90 -[testenv:lint] -skipsdist = True -commands = - ruff --version - ruff format --check . - ruff check . -deps = - ruff -basepython = python3.10 - [testenv] # Need to use develop install so that paths # for aggregate code coverage combine @@ -73,8 +62,8 @@ deps = httpx019: httpx==0.19 {py38,py39,py310}-{httpx}: pytest-asyncio depends = - lint,{py38,py39,py310,py311,py312,pypy3}-{requests-urllib3-1,httplib2,urllib3-1,tornado4,boto3},{py310,py311,py312}-{requests-urllib3-2,urllib3-2},{py38,py39,py310,py311,py312}-{aiohttp},{py38,py39,py310,py311,py312}-{httpx}: cov-clean - cov-report: lint,{py38,py39,py310,py311,py312,pypy3}-{requests-urllib3-1,httplib2,urllib3-1,tornado4,boto3},{py310,py311,py312}-{requests-urllib3-2,urllib3-2},{py38,py39,py310,py311,py312}-{aiohttp} + {py38,py39,py310,py311,py312,pypy3}-{requests-urllib3-1,httplib2,urllib3-1,tornado4,boto3},{py310,py311,py312}-{requests-urllib3-2,urllib3-2},{py38,py39,py310,py311,py312}-{aiohttp},{py38,py39,py310,py311,py312}-{httpx}: cov-clean + cov-report: {py38,py39,py310,py311,py312,pypy3}-{requests-urllib3-1,httplib2,urllib3-1,tornado4,boto3},{py310,py311,py312}-{requests-urllib3-2,urllib3-2},{py38,py39,py310,py311,py312}-{aiohttp} passenv = AWS_ACCESS_KEY_ID AWS_DEFAULT_REGION From e8346ad30e648f686a48af04e168fda4592b0de7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Dec 2023 13:04:27 +0000 Subject: [PATCH 20/35] build(deps): bump actions/setup-python from 4 to 5 Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4 to 5. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/docs.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/main.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 68d8f79..2faf9a7 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -11,7 +11,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: "3.12" diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 88a775e..ff3a50a 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -12,7 +12,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 - name: Install dependencies run: pip install ruff - name: Lint diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9804d86..53f1488 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -19,7 +19,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} From dd97b02b72dcaf765b805e92d22b16f0828da7d5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Dec 2023 13:59:48 +0000 Subject: [PATCH 21/35] build(deps): bump sphinx-rtd-theme from 1.3.0 to 2.0.0 Bumps [sphinx-rtd-theme](https://github.com/readthedocs/sphinx_rtd_theme) from 1.3.0 to 2.0.0. - [Changelog](https://github.com/readthedocs/sphinx_rtd_theme/blob/master/docs/changelog.rst) - [Commits](https://github.com/readthedocs/sphinx_rtd_theme/compare/1.3.0...2.0.0) --- updated-dependencies: - dependency-name: sphinx-rtd-theme dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 47a5688..2550dbc 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,2 @@ sphinx<8 -sphinx_rtd_theme==1.3.0 +sphinx_rtd_theme==2.0.0 From dbf7a3337bf609f1df2cf44dac64ff5285281db1 Mon Sep 17 00:00:00 2001 From: Jair Henrique Date: Tue, 12 Dec 2023 13:36:39 -0300 Subject: [PATCH 22/35] Show ruff diff errors --- .github/workflows/lint.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index ff3a50a..619ed4c 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -18,5 +18,5 @@ jobs: - name: Lint run: | ruff --version - ruff format --check . - ruff check . + ruff format --check --diff . + ruff check --diff . From db1e9e71800b7c261d37a227fb9234d5013a59b6 Mon Sep 17 00:00:00 2001 From: Parker Hancock <633163+parkerhancock@users.noreply.github.com> Date: Fri, 8 Dec 2023 16:17:27 -0600 Subject: [PATCH 23/35] make cassettes human readable --- tests/integration/test_httpx.py | 26 ++++++++++++++++++++++++++ vcr/stubs/httpx_stubs.py | 10 +++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/tests/integration/test_httpx.py b/tests/integration/test_httpx.py index 22b3e69..f166157 100644 --- a/tests/integration/test_httpx.py +++ b/tests/integration/test_httpx.py @@ -285,3 +285,29 @@ def test_stream(tmpdir, httpbin, do_request): assert cassette_content == response_content assert len(cassette_content) == 512 assert cassette.play_count == 1 + +@pytest.mark.online +def test_text_content_type(tmpdir, httpbin, do_request): + url = httpbin.url + "/json" + + with vcr.use_cassette(str(tmpdir.join("json_type.yaml"))): + response = do_request()("GET", url) + + with vcr.use_cassette(str(tmpdir.join("json_type.yaml"))) as cassette: + cassette_response = do_request()("GET", url) + assert cassette_response.content == response.content + assert cassette.play_count == 1 + assert isinstance(cassette.responses[0]['content'], str) + +@pytest.mark.online +def test_binary_content_type(tmpdir, httpbin, do_request): + url = httpbin.url + "/bytes/1024" + + with vcr.use_cassette(str(tmpdir.join("json_type.yaml"))): + response = do_request()("GET", url) + + with vcr.use_cassette(str(tmpdir.join("json_type.yaml"))) as cassette: + cassette_response = do_request()("GET", url) + assert cassette_response.content == response.content + assert cassette.play_count == 1 + assert isinstance(cassette.responses[0]['content'], bytes) \ No newline at end of file diff --git a/vcr/stubs/httpx_stubs.py b/vcr/stubs/httpx_stubs.py index aa5e05a..ddf8e03 100644 --- a/vcr/stubs/httpx_stubs.py +++ b/vcr/stubs/httpx_stubs.py @@ -33,12 +33,18 @@ def _transform_headers(httpx_response): return out + def _to_serialized_response(httpx_response): + try: + content = httpx_response.content.decode("utf-8") + except UnicodeDecodeError: + content = httpx_response.content + return { "status_code": httpx_response.status_code, "http_version": httpx_response.http_version, "headers": _transform_headers(httpx_response), - "content": httpx_response.content, + "content": content, } @@ -58,6 +64,8 @@ def _from_serialized_headers(headers): @patch("httpx.Response.read", MagicMock()) def _from_serialized_response(request, serialized_response, history=None): content = serialized_response.get("content") + if isinstance(content, str): + content = content.encode("utf-8") response = httpx.Response( status_code=serialized_response.get("status_code"), request=request, From 85ae012d9c2bf90cd7a3e1311e073bc3b6fe2afc Mon Sep 17 00:00:00 2001 From: Parker Hancock <633163+parkerhancock@users.noreply.github.com> Date: Tue, 12 Dec 2023 09:14:36 -0600 Subject: [PATCH 24/35] fix linting --- tests/integration/test_httpx.py | 4 ++-- vcr/stubs/httpx_stubs.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/integration/test_httpx.py b/tests/integration/test_httpx.py index f166157..a69b6c4 100644 --- a/tests/integration/test_httpx.py +++ b/tests/integration/test_httpx.py @@ -298,7 +298,7 @@ def test_text_content_type(tmpdir, httpbin, do_request): assert cassette_response.content == response.content assert cassette.play_count == 1 assert isinstance(cassette.responses[0]['content'], str) - + @pytest.mark.online def test_binary_content_type(tmpdir, httpbin, do_request): url = httpbin.url + "/bytes/1024" @@ -310,4 +310,4 @@ def test_binary_content_type(tmpdir, httpbin, do_request): cassette_response = do_request()("GET", url) assert cassette_response.content == response.content assert cassette.play_count == 1 - assert isinstance(cassette.responses[0]['content'], bytes) \ No newline at end of file + assert isinstance(cassette.responses[0]['content'], bytes) diff --git a/vcr/stubs/httpx_stubs.py b/vcr/stubs/httpx_stubs.py index ddf8e03..140c0ab 100644 --- a/vcr/stubs/httpx_stubs.py +++ b/vcr/stubs/httpx_stubs.py @@ -39,7 +39,7 @@ def _to_serialized_response(httpx_response): content = httpx_response.content.decode("utf-8") except UnicodeDecodeError: content = httpx_response.content - + return { "status_code": httpx_response.status_code, "http_version": httpx_response.http_version, From 88cf01aa14ef21aed2e3ca130655400b6ead9e48 Mon Sep 17 00:00:00 2001 From: Jair Henrique Date: Tue, 12 Dec 2023 13:32:49 -0300 Subject: [PATCH 25/35] Fix format code --- tests/integration/test_httpx.py | 6 ++++-- vcr/stubs/httpx_stubs.py | 1 - 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/integration/test_httpx.py b/tests/integration/test_httpx.py index a69b6c4..723daed 100644 --- a/tests/integration/test_httpx.py +++ b/tests/integration/test_httpx.py @@ -286,6 +286,7 @@ def test_stream(tmpdir, httpbin, do_request): assert len(cassette_content) == 512 assert cassette.play_count == 1 + @pytest.mark.online def test_text_content_type(tmpdir, httpbin, do_request): url = httpbin.url + "/json" @@ -297,7 +298,8 @@ def test_text_content_type(tmpdir, httpbin, do_request): cassette_response = do_request()("GET", url) assert cassette_response.content == response.content assert cassette.play_count == 1 - assert isinstance(cassette.responses[0]['content'], str) + assert isinstance(cassette.responses[0]["content"], str) + @pytest.mark.online def test_binary_content_type(tmpdir, httpbin, do_request): @@ -310,4 +312,4 @@ def test_binary_content_type(tmpdir, httpbin, do_request): cassette_response = do_request()("GET", url) assert cassette_response.content == response.content assert cassette.play_count == 1 - assert isinstance(cassette.responses[0]['content'], bytes) + assert isinstance(cassette.responses[0]["content"], bytes) diff --git a/vcr/stubs/httpx_stubs.py b/vcr/stubs/httpx_stubs.py index 140c0ab..4d4b96a 100644 --- a/vcr/stubs/httpx_stubs.py +++ b/vcr/stubs/httpx_stubs.py @@ -33,7 +33,6 @@ def _transform_headers(httpx_response): return out - def _to_serialized_response(httpx_response): try: content = httpx_response.content.decode("utf-8") From 3168e7813e8a788d13b245b10ff4593dc54d1279 Mon Sep 17 00:00:00 2001 From: Sebastian Pipping Date: Tue, 12 Dec 2023 18:41:52 +0100 Subject: [PATCH 26/35] pre-commit: Enable Ruff and Ruff Black-style formatting --- .pre-commit-config.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..88c0c1e --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,9 @@ +# Copyright (c) 2023 Sebastian Pipping +# Licensed under the MIT license + +repos: + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.1.7 + hooks: + - id: ruff + - id: ruff-format From a542567e4a968c43d30ab371c2b09eb2153190ec Mon Sep 17 00:00:00 2001 From: Sebastian Pipping Date: Tue, 12 Dec 2023 18:46:04 +0100 Subject: [PATCH 27/35] pre-commit: Integrate with GitHub Actions CI --- .github/workflows/pre-commit.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .github/workflows/pre-commit.yml diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml new file mode 100644 index 0000000..a7bb78b --- /dev/null +++ b/.github/workflows/pre-commit.yml @@ -0,0 +1,20 @@ +# Copyright (c) 2023 Sebastian Pipping +# Licensed under the MIT license + +name: Run pre-commit + +on: +- pull_request +- push +- workflow_dispatch + +jobs: + pre-commit: + name: Run pre-commit + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: 3.12 + - uses: pre-commit/action@v3.0.0 From e5555a5d5b01113f82631556e78d6711d1826490 Mon Sep 17 00:00:00 2001 From: Sebastian Pipping Date: Tue, 12 Dec 2023 18:46:24 +0100 Subject: [PATCH 28/35] pre-commit: Make CI keep keep the config up to date via pull requests --- .../workflows/pre-commit-detect-outdated.yml | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 .github/workflows/pre-commit-detect-outdated.yml diff --git a/.github/workflows/pre-commit-detect-outdated.yml b/.github/workflows/pre-commit-detect-outdated.yml new file mode 100644 index 0000000..8b782d1 --- /dev/null +++ b/.github/workflows/pre-commit-detect-outdated.yml @@ -0,0 +1,62 @@ +# Copyright (c) 2023 Sebastian Pipping +# Licensed under the MIT license + +name: Detect outdated pre-commit hooks + +on: + schedule: + - cron: '0 16 * * 5' # Every Friday 4pm + +# NOTE: This will drop all permissions from GITHUB_TOKEN except metadata read, +# and then (re)add the ones listed below: +permissions: + contents: write + pull-requests: write + +jobs: + pre_commit_detect_outdated: + name: Detect outdated pre-commit hooks + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Python 3.12 + uses: actions/setup-python@v4 + with: + python-version: 3.12 + + - name: Install pre-commit + run: |- + pip install \ + --disable-pip-version-check \ + --no-warn-script-location \ + --user \ + pre-commit + echo "PATH=${HOME}/.local/bin:${PATH}" >> "${GITHUB_ENV}" + + - name: Check for outdated hooks + run: |- + pre-commit autoupdate + git diff -- .pre-commit-config.yaml + + - name: Create pull request from changes (if any) + id: create-pull-request + uses: peter-evans/create-pull-request@v5 + with: + author: 'pre-commit ' + base: master + body: |- + For your consideration. + + :warning: Please **CLOSE AND RE-OPEN** this pull request so that [further workflow runs get triggered](https://github.com/peter-evans/create-pull-request/blob/main/docs/concepts-guidelines.md#triggering-further-workflow-runs) for this pull request. + branch: precommit-autoupdate + commit-message: "pre-commit: Autoupdate" + delete-branch: true + draft: true + labels: enhancement + title: "pre-commit: Autoupdate" + + - name: Log pull request URL + if: "${{ steps.create-pull-request.outputs.pull-request-url }}" + run: | + echo "Pull request URL is: ${{ steps.create-pull-request.outputs.pull-request-url }}" From 2d5f8a499e6be17eef6a1902f35034daaf0a519b Mon Sep 17 00:00:00 2001 From: Sebastian Pipping Date: Tue, 12 Dec 2023 18:48:42 +0100 Subject: [PATCH 29/35] lint.yml: Drop as superseded by pre-commit.yml --- .github/workflows/lint.yml | 22 ---------------------- 1 file changed, 22 deletions(-) delete mode 100644 .github/workflows/lint.yml diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml deleted file mode 100644 index 619ed4c..0000000 --- a/.github/workflows/lint.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Lint - -on: - push: - branches: - - master - pull_request: - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - - name: Install dependencies - run: pip install ruff - - name: Lint - run: | - ruff --version - ruff format --check --diff . - ruff check --diff . From 957c8bd7a37ceaed0ac4f67083ca24ac4fa4be61 Mon Sep 17 00:00:00 2001 From: Sebastian Pipping Date: Tue, 12 Dec 2023 18:51:03 +0100 Subject: [PATCH 30/35] pre-commit: Protect against accidental merge conflict markers --- .pre-commit-config.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 88c0c1e..522033f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,3 +7,8 @@ repos: hooks: - id: ruff - id: ruff-format + + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: check-merge-conflict From e9102b2bb40e301ffaa55bfacd2f3d352c67e5b7 Mon Sep 17 00:00:00 2001 From: Sebastian Pipping Date: Tue, 12 Dec 2023 18:54:40 +0100 Subject: [PATCH 31/35] pre-commit: Mass-apply end-of-file-fixer --- docs/_static/vcr.svg | 2 +- docs/changelog.rst | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/_static/vcr.svg b/docs/_static/vcr.svg index 061b5b1..b0957dc 100644 --- a/docs/_static/vcr.svg +++ b/docs/_static/vcr.svg @@ -24,4 +24,4 @@ - \ No newline at end of file + diff --git a/docs/changelog.rst b/docs/changelog.rst index 17d1457..52f6980 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -287,4 +287,3 @@ All help in providing PRs to close out bug issues is appreciated. Even if that i - Add support for requests / urllib3 - 0.0.1 - Initial Release - From 962284072bebced7c75c33763b60254a1659fffb Mon Sep 17 00:00:00 2001 From: Sebastian Pipping Date: Tue, 12 Dec 2023 18:55:16 +0100 Subject: [PATCH 32/35] pre-commit: Enable end-of-file-fixer --- .pre-commit-config.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 522033f..e3165dc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,3 +12,4 @@ repos: rev: v4.4.0 hooks: - id: check-merge-conflict + - id: end-of-file-fixer From 1dd9cbde8b6a44f4779cd230f2c1f552be11a2a1 Mon Sep 17 00:00:00 2001 From: Sebastian Pipping Date: Tue, 12 Dec 2023 18:56:19 +0100 Subject: [PATCH 33/35] pre-commit: Mass-apply trailing-whitespace --- .github/workflows/docs.yml | 2 +- docs/advanced.rst | 8 ++--- runtests.sh | 2 +- tests/fixtures/migration/new_cassette.json | 6 ++-- tests/fixtures/migration/new_cassette.yaml | 2 +- tests/fixtures/migration/old_cassette.json | 34 +++++++++++----------- tests/fixtures/wild/domain_redirect.yaml | 2 +- 7 files changed, 28 insertions(+), 28 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 2faf9a7..f0b51b6 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -14,7 +14,7 @@ jobs: - uses: actions/setup-python@v5 with: python-version: "3.12" - + - name: Install build dependencies run: pip install -r docs/requirements.txt - name: Rendering HTML documentation diff --git a/docs/advanced.rst b/docs/advanced.rst index 65b3d4b..118570f 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -16,7 +16,7 @@ a nice addition. Here's an example: with vcr.use_cassette('fixtures/vcr_cassettes/synopsis.yaml') as cass: response = urllib2.urlopen('http://www.zombo.com/').read() # cass should have 1 request inside it - assert len(cass) == 1 + assert len(cass) == 1 # the request uri should have been http://www.zombo.com/ assert cass.requests[0].uri == 'http://www.zombo.com/' @@ -208,7 +208,7 @@ So these two calls are the same: # original (still works) vcr = VCR(filter_headers=['authorization']) - + # new vcr = VCR(filter_headers=[('authorization', None)]) @@ -218,7 +218,7 @@ Here are two examples of the new functionality: # replace with a static value (most common) vcr = VCR(filter_headers=[('authorization', 'XXXXXX')]) - + # replace with a callable, for example when testing # lots of different kinds of authorization. def replace_auth(key, value, request): @@ -286,7 +286,7 @@ sensitive data from the response body: before_record_response=scrub_string(settings.USERNAME, 'username'), ) with my_vcr.use_cassette('test.yml'): - # your http code here + # your http code here Decode compressed response diff --git a/runtests.sh b/runtests.sh index f219725..9e40a2d 100755 --- a/runtests.sh +++ b/runtests.sh @@ -1,7 +1,7 @@ #!/bin/bash # https://blog.ionelmc.ro/2015/04/14/tox-tricks-and-patterns/#when-it-inevitably-leads-to-shell-scripts -# If you are getting an INVOCATION ERROR for this script then there is +# If you are getting an INVOCATION ERROR for this script then there is # a good chance you are running on Windows. # You can and should use WSL for running tox on Windows when it calls bash scripts. REQUESTS_CA_BUNDLE=`python3 -m pytest_httpbin.certs` exec pytest "$@" diff --git a/tests/fixtures/migration/new_cassette.json b/tests/fixtures/migration/new_cassette.json index 5526989..822fa2a 100644 --- a/tests/fixtures/migration/new_cassette.json +++ b/tests/fixtures/migration/new_cassette.json @@ -15,9 +15,9 @@ }, "response": { "status": { - "message": "OK", + "message": "OK", "code": 200 - }, + }, "headers": { "access-control-allow-origin": ["*"], "content-type": ["application/json"], @@ -25,7 +25,7 @@ "server": ["gunicorn/0.17.4"], "content-length": ["32"], "connection": ["keep-alive"] - }, + }, "body": { "string": "{\n \"origin\": \"217.122.164.194\"\n}" } diff --git a/tests/fixtures/migration/new_cassette.yaml b/tests/fixtures/migration/new_cassette.yaml index e319dc8..b30652f 100644 --- a/tests/fixtures/migration/new_cassette.yaml +++ b/tests/fixtures/migration/new_cassette.yaml @@ -2,7 +2,7 @@ version: 1 interactions: - request: body: null - headers: + headers: accept: ['*/*'] accept-encoding: ['gzip, deflate, compress'] user-agent: ['python-requests/2.2.1 CPython/2.6.1 Darwin/10.8.0'] diff --git a/tests/fixtures/migration/old_cassette.json b/tests/fixtures/migration/old_cassette.json index f6bfed1..7c4ff68 100644 --- a/tests/fixtures/migration/old_cassette.json +++ b/tests/fixtures/migration/old_cassette.json @@ -1,31 +1,31 @@ [ { "request": { - "body": null, - "protocol": "http", - "method": "GET", + "body": null, + "protocol": "http", + "method": "GET", "headers": { - "accept-encoding": "gzip, deflate, compress", - "accept": "*/*", + "accept-encoding": "gzip, deflate, compress", + "accept": "*/*", "user-agent": "python-requests/2.2.1 CPython/2.6.1 Darwin/10.8.0" - }, - "host": "httpbin.org", - "path": "/ip", + }, + "host": "httpbin.org", + "path": "/ip", "port": 80 - }, + }, "response": { "status": { - "message": "OK", + "message": "OK", "code": 200 - }, + }, "headers": [ - "access-control-allow-origin: *\r\n", - "content-type: application/json\r\n", - "date: Mon, 21 Apr 2014 23:13:40 GMT\r\n", - "server: gunicorn/0.17.4\r\n", - "content-length: 32\r\n", + "access-control-allow-origin: *\r\n", + "content-type: application/json\r\n", + "date: Mon, 21 Apr 2014 23:13:40 GMT\r\n", + "server: gunicorn/0.17.4\r\n", + "content-length: 32\r\n", "connection: keep-alive\r\n" - ], + ], "body": { "string": "{\n \"origin\": \"217.122.164.194\"\n}" } diff --git a/tests/fixtures/wild/domain_redirect.yaml b/tests/fixtures/wild/domain_redirect.yaml index 618babc..5df9210 100644 --- a/tests/fixtures/wild/domain_redirect.yaml +++ b/tests/fixtures/wild/domain_redirect.yaml @@ -10,7 +10,7 @@ interactions: uri: http://seomoz.org/ response: body: {string: ''} - headers: + headers: Location: ['http://moz.com/'] Server: ['BigIP'] Connection: ['Keep-Alive'] From 97ad51fe6c055b785b88d7de8ba3a5659bddb54e Mon Sep 17 00:00:00 2001 From: Sebastian Pipping Date: Tue, 12 Dec 2023 18:55:55 +0100 Subject: [PATCH 34/35] pre-commit: Enable trailing-whitespace --- .pre-commit-config.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e3165dc..0ecd9b1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,3 +13,4 @@ repos: hooks: - id: check-merge-conflict - id: end-of-file-fixer + - id: trailing-whitespace From b0cb8765d5b45e68839c0d1e50f2f7e4f3053cdc Mon Sep 17 00:00:00 2001 From: Sebastian Pipping Date: Tue, 12 Dec 2023 19:54:29 +0100 Subject: [PATCH 35/35] pre-commit: Add --show-source to ruff Suggested by Jair Henrique. --- .pre-commit-config.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0ecd9b1..9d4273e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,6 +6,7 @@ repos: rev: v0.1.7 hooks: - id: ruff + args: ["--show-source"] - id: ruff-format - repo: https://github.com/pre-commit/pre-commit-hooks