mirror of
https://github.com/fastapi/fastapi.git
synced 2026-02-27 20:29:48 -05:00
📝 Update docs for responses and new stream with yield (#15023)
This commit is contained in:
committed by
GitHub
parent
c0836dc1b7
commit
1377052c6c
@@ -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.
|
||||
|
||||
///
|
||||
|
||||
|
||||
@@ -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 }
|
||||
|
||||
|
||||
@@ -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] *}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user