httpx/tests/client/test_event_hooks.py
Michiel W. Beijen 392dbe45f0
Add support for zstd decoding (#3139)
This adds support for zstd decoding using the python package zstandard.
This is similar to how it is implemented in urllib3. I also chose the
optional installation option httpx[zstd] to mimic the same option in
urllib3.

zstd decoding is similar to brotli, but in benchmarks it is supposed to
be even faster. The zstd compression is described in RFC 8878.

See https://github.com/encode/httpx/discussions/1986

Co-authored-by: Kamil Monicz <kamil@monicz.dev>
2024-03-21 10:17:15 +00:00

229 lines
6.9 KiB
Python

import pytest
import httpx
def app(request: httpx.Request) -> httpx.Response:
if request.url.path == "/redirect":
return httpx.Response(303, headers={"server": "testserver", "location": "/"})
elif request.url.path.startswith("/status/"):
status_code = int(request.url.path[-3:])
return httpx.Response(status_code, headers={"server": "testserver"})
return httpx.Response(200, headers={"server": "testserver"})
def test_event_hooks():
events = []
def on_request(request):
events.append({"event": "request", "headers": dict(request.headers)})
def on_response(response):
events.append({"event": "response", "headers": dict(response.headers)})
event_hooks = {"request": [on_request], "response": [on_response]}
with httpx.Client(
event_hooks=event_hooks, transport=httpx.MockTransport(app)
) as http:
http.get("http://127.0.0.1:8000/", auth=("username", "password"))
assert events == [
{
"event": "request",
"headers": {
"host": "127.0.0.1:8000",
"user-agent": f"python-httpx/{httpx.__version__}",
"accept": "*/*",
"accept-encoding": "gzip, deflate, br, zstd",
"connection": "keep-alive",
"authorization": "Basic dXNlcm5hbWU6cGFzc3dvcmQ=",
},
},
{
"event": "response",
"headers": {"server": "testserver"},
},
]
def test_event_hooks_raising_exception(server):
def raise_on_4xx_5xx(response):
response.raise_for_status()
event_hooks = {"response": [raise_on_4xx_5xx]}
with httpx.Client(
event_hooks=event_hooks, transport=httpx.MockTransport(app)
) as http:
try:
http.get("http://127.0.0.1:8000/status/400")
except httpx.HTTPStatusError as exc:
assert exc.response.is_closed
@pytest.mark.anyio
async def test_async_event_hooks():
events = []
async def on_request(request):
events.append({"event": "request", "headers": dict(request.headers)})
async def on_response(response):
events.append({"event": "response", "headers": dict(response.headers)})
event_hooks = {"request": [on_request], "response": [on_response]}
async with httpx.AsyncClient(
event_hooks=event_hooks, transport=httpx.MockTransport(app)
) as http:
await http.get("http://127.0.0.1:8000/", auth=("username", "password"))
assert events == [
{
"event": "request",
"headers": {
"host": "127.0.0.1:8000",
"user-agent": f"python-httpx/{httpx.__version__}",
"accept": "*/*",
"accept-encoding": "gzip, deflate, br, zstd",
"connection": "keep-alive",
"authorization": "Basic dXNlcm5hbWU6cGFzc3dvcmQ=",
},
},
{
"event": "response",
"headers": {"server": "testserver"},
},
]
@pytest.mark.anyio
async def test_async_event_hooks_raising_exception():
async def raise_on_4xx_5xx(response):
response.raise_for_status()
event_hooks = {"response": [raise_on_4xx_5xx]}
async with httpx.AsyncClient(
event_hooks=event_hooks, transport=httpx.MockTransport(app)
) as http:
try:
await http.get("http://127.0.0.1:8000/status/400")
except httpx.HTTPStatusError as exc:
assert exc.response.is_closed
def test_event_hooks_with_redirect():
"""
A redirect request should trigger additional 'request' and 'response' event hooks.
"""
events = []
def on_request(request):
events.append({"event": "request", "headers": dict(request.headers)})
def on_response(response):
events.append({"event": "response", "headers": dict(response.headers)})
event_hooks = {"request": [on_request], "response": [on_response]}
with httpx.Client(
event_hooks=event_hooks,
transport=httpx.MockTransport(app),
follow_redirects=True,
) as http:
http.get("http://127.0.0.1:8000/redirect", auth=("username", "password"))
assert events == [
{
"event": "request",
"headers": {
"host": "127.0.0.1:8000",
"user-agent": f"python-httpx/{httpx.__version__}",
"accept": "*/*",
"accept-encoding": "gzip, deflate, br, zstd",
"connection": "keep-alive",
"authorization": "Basic dXNlcm5hbWU6cGFzc3dvcmQ=",
},
},
{
"event": "response",
"headers": {"location": "/", "server": "testserver"},
},
{
"event": "request",
"headers": {
"host": "127.0.0.1:8000",
"user-agent": f"python-httpx/{httpx.__version__}",
"accept": "*/*",
"accept-encoding": "gzip, deflate, br, zstd",
"connection": "keep-alive",
"authorization": "Basic dXNlcm5hbWU6cGFzc3dvcmQ=",
},
},
{
"event": "response",
"headers": {"server": "testserver"},
},
]
@pytest.mark.anyio
async def test_async_event_hooks_with_redirect():
"""
A redirect request should trigger additional 'request' and 'response' event hooks.
"""
events = []
async def on_request(request):
events.append({"event": "request", "headers": dict(request.headers)})
async def on_response(response):
events.append({"event": "response", "headers": dict(response.headers)})
event_hooks = {"request": [on_request], "response": [on_response]}
async with httpx.AsyncClient(
event_hooks=event_hooks,
transport=httpx.MockTransport(app),
follow_redirects=True,
) as http:
await http.get("http://127.0.0.1:8000/redirect", auth=("username", "password"))
assert events == [
{
"event": "request",
"headers": {
"host": "127.0.0.1:8000",
"user-agent": f"python-httpx/{httpx.__version__}",
"accept": "*/*",
"accept-encoding": "gzip, deflate, br, zstd",
"connection": "keep-alive",
"authorization": "Basic dXNlcm5hbWU6cGFzc3dvcmQ=",
},
},
{
"event": "response",
"headers": {"location": "/", "server": "testserver"},
},
{
"event": "request",
"headers": {
"host": "127.0.0.1:8000",
"user-agent": f"python-httpx/{httpx.__version__}",
"accept": "*/*",
"accept-encoding": "gzip, deflate, br, zstd",
"connection": "keep-alive",
"authorization": "Basic dXNlcm5hbWU6cGFzc3dvcmQ=",
},
},
{
"event": "response",
"headers": {"server": "testserver"},
},
]