From 3b003350f4195935aded8f0809b7fda1fe700662 Mon Sep 17 00:00:00 2001 From: florimondmanca Date: Sun, 1 Mar 2020 23:59:28 +0100 Subject: [PATCH 1/2] Document cleaning up streaming responses without block usage --- docs/async.md | 30 +++++++++++++++++++++++++++++- docs/quickstart.md | 21 +++++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/docs/async.md b/docs/async.md index 0e83ebf6a1..2c76819782 100644 --- a/docs/async.md +++ b/docs/async.md @@ -78,7 +78,35 @@ The async response streaming methods are: * `Response.aiter_text()` - For streaming the response content as text. * `Response.aiter_lines()` - For streaming the response content as lines of text. * `Response.aiter_raw()` - For streaming the raw response bytes, without applying content decoding. -* `Response.aclose()` - For closing the response. You don't usually need this, since `.stream` block close the response automatically on exit. +* `Response.aclose()` - For closing the response. When using the context-managed syntax, `.stream()` will close the response automatically on exit. + +If you need to return the body as an async iterator without block usage, you can manually enter the `.stream()` async context, and make sure it gets closed eventually. For example, if building a Starlette web view that returns a streaming response, this can be achieved using `AsyncExitStack` and a background task: + +```python +from contextlib import AsyncExitStack +import httpx +from starlette.applications import Starlette +from starlette.routing import Route +from starlette.background import BackgroundTask +from starlette.responses import StreamingResponse + +client = httpx.AsyncClient() + +async def home(request): + data = await request.json() + url = data["url"] + + exit_stack = AsyncExitStack() + r = await exit_stack.enter_async_context(client.stream("GET", url)) + + return StreamingResponse( + r.aiter_raw(), + media_type="application/octet-stream", + background=BackgroundTask(exit_stack.aclose), + ) + +app = Starlette(routes=[Route("/", home, methods=["POST"])]) +``` ### Streaming requests diff --git a/docs/quickstart.md b/docs/quickstart.md index a147b04608..0bd1c64206 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -337,6 +337,27 @@ If you're using streaming responses in any of these ways then the `response.cont ... print(r.text) ``` +If you need to return the body as an iterator without block usage, you can manually enter the `.stream()` context and make sure it gets closed eventually. For example, if building a Flask web view that returns a streaming response, this can be achieved using `ExitStack` and werkzeug's `ClosingIterator`: + +```python +from contextlib import ExitStack +import httpx +from flask import Flask, Response, request +from werkzeug.wsgi import ClosingIterator + +app = Flask(__name__) + +@app.route("/", methods=["post"]) +def home() -> Response: + url = request.json["url"] + + exit_stack = ExitStack() + r = exit_stack.enter_context(httpx.stream("GET", url)) + body = ClosingIterator(r.iter_raw(), callbacks=[exit_stack.close]) + + return Response(body, mimetype="application/octet-stream") +``` + ## Cookies Any cookies that are set on the response can be easily accessed: From b86a908519046ffde8982cd31fe33d76e7bb97f4 Mon Sep 17 00:00:00 2001 From: florimondmanca Date: Mon, 2 Mar 2020 00:03:55 +0100 Subject: [PATCH 2/2] Add cross-link in Requests compatibility guide --- docs/compatibility.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/compatibility.md b/docs/compatibility.md index 89235b1262..a8bad4e57b 100644 --- a/docs/compatibility.md +++ b/docs/compatibility.md @@ -22,7 +22,7 @@ HTTPX provides a `.stream()` interface rather than using `stream=True`. This ens For example: ```python -with request.stream("GET", "https://www.example.com") as response: +with httpx.stream("GET", "https://www.example.com") as response: ... ``` @@ -34,6 +34,8 @@ Within a `stream()` block request data is made available with: * `.iter_raw()` - Use this instead of `response.raw` * `.read()` - Read the entire response body, making `request.text` and `response.content` available. +For more information, see [Streaming Responses](/quickstart#streaming-responses). + ## SSL configuration When using a `Client` instance, the `trust_env`, `verify`, and `cert` arguments should always be passed on client instantiation, rather than passed to the request method.