]> git.ipfire.org Git - thirdparty/fastapi/fastapi.git/commitdiff
📝 Update docs for responses and new stream with `yield` (#15023)
authorSebastián Ramírez <tiangolo@gmail.com>
Fri, 27 Feb 2026 20:51:40 +0000 (12:51 -0800)
committerGitHub <noreply@github.com>
Fri, 27 Feb 2026 20:51:40 +0000 (21:51 +0100)
docs/en/docs/advanced/custom-response.md
docs/en/docs/advanced/response-directly.md
docs/en/docs/advanced/stream-data.md
docs_src/stream_data/tutorial002_py310.py
tests/test_tutorial/test_stream_data/test_tutorial002.py

index 823ee5ff2c471724a4dd1367e683a7217712d115..e0fafa5dfe3160f0eca2b9bb45df01ab1e315e7a 100644 (file)
@@ -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 <a href="https://docs.python.org/3/glossary.html#term-file-like-object" class="external-link" target="_blank">file-like</a> 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.
 
 ///
 
index 9d58490eb11ec170603f5ca5747ff3a465e32852..dd0e63c396e242b65b11f0c49e9026b6d8221f49 100644 (file)
@@ -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 }
 
index 4bec4edf998fb0145592aced0cf7d7182898afaa..422ade867a62ccb6344364a63aeef563573bff8f 100644 (file)
@@ -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 <a href="https://asyncer.tiangolo.com" class="external-link" target="_blank">Asyncer</a>, 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] *}
index 7fc884fa25abdb4244298f69e3fe8de231941f20..aa8bcee3a925e331f7a2087034267feb5b739df0 100644 (file)
@@ -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
index 83201a7a22317bf0b13f37814d8501bb8dbcde39..8bd7384c5787d283ca716ddde3729d9e9a7e1c64 100644 (file)
@@ -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",