Browse Source

📝 Update docs for responses and new stream with `yield` (#15023)

pull/15024/head
Sebastián Ramírez 3 months ago
committed by GitHub
parent
commit
1377052c6c
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 32
      docs/en/docs/advanced/custom-response.md
  2. 12
      docs/en/docs/advanced/response-directly.md
  3. 18
      docs/en/docs/advanced/stream-data.md
  4. 26
      docs_src/stream_data/tutorial002_py310.py
  5. 15
      tests/test_tutorial/test_stream_data/test_tutorial002.py

32
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. 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 } ### `RedirectResponse` { #redirectresponse }
Returns an HTTP redirect. Uses a 307 status code (Temporary Redirect) by default. 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 } ### `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] *} {* ../../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 /// 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.
/// ///

12
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 } ## 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 /// 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. 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 } ## 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 } ## 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] *} {* ../../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. 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 } ## Notes { #notes }

18
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*: 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 } ### 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. 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 /// 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 } ### Files and Async { #files-and-async }
In most cases, file-like objects are not compatible with async and await by default. 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. 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 /// 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. 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] *}

26
docs_src/stream_data/tutorial002_py310.py

@ -22,23 +22,33 @@ class PNGStreamingResponse(StreamingResponse):
@app.get("/image/stream", response_class=PNGStreamingResponse) @app.get("/image/stream", response_class=PNGStreamingResponse)
async def stream_image() -> AsyncIterable[bytes]: async def stream_image() -> AsyncIterable[bytes]:
for chunk in read_image(): with read_image() as image_file:
yield chunk for chunk in image_file:
yield chunk
@app.get("/image/stream-no-async", response_class=PNGStreamingResponse) @app.get("/image/stream-no-async", response_class=PNGStreamingResponse)
def stream_image_no_async() -> Iterable[bytes]: def stream_image_no_async() -> Iterable[bytes]:
for chunk in read_image(): with read_image() as image_file:
yield chunk 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) @app.get("/image/stream-no-annotation", response_class=PNGStreamingResponse)
async def stream_image_no_annotation(): async def stream_image_no_annotation():
for chunk in read_image(): with read_image() as image_file:
yield chunk for chunk in image_file:
yield chunk
@app.get("/image/stream-no-async-no-annotation", response_class=PNGStreamingResponse) @app.get("/image/stream-no-async-no-annotation", response_class=PNGStreamingResponse)
def stream_image_no_async_no_annotation(): def stream_image_no_async_no_annotation():
for chunk in read_image(): with read_image() as image_file:
yield chunk for chunk in image_file:
yield chunk

15
tests/test_tutorial/test_stream_data/test_tutorial002.py

@ -26,6 +26,7 @@ def get_client(mod):
[ [
"/image/stream", "/image/stream",
"/image/stream-no-async", "/image/stream-no-async",
"/image/stream-no-async-yield-from",
"/image/stream-no-annotation", "/image/stream-no-annotation",
"/image/stream-no-async-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": { "/image/stream-no-annotation": {
"get": { "get": {
"summary": "Stream Image No Annotation", "summary": "Stream Image No Annotation",

Loading…
Cancel
Save