From 5cf23298ac6be250c21e37d5bdb018959a2a9cd2 Mon Sep 17 00:00:00 2001 From: Allan Crooks Date: Sat, 20 Jan 2024 17:46:52 +0000 Subject: [PATCH] HTTPX stub now generates cassettes in the same format as other stubs. As part of this, I've removed the tests which inspect the data type of the response content in the cassette. That behaviour should be controlled via the inbuilt serializers. --- tests/integration/test_httpx.py | 27 ---------------------- vcr/stubs/httpx_stubs.py | 40 ++++++++++++++++----------------- 2 files changed, 20 insertions(+), 47 deletions(-) diff --git a/tests/integration/test_httpx.py b/tests/integration/test_httpx.py index 5a03aa2..d3883e2 100644 --- a/tests/integration/test_httpx.py +++ b/tests/integration/test_httpx.py @@ -314,30 +314,3 @@ def test_load_gzipped(do_request, cassette_name, reason): assert cassette_response.status_code == 200 assert cassette_response.reason_phrase == reason - -@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) diff --git a/vcr/stubs/httpx_stubs.py b/vcr/stubs/httpx_stubs.py index 34855c5..477cf95 100644 --- a/vcr/stubs/httpx_stubs.py +++ b/vcr/stubs/httpx_stubs.py @@ -1,3 +1,4 @@ +import asyncio import functools import inspect import logging @@ -35,17 +36,17 @@ 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 +async def _to_serialized_response(resp, aread): + + if aread: + await resp.aread() + else: + resp.read() return { - "status_code": httpx_response.status_code, - "http_version": httpx_response.http_version, - "headers": _transform_headers(httpx_response), - "content": content, + "status": dict(code=resp.status_code, message=resp.reason_phrase), + "headers": _transform_headers(resp), + "body": {"string": resp.content}, } @@ -65,17 +66,16 @@ def _from_serialized_headers(headers): @patch("httpx.Response.read", MagicMock()) def _from_serialized_response(request, serialized_response, history=None): - # HTTPX cassette format. + # Cassette format generated for HTTPX requests by older versions of + # vcrpy. We restructure the content to resemble what a regular + # cassette looks like. if "status_code" in serialized_response: serialized_response = decode_response(convert_body_to_bytes({ 'headers': serialized_response['headers'], 'body': {'string': serialized_response['content']}, 'status': {'code': serialized_response['status_code']}, })) - # We don't store the reason phrase in this format. extensions = None - - # Cassette format that all other stubs use. else: extensions = {"reason_phrase": serialized_response["status"]["message"].encode()} @@ -113,17 +113,17 @@ def _shared_vcr_send(cassette, real_send, *args, **kwargs): return vcr_request, None -def _record_responses(cassette, vcr_request, real_response): +async def _record_responses(cassette, vcr_request, real_response, aread): for past_real_response in real_response.history: past_vcr_request = _make_vcr_request(past_real_response.request) - cassette.append(past_vcr_request, _to_serialized_response(past_real_response)) + cassette.append(past_vcr_request, await _to_serialized_response(past_real_response, aread)) if real_response.history: # If there was a redirection keep we want the request which will hold the # final redirect value vcr_request = _make_vcr_request(real_response.request) - cassette.append(vcr_request, _to_serialized_response(real_response)) + cassette.append(vcr_request, await _to_serialized_response(real_response, aread)) return real_response @@ -141,8 +141,8 @@ 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) + await _record_responses(cassette, vcr_request, real_response, aread=True) + return real_response def async_vcr_send(cassette, real_send): @@ -161,8 +161,8 @@ 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) + asyncio.run(_record_responses(cassette, vcr_request, real_response, aread=False)) + return real_response def sync_vcr_send(cassette, real_send):