diff --git a/docs/src/encoder/tutorial001.py b/docs/src/encoder/tutorial001.py new file mode 100644 index 000000000..11984dd96 --- /dev/null +++ b/docs/src/encoder/tutorial001.py @@ -0,0 +1,22 @@ +from datetime import datetime + +from fastapi import FastAPI +from fastapi.encoders import jsonable_encoder +from pydantic import BaseModel + +fake_db = {} + + +class Item(BaseModel): + title: str + timestamp: datetime + description: str = None + + +app = FastAPI() + + +@app.put("/items/{id}") +def update_item(id: str, item: Item): + json_compatible_item_data = jsonable_encoder(item) + fake_db[id] = json_compatible_item_data diff --git a/docs/src/response_directly/tutorial001.py b/docs/src/response_directly/tutorial001.py new file mode 100644 index 000000000..92ae0ca37 --- /dev/null +++ b/docs/src/response_directly/tutorial001.py @@ -0,0 +1,21 @@ +from datetime import datetime + +from fastapi import FastAPI +from fastapi.encoders import jsonable_encoder +from pydantic import BaseModel +from starlette.responses import JSONResponse + + +class Item(BaseModel): + title: str + timestamp: datetime + description: str = None + + +app = FastAPI() + + +@app.put("/items/{id}") +def update_item(id: str, item: Item): + json_compatible_item_data = jsonable_encoder(item) + return JSONResponse(content=json_compatible_item_data) diff --git a/docs/src/response_directly/tutorial002.py b/docs/src/response_directly/tutorial002.py new file mode 100644 index 000000000..fe961335e --- /dev/null +++ b/docs/src/response_directly/tutorial002.py @@ -0,0 +1,20 @@ +from fastapi import FastAPI +from starlette.responses import Response + +app = FastAPI() + + +@app.get("/legacy/") +def get_legacy_data(): + data = """ + <?xml version="1.0"?> + <shampoo> + <Header> + Apply shampoo here. + <Header> + <Body> + You'll have to use soap here. + </Body> + </shampoo> + """ + return Response(content=data, media_type="application/xml") diff --git a/docs/tutorial/custom-response.md b/docs/tutorial/custom-response.md index 0f0763992..600033f15 100644 --- a/docs/tutorial/custom-response.md +++ b/docs/tutorial/custom-response.md @@ -1,98 +1,88 @@ !!! warning This is a rather advanced topic. - + If you are starting with **FastAPI**, you might not need this. By default, **FastAPI** will return the responses using Starlette's `JSONResponse`. -But you can override it. +You can override it by returning a `Response` directly, <a href="https://fastapi.tiangolo.com/tutorial/response-directly/" target="_blank">as seen in a previous section</a>. -## Use `UJSONResponse` +But if you return a `Response` directly, the data won't be automatically converted, and the documentation won't be automatically generated (for example, including the specific "media type", in the HTTP header `Content-Type`). -For example, if you are squeezing performance, you can use `ujson` and set the response to be Starlette's `UJSONResponse`. +But you can also declare the `Response` that you want to be used, in the *path operation decorator*. -### Import `UJSONResponse` +The contents that you return from your *path operation function* will be put inside of that `Response`. -```Python hl_lines="2" -{!./src/custom_response/tutorial001.py!} -``` +And if that `Response` has a JSON media type (`application/json`), like is the case with the `JSONResponse` and `UJSONResponse`, the data you return will be automatically converted (and filtered) with any Pydantic `response_model` that you declared in the *path operation decorator*. -!!! note - Notice that you import it directly from `starlette.responses`, not from `fastapi`. +## Use `UJSONResponse` -### Make your path operation use it +For example, if you are squeezing performance, you can install and use `ujson` and set the response to be Starlette's `UJSONResponse`. -Make your path operation use `UJSONResponse` as the response class using the parameter `content_type`: +Import the `Response` class (sub-class) you want to use and declare it in the *path operation decorator*. -```Python hl_lines="7" +```Python hl_lines="2 7" {!./src/custom_response/tutorial001.py!} ``` +!!! note + Notice that you import it directly from `starlette.responses`, not from `fastapi`. + !!! info - The parameter is called `content_type` because it will also be used to define the "media type" of the response. + The parameter `response_class` will also be used to define the "media type" of the response. - And will be documented as such in OpenAPI. + In this case, the HTTP header `Content-Type` will be set to `application/json`. + + And it will be documented as such in OpenAPI. ## HTML Response To return a response with HTML directly from **FastAPI**, use `HTMLResponse`. -### Import `HTMLResponse` +* Import `HTMLResponse`. +* Pass `HTMLResponse` as the parameter `content_type` of your path operation. -```Python hl_lines="2" +```Python hl_lines="2 7" {!./src/custom_response/tutorial002.py!} ``` !!! note Notice that you import it directly from `starlette.responses`, not from `fastapi`. - -### Define your `content_type` class - -Pass `HTMLResponse` as the parameter `content_type` of your path operation: - -```Python hl_lines="7" -{!./src/custom_response/tutorial002.py!} -``` - !!! info - The parameter is called `content_type` because it will also be used to define the "media type" of the response. + The parameter `response_class` will also be used to define the "media type" of the response. In this case, the HTTP header `Content-Type` will be set to `text/html`. And it will be documented as such in OpenAPI. - ### Return a Starlette `Response` -You can also override the response directly in your path operation. - -If you return an object that is an instance of Starlette's `Response`, it will be used as the response directly. +As seen in <a href="https://fastapi.tiangolo.com/tutorial/response-directly/" target="_blank">another section</a>, you can also override the response directly in your path operation, by returning it. The same example from above, returning an `HTMLResponse`, could look like: -```Python hl_lines="7" +```Python hl_lines="2 7 19" {!./src/custom_response/tutorial003.py!} ``` -!!! info - Of course, the `Content-Type` header will come from the the `Response` object your returned. - !!! warning - A `Response` returned directly by your path operation function won't be documented in OpenAPI and won't be visible in the automatic interactive docs. + A `Response` returned directly by your path operation function won't be documented in OpenAPI (for example, the `Content-Type` won't be documented) and won't be visible in the automatic interactive docs. +!!! info + Of course, the actual `Content-Type` header, status code, etc, will come from the `Response` object your returned. ### Document in OpenAPI and override `Response` -If you want to override the response from inside of the function but at the same time document the "media type" in OpenAPI, you can use the `content_type` parameter AND return a `Response` object. +If you want to override the response from inside of the function but at the same time document the "media type" in OpenAPI, you can use the `response_class` parameter AND return a `Response` object. -The `content_type` class will then be used only to document the OpenAPI path operation, but your `Response` will be used as is. +The `response_class` will then be used only to document the OpenAPI path operation, but your `Response` will be used as is. #### Return an `HTMLResponse` directly For example, it could be something like: -```Python hl_lines="7 23" +```Python hl_lines="7 23 21" {!./src/custom_response/tutorial004.py!} ``` @@ -100,16 +90,10 @@ In this example, the function `generate_html_response()` already generates a Sta By returning the result of calling `generate_html_response()`, you are already returning a `Response` that will override the default **FastAPI** behavior. -#### Declare `HTMLResponse` as `content_type` - -But by declaring it also in the path operation decorator: +But as you passed the `HTMLResponse` in the `response_class`, **FastAPI** will know how to document it in OpenAPI and the interactive docs as HTML with `text/html`: -```Python hl_lines="21" -{!./src/custom_response/tutorial004.py!} -``` - -#### OpenAPI knows how to document it +<img src="/img/tutorial/custom-response/image01.png"> -...**FastAPI** will be able to document it in OpenAPI and in the interactive docs as HTML with `text/html`: +## Additional documentation -<img src="/img/tutorial/custom-response/image01.png"> +You can also declare the media type and many other details in OpenAPI using `responses`: <a href="https://fastapi.tiangolo.com/tutorial/additional-responses/" target="_blank">Additional Responses in OpenAPI</a>. diff --git a/docs/tutorial/encoder.md b/docs/tutorial/encoder.md new file mode 100644 index 000000000..41893194e --- /dev/null +++ b/docs/tutorial/encoder.md @@ -0,0 +1,32 @@ +There are some cases where you might need to convert a data type (like a Pydantic model) to something compatible with JSON (like a `dict`, `list`, etc). + +For example, if you need to store it in a database. + +For that, **FastAPI** provides a `jsonable_encoder()` function. + +## Using the `jsonable_encoder` + +Let's imagine that you have a database `fake_db` that only receives JSON compatible data. + +For example, it doesn't receive `datetime` objects, as those are not compatible with JSON. + +So, a `datetime` object would have to be converted to a `str` containing the data in <a href="https://en.wikipedia.org/wiki/ISO_8601" target="_blank">ISO format</a>. + +The same way, this database wouldn't receive a Pydantic model (an object with attributes), only a `dict`. + +You can use `jsonable_encoder` for that. + +It receives an object, like a Pydantic model, and returns a JSON compatible version: + +```Python hl_lines="4 21" +{!./src/encoder/tutorial001.py!} +``` + +In this example, it would convert the Pydantic model to a `dict`, and the `datetime` to a `str`. + +The result of calling it is something that can be encoded with the Python standard <a href="https://docs.python.org/3/library/json.html#json.dumps" target="_blank">`json.dumps()`</a>. + +It doesn't return a large `str` containing the data in JSON format (as a string). It returns a Python standard data structure (e.g. a `dict`) with values and sub-values that are all compatible with JSON. + +!!! note + `jsonable_encoder` is actually used by **FastAPI** internally to convert data. But it is useful in many other scenarios. diff --git a/docs/tutorial/response-directly.md b/docs/tutorial/response-directly.md new file mode 100644 index 000000000..f622cab85 --- /dev/null +++ b/docs/tutorial/response-directly.md @@ -0,0 +1,63 @@ +When you create a **FastAPI** *path operation* you can normally return any data from it: a `dict`, a `list`, a Pydantic model, a database model, etc. + +By default, **FastAPI** would automatically convert that return value to JSON using the <a href="https://fastapi.tiangolo.com/tutorial/encoder/" target="_blank">`jsonable_encoder`</a>. + +Then, behind the scenes, it would put that JSON-compatible data (e.g. a `dict`) inside of a Starlette `JSONResponse` that would be used to send the response to the client. + +But you can return a `JSONResponse` directly from your *path operations*. + +It might be useful, for example, to return custom headers or cookies. + +## Starlette `Response` + +In fact, you can return any <a href="https://www.starlette.io/responses/" target="_blank">Starlette `Response`</a> or any sub-class of it. + +!!! tip + `JSONResponse` itself is a sub-class of `Response`. + +And when you return a Starlette `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. + +## Using the `jsonable_encoder` in a `Response` + +Because **FastAPI** doesn't do any change to a `Response` you return, you have to make sure it's contents are ready for it. + +For example, you cannot put a Pydantic model in a `JSONResponse` without first converting it to a `dict` with all the data types (like `datetime`, `UUID`, etc) converted to JSON-compatible types. + +For those cases, you can use the `jsonable_encoder` to convert your data before passing it to a response: + +```Python hl_lines="4 6 20 21" +{!./src/response_directly/tutorial001.py!} +``` + +!!! note + Notice that you import it directly from `starlette.responses`, not from `fastapi`. + +## Returning a custom `Response` + +The example above shows all the parts you need, but it's not very useful yet, as you could have just returned the `item` directly, and **FastAPI** would put it in a `JSONResponse` for you, converting it to a `dict`, etc. All that by default. + +Now, let's see how you could use that to return a custom response. + +Let's say you want to return a response that is not available in the default <a href="https://www.starlette.io/responses/" target="_blank">Starlette `Response`s</a>. + +Let's say that you want to return <a href="https://en.wikipedia.org/wiki/XML" target="_blank">XML</a>. + +You could put your XML content in a string, put it in a Starlette Response, and return it: + +```Python hl_lines="2 20" +{!./src/response_directly/tutorial002.py!} +``` + +## Notes + +When you return a `Response` directly its data is not validated, converted (serialized), nor documented automatically. + +But you can still <a href="tutorial/additional-responses/" target="_blank">document it</a>. + +In the next sections you will see how to use/declare these custom `Response`s while still having automatic data conversion, documentation, etc. + +You will also see how to use them to set response Headers and Cookies. diff --git a/mkdocs.yml b/mkdocs.yml index 2ea289d8e..b53936384 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -44,7 +44,9 @@ nav: - Path Operation Configuration: 'tutorial/path-operation-configuration.md' - Path Operation Advanced Configuration: 'tutorial/path-operation-advanced-configuration.md' - Additional Status Codes: 'tutorial/additional-status-codes.md' - - Custom Response: 'tutorial/custom-response.md' + - JSON compatible encoder: 'tutorial/encoder.md' + - Return a Response directly: 'tutorial/response-directly.md' + - Custom Response Class: 'tutorial/custom-response.md' - Additional Responses in OpenAPI: 'tutorial/additional-responses.md' - Dependencies: - First Steps: 'tutorial/dependencies/first-steps.md'