Skip to content

Commit

Permalink
Allow HTTPX stub to read cassettes generated by other stubs.
Browse files Browse the repository at this point in the history
This was due to a custom format being defined in the HTTPX stub.
  • Loading branch information
Allan Crooks authored and jairhenrique committed Jan 23, 2024
1 parent f1e0241 commit 5fa7010
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 10 deletions.
41 changes: 41 additions & 0 deletions tests/integration/cassettes/gzip_httpx_old_format.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
interactions:
- request:
body: ''
headers:
accept:
- '*/*'
accept-encoding:
- gzip, deflate, br
connection:
- keep-alive
host:
- httpbin.org
user-agent:
- python-httpx/0.23.0
method: GET
uri: https://httpbin.org/gzip
response:
content: "{\n \"gzipped\": true, \n \"headers\": {\n \"Accept\": \"*/*\",
\n \"Accept-Encoding\": \"gzip, deflate, br\", \n \"Host\": \"httpbin.org\",
\n \"User-Agent\": \"python-httpx/0.23.0\", \n \"X-Amzn-Trace-Id\": \"Root=1-62a62a8d-5f39b5c50c744da821d6ea99\"\n
\ }, \n \"method\": \"GET\", \n \"origin\": \"146.200.25.115\"\n}\n"
headers:
Access-Control-Allow-Credentials:
- 'true'
Access-Control-Allow-Origin:
- '*'
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Length:
- '230'
Content-Type:
- application/json
Date:
- Sun, 12 Jun 2022 18:03:57 GMT
Server:
- gunicorn/19.9.0
http_version: HTTP/1.1
status_code: 200
version: 1
42 changes: 42 additions & 0 deletions tests/integration/cassettes/gzip_requests.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
interactions:
- request:
body: null
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate, br
Connection:
- keep-alive
User-Agent:
- python-requests/2.28.0
method: GET
uri: https://httpbin.org/gzip
response:
body:
string: !!binary |
H4sIAKwrpmIA/z2OSwrCMBCG956izLIkfQSxkl2RogfQA9R2bIM1iUkqaOndnYDIrGa+/zELDB9l
LfYgg5uRwYhtj86DXKDuOrQBJKR5Cuy38kZ3pld6oHu0sqTH29QGZMnVkepgtMYuKKNJcEe0vJ3U
C4mcjI9hpaiygqaUW7ETFYGLR8frAXXE9h1Go7nD54w++FxkYp8VsDJ4IBH6E47NmVzGqUHFkn8g
rJsvp2omYs8AAAA=
headers:
Access-Control-Allow-Credentials:
- 'true'
Access-Control-Allow-Origin:
- '*'
Connection:
- Close
Content-Encoding:
- gzip
Content-Length:
- '182'
Content-Type:
- application/json
Date:
- Sun, 12 Jun 2022 18:08:44 GMT
Server:
- Pytest-HTTPBIN/0.1.0
status:
code: 200
message: great
version: 1
34 changes: 31 additions & 3 deletions tests/integration/test_httpx.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import pytest

import os
import vcr

asyncio = pytest.importorskip("asyncio")
Expand Down Expand Up @@ -218,8 +218,8 @@ def test_work_with_gzipped_data(httpbin, do_request, yml):
with vcr.use_cassette(yml) as cassette:
cassette_response = do_request(headers=headers)("GET", url)

assert cassette_response.headers["content-encoding"] == "gzip"
assert cassette_response.read()
# Show we can read the content of the cassette.
assert cassette_response.json()['gzipped'] == True
assert cassette.play_count == 1


Expand Down Expand Up @@ -287,6 +287,34 @@ def test_stream(tmpdir, httpbin, do_request):
assert cassette.play_count == 1


# Regular cassette formats support the status reason,
# but the old HTTPX cassette format does not.
@pytest.mark.parametrize(
"cassette_name,reason",
[
("requests", "great"),
("httpx_old_format", "OK"),
],
)
def test_load_gzipped(do_request, cassette_name, reason):
mydir = os.path.dirname(os.path.realpath(__file__))
yml = f"{mydir}/cassettes/gzip_{cassette_name}.yaml"
url = "https://httpbin.org/gzip"

with vcr.use_cassette(yml) as cassette:
cassette_response = do_request()("GET", url)
assert str(cassette_response.request.url) == url
assert cassette.play_count == 1

# Should be able to load up the JSON inside,
# regardless whether the content is the gzipped
# in the cassette or not.
json = cassette_response.json()
assert json["method"] == "GET", json
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"
Expand Down
29 changes: 22 additions & 7 deletions vcr/stubs/httpx_stubs.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

from vcr.errors import CannotOverwriteExistingCassetteException
from vcr.request import Request as VcrRequest
from vcr.filters import decode_response
from vcr.serializers.compat import convert_body_to_bytes

_httpx_signature = inspect.signature(httpx.Client.request)

Expand Down Expand Up @@ -62,17 +64,30 @@ 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")
if isinstance(content, str):
content = content.encode("utf-8")

# HTTPX cassette format.
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()}

response = httpx.Response(
status_code=serialized_response.get("status_code"),
status_code=serialized_response["status"]["code"],
request=request,
headers=_from_serialized_headers(serialized_response.get("headers")),
content=content,
headers=_from_serialized_headers(serialized_response["headers"]),
content=serialized_response["body"]["string"],
history=history or [],
extensions=extensions,
)
response._content = content

return response


Expand Down

0 comments on commit 5fa7010

Please sign in to comment.