diff --git a/docs/en/docs/advanced/custom-response.md b/docs/en/docs/advanced/custom-response.md
index 823ee5ff2c..e0fafa5dfe 100644
--- a/docs/en/docs/advanced/custom-response.md
+++ b/docs/en/docs/advanced/custom-response.md
@@ -138,6 +138,14 @@ Takes some data and returns an `application/json` encoded response.
This is the default response used in **FastAPI**, as you read above.
+/// note | Technical Details
+
+But if you declare a response model or return type, that will be used directly to serialize the data to JSON, and a response with the right media type for JSON will be returned directly, without using the `JSONResponse` class.
+
+This is the ideal way to get the best performance.
+
+///
+
### `RedirectResponse` { #redirectresponse }
Returns an HTTP redirect. Uses a 307 status code (Temporary Redirect) by default.
@@ -165,7 +173,7 @@ You can also use the `status_code` parameter combined with the `response_class`
### `StreamingResponse` { #streamingresponse }
-Takes an async generator or a normal generator/iterator and streams the response body.
+Takes an async generator or a normal generator/iterator (a function with `yield`) and streams the response body.
{* ../../docs_src/custom_response/tutorial007_py310.py hl[3,16] *}
@@ -179,27 +187,11 @@ This would be even more important with large or infinite streams.
///
-#### Using `StreamingResponse` with file-like objects { #using-streamingresponse-with-file-like-objects }
-
-If you have a file-like object (e.g. the object returned by `open()`), you can create a generator function to iterate over that file-like object.
-
-That way, you don't have to read it all first in memory, and you can pass that generator function to the `StreamingResponse`, and return it.
-
-This includes many libraries to interact with cloud storage, video processing, and others.
-
-{* ../../docs_src/custom_response/tutorial008_py310.py hl[2,10:12,14] *}
-
-1. This is the generator function. It's a "generator function" because it contains `yield` statements inside.
-2. By using a `with` block, we make sure that the file-like object is closed after the generator function is done. So, after it finishes sending the response.
-3. This `yield from` tells the function to iterate over that thing named `file_like`. And then, for each part iterated, yield that part as coming from this generator function (`iterfile`).
-
- So, it is a generator function that transfers the "generating" work to something else internally.
-
- By doing it this way, we can put it in a `with` block, and that way, ensure that the file-like object is closed after finishing.
-
/// tip
-Notice that here as we are using standard `open()` that doesn't support `async` and `await`, we declare the path operation with normal `def`.
+Instead of returning a `StreamingResponse` directly, you should probably follow the style in [Stream Data](./stream-data.md){.internal-link target=_blank}, it's much more convenient and handles cancellation behind the scenes for you.
+
+If you are streaming JSON Lines, follow the [Stream JSON Lines](../tutorial/stream-json-lines.md){.internal-link target=_blank} tutorial.
///
diff --git a/docs/en/docs/advanced/response-directly.md b/docs/en/docs/advanced/response-directly.md
index 9d58490eb1..dd0e63c396 100644
--- a/docs/en/docs/advanced/response-directly.md
+++ b/docs/en/docs/advanced/response-directly.md
@@ -16,7 +16,7 @@ You will normally have much better performance using a [Response Model](../tutor
## Return a `Response` { #return-a-response }
-You can return any `Response` or any sub-class of it.
+You can return a `Response` or any sub-class of it.
/// info
@@ -28,7 +28,9 @@ And when you return a `Response`, **FastAPI** will pass it directly.
It won't do any data conversion with Pydantic models, it won't convert the contents to any type, etc.
-This gives you a lot of flexibility. You can return any data type, override any data declaration or validation, etc.
+This gives you a lot of **flexibility**. You can return any data type, override any data declaration or validation, etc.
+
+It also gives you a lot of **responsibility**. You have to make sure that the data you return is correct, in the correct format, that it can be serialized, etc.
## Using the `jsonable_encoder` in a `Response` { #using-the-jsonable-encoder-in-a-response }
@@ -62,15 +64,15 @@ You could put your XML content in a string, put that in a `Response`, and return
## How a Response Model Works { #how-a-response-model-works }
-When you declare a [Response Model](../tutorial/response-model.md){.internal-link target=_blank} in a path operation, **FastAPI** will use it to serialize the data to JSON, using Pydantic.
+When you declare a [Response Model - Return Type](../tutorial/response-model.md){.internal-link target=_blank} in a path operation, **FastAPI** will use it to serialize the data to JSON, using Pydantic.
{* ../../docs_src/response_model/tutorial001_01_py310.py hl[16,21] *}
As that will happen on the Rust side, the performance will be much better than if it was done with regular Python and the `JSONResponse` class.
-When using a response model FastAPI won't use the `jsonable_encoder` to convert the data (which would be slower) nor the `JSONResponse` class.
+When using a `response_model` or return type, FastAPI won't use the `jsonable_encoder` to convert the data (which would be slower) nor the `JSONResponse` class.
-Instead it takes the JSON bytes generated with Pydantic using the response model and returns a `Response` with the right media type for JSON directly (`application/json`).
+Instead it takes the JSON bytes generated with Pydantic using the response model (or return type) and returns a `Response` with the right media type for JSON directly (`application/json`).
## Notes { #notes }
diff --git a/docs/en/docs/advanced/stream-data.md b/docs/en/docs/advanced/stream-data.md
index 4bec4edf99..422ade867a 100644
--- a/docs/en/docs/advanced/stream-data.md
+++ b/docs/en/docs/advanced/stream-data.md
@@ -54,7 +54,7 @@ For example, you can create a `PNGStreamingResponse` that sets the `Content-Type
Then you can use this new class in `response_class=PNGStreamingResponse` in your *path operation function*:
-{* ../../docs_src/stream_data/tutorial002_py310.py ln[23:26] hl[23] *}
+{* ../../docs_src/stream_data/tutorial002_py310.py ln[23:27] hl[23] *}
### Simulate a File { #simulate-a-file }
@@ -62,7 +62,7 @@ In this example, we are simulating a file with `io.BytesIO`, which is a file-lik
For example, we can iterate over it to consume its contents, as we could with a file.
-{* ../../docs_src/stream_data/tutorial002_py310.py ln[1:26] hl[3,12:13,25] *}
+{* ../../docs_src/stream_data/tutorial002_py310.py ln[1:27] hl[3,12:13,25] *}
/// note | Technical Details
@@ -72,6 +72,10 @@ Only so that it can live in the same file for this example and you can copy it a
///
+By using a `with` block, we make sure that the file-like object is closed after the generator function (the function with `yield`) is done. So, after it finishes sending the response.
+
+It wouldn't be that important in this specific example because it's a fake in-memory file (with `io.BytesIO`), but with a real file, it would be important to make sure the file is closed after the work with it is done.
+
### Files and Async { #files-and-async }
In most cases, file-like objects are not compatible with async and await by default.
@@ -90,10 +94,18 @@ But in many cases reading a file or a file-like object would block.
To avoid blocking the event loop, you can simply declare the *path operation function* with regular `def` instead of `async def`, that way FastAPI will run it on a threadpool worker, to avoid blocking the main loop.
-{* ../../docs_src/stream_data/tutorial002_py310.py ln[29:32] hl[30] *}
+{* ../../docs_src/stream_data/tutorial002_py310.py ln[30:34] hl[31] *}
/// tip
If you need to call blocking code from inside of an async function, or an async function from inside of a blocking function, you could use Asyncer, a sibling library to FastAPI.
///
+
+### `yield from` { #yield-from }
+
+When you are iterating over something, like a file-like object, and then you are doing `yield` for each item, you could also use `yield from` to yield each item directly and skip the `for` loop.
+
+This is not particular to FastAPI, it's just Python, but it's a nice trick to know. 😎
+
+{* ../../docs_src/stream_data/tutorial002_py310.py ln[37:40] hl[40] *}
diff --git a/docs_src/stream_data/tutorial002_py310.py b/docs_src/stream_data/tutorial002_py310.py
index 7fc884fa25..aa8bcee3a9 100644
--- a/docs_src/stream_data/tutorial002_py310.py
+++ b/docs_src/stream_data/tutorial002_py310.py
@@ -22,23 +22,33 @@ class PNGStreamingResponse(StreamingResponse):
@app.get("/image/stream", response_class=PNGStreamingResponse)
async def stream_image() -> AsyncIterable[bytes]:
- for chunk in read_image():
- yield chunk
+ with read_image() as image_file:
+ for chunk in image_file:
+ yield chunk
@app.get("/image/stream-no-async", response_class=PNGStreamingResponse)
def stream_image_no_async() -> Iterable[bytes]:
- for chunk in read_image():
- yield chunk
+ with read_image() as image_file:
+ for chunk in image_file:
+ yield chunk
+
+
+@app.get("/image/stream-no-async-yield-from", response_class=PNGStreamingResponse)
+def stream_image_no_async_yield_from() -> Iterable[bytes]:
+ with read_image() as image_file:
+ yield from image_file
@app.get("/image/stream-no-annotation", response_class=PNGStreamingResponse)
async def stream_image_no_annotation():
- for chunk in read_image():
- yield chunk
+ with read_image() as image_file:
+ for chunk in image_file:
+ yield chunk
@app.get("/image/stream-no-async-no-annotation", response_class=PNGStreamingResponse)
def stream_image_no_async_no_annotation():
- for chunk in read_image():
- yield chunk
+ with read_image() as image_file:
+ for chunk in image_file:
+ yield chunk
diff --git a/tests/test_tutorial/test_stream_data/test_tutorial002.py b/tests/test_tutorial/test_stream_data/test_tutorial002.py
index 83201a7a22..8bd7384c57 100644
--- a/tests/test_tutorial/test_stream_data/test_tutorial002.py
+++ b/tests/test_tutorial/test_stream_data/test_tutorial002.py
@@ -26,6 +26,7 @@ def get_client(mod):
[
"/image/stream",
"/image/stream-no-async",
+ "/image/stream-no-async-yield-from",
"/image/stream-no-annotation",
"/image/stream-no-async-no-annotation",
],
@@ -73,6 +74,20 @@ def test_openapi_schema(client: TestClient):
},
}
},
+ "/image/stream-no-async-yield-from": {
+ "get": {
+ "summary": "Stream Image No Async Yield From",
+ "operationId": "stream_image_no_async_yield_from_image_stream_no_async_yield_from_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "image/png": {"schema": {"type": "string"}}
+ },
+ }
+ },
+ }
+ },
"/image/stream-no-annotation": {
"get": {
"summary": "Stream Image No Annotation",