mirror of
https://github.com/fastapi/fastapi.git
synced 2026-04-16 04:53:11 -04:00
📝 Refactor docs, "Tutorial - User Guide" and "Advanced User Guide" (#887)
This commit is contained in:
committed by
GitHub
parent
a41a729682
commit
22982287ff
@@ -1,238 +0,0 @@
|
||||
!!! warning
|
||||
This is a rather advanced topic.
|
||||
|
||||
If you are starting with **FastAPI**, you might not need this.
|
||||
|
||||
You can declare additional responses, with additional status codes, media types, descriptions, etc.
|
||||
|
||||
Those additional responses will be included in the OpenAPI schema, so they will also appear in the API docs.
|
||||
|
||||
But for those additional responses you have to make sure you return a `Response` like `JSONResponse` directly, with your status code and content.
|
||||
|
||||
## Additional Response with `model`
|
||||
|
||||
You can pass to your *path operation decorators* a parameter `responses`.
|
||||
|
||||
It receives a `dict`, the keys are status codes for each response, like `200`, and the values are other `dict`s with the information for each of them.
|
||||
|
||||
Each of those response `dict`s can have a key `model`, containing a Pydantic model, just like `response_model`.
|
||||
|
||||
**FastAPI** will take that model, generate its JSON Schema and include it in the correct place in OpenAPI.
|
||||
|
||||
For example, to declare another response with a status code `404` and a Pydantic model `Message`, you can write:
|
||||
|
||||
```Python hl_lines="18 23"
|
||||
{!./src/additional_responses/tutorial001.py!}
|
||||
```
|
||||
|
||||
!!! note
|
||||
Have in mind that you have to return the `JSONResponse` directly.
|
||||
|
||||
!!! info
|
||||
The `model` key is not part of OpenAPI.
|
||||
|
||||
**FastAPI** will take the Pydantic model from there, generate the `JSON Schema`, and put it in the correct place.
|
||||
|
||||
The correct place is:
|
||||
|
||||
* In the key `content`, that has as value another JSON object (`dict`) that contains:
|
||||
* A key with the media type, e.g. `application/json`, that contains as value another JSON object, that contains:
|
||||
* A key `schema`, that has as the value the JSON Schema from the model, here's the correct place.
|
||||
* **FastAPI** adds a reference here to the global JSON Schemas in another place in your OpenAPI instead of including it directly. This way, other applications and clients can use those JSON Schemas directly, provide better code generation tools, etc.
|
||||
|
||||
The generated responses in the OpenAPI for this *path operation* will be:
|
||||
|
||||
```JSON hl_lines="3 4 5 6 7 8 9 10 11 12"
|
||||
{
|
||||
"responses": {
|
||||
"404": {
|
||||
"description": "Additional Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Message"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Item"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The schemas are referenced to another place inside the OpenAPI schema:
|
||||
|
||||
```JSON hl_lines="4 5 6 7 8 9 10 11 12 13 14 15 16"
|
||||
{
|
||||
"components": {
|
||||
"schemas": {
|
||||
"Message": {
|
||||
"title": "Message",
|
||||
"required": [
|
||||
"message"
|
||||
],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"title": "Message",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Item": {
|
||||
"title": "Item",
|
||||
"required": [
|
||||
"id",
|
||||
"value"
|
||||
],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"title": "Id",
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"title": "Value",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ValidationError": {
|
||||
"title": "ValidationError",
|
||||
"required": [
|
||||
"loc",
|
||||
"msg",
|
||||
"type"
|
||||
],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"loc": {
|
||||
"title": "Location",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"msg": {
|
||||
"title": "Message",
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"title": "Error Type",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/ValidationError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Additional media types for the main response
|
||||
|
||||
You can use this same `responses` parameter to add different media types for the same main response.
|
||||
|
||||
For example, you can add an additional media type of `image/png`, declaring that your *path operation* can return a JSON object (with media type `application/json`) or a PNG image:
|
||||
|
||||
```Python hl_lines="17 18 19 20 21 22 23 24 28"
|
||||
{!./src/additional_responses/tutorial002.py!}
|
||||
```
|
||||
|
||||
!!! note
|
||||
Notice that you have to return the image using a `FileResponse` directly.
|
||||
|
||||
!!! info
|
||||
Unless you specify a different media type explicitly in your `responses` parameter, FastAPI will assume the response has the same media type as the main response class (default `application/json`).
|
||||
|
||||
But if you have specified a custom response class with `None` as its media type, FastAPI will use `application/json` for any additional response that has an associated model.
|
||||
|
||||
## Combining information
|
||||
|
||||
You can also combine response information from multiple places, including the `response_model`, `status_code`, and `responses` parameters.
|
||||
|
||||
You can declare a `response_model`, using the default status code `200` (or a custom one if you need), and then declare additional information for that same response in `responses`, directly in the OpenAPI schema.
|
||||
|
||||
**FastAPI** will keep the additional information from `responses`, and combine it with the JSON Schema from your model.
|
||||
|
||||
For example, you can declare a response with a status code `404` that uses a Pydantic model and has a custom `description`.
|
||||
|
||||
And a response with a status code `200` that uses your `response_model`, but includes a custom `example`:
|
||||
|
||||
```Python hl_lines="20 21 22 23 24 25 26 27 28 29 30 31"
|
||||
{!./src/additional_responses/tutorial003.py!}
|
||||
```
|
||||
|
||||
It will all be combined and included in your OpenAPI, and shown in the API docs:
|
||||
|
||||
<img src="/img/tutorial/additional-responses/image01.png">
|
||||
|
||||
## Combine predefined responses and custom ones
|
||||
|
||||
You might want to have some predefined responses that apply to many *path operations*, but you want to combine them with custom responses needed by each *path operation*.
|
||||
|
||||
For those cases, you can use the Python technique of "unpacking" a `dict` with `**dict_to_unpack`:
|
||||
|
||||
```Python
|
||||
old_dict = {
|
||||
"old key": "old value",
|
||||
"second old key": "second old value",
|
||||
}
|
||||
new_dict = {**old_dict, "new key": "new value"}
|
||||
```
|
||||
|
||||
Here, `new_dict` will contain all the key-value pairs from `old_dict` plus the new key-value pair:
|
||||
|
||||
```Python
|
||||
{
|
||||
"old key": "old value",
|
||||
"second old key": "second old value",
|
||||
"new key": "new value",
|
||||
}
|
||||
```
|
||||
|
||||
You can use that technique to re-use some predefined responses in your *path operations* and combine them with additional custom ones.
|
||||
|
||||
For example:
|
||||
|
||||
```Python hl_lines="11 12 13 14 15 24"
|
||||
{!./src/additional_responses/tutorial004.py!}
|
||||
```
|
||||
|
||||
## More information about OpenAPI responses
|
||||
|
||||
To see what exactly you can include in the responses, you can check these sections in the OpenAPI specification:
|
||||
|
||||
* <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#responsesObject" class="external-link" target="_blank">OpenAPI Responses Object</a>, it includes the `Response Object`.
|
||||
* <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#responseObject" class="external-link" target="_blank">OpenAPI Response Object</a>, you can include anything from this directly in each response inside your `responses` parameter. Including `description`, `headers`, `content` (inside of this is that you declare different media types and JSON Schemas), and `links`.
|
||||
@@ -1,30 +0,0 @@
|
||||
By default, **FastAPI** will return the responses using Starlette's `JSONResponse`, putting the content you return from your *path operation* inside of that `JSONResponse`.
|
||||
|
||||
It will use the default status code or the one you set in your *path operation*.
|
||||
|
||||
## Additional status codes
|
||||
|
||||
If you want to return additional status codes apart from the main one, you can do that by returning a `Response` directly, like a `JSONResponse`, and set the additional status code directly.
|
||||
|
||||
For example, let's say that you want to have a *path operation* that allows to update items, and returns HTTP status codes of 200 "OK" when successful.
|
||||
|
||||
But you also want it to accept new items. And when the items didn't exist before, it creates them, and returns an HTTP status code of 201 "Created".
|
||||
|
||||
To achieve that, import `JSONResponse`, and return your content there directly, setting the `status_code` that you want:
|
||||
|
||||
```Python hl_lines="2 20"
|
||||
{!./src/additional_status_codes/tutorial001.py!}
|
||||
```
|
||||
|
||||
!!! warning
|
||||
When you return a `Response` directly, like in the example above, it will be returned directly.
|
||||
|
||||
It won't be serialized with a model, etc.
|
||||
|
||||
Make sure it has the data you want it to have, and that the values are valid JSON (if you are using `JSONResponse`).
|
||||
|
||||
## OpenAPI and API docs
|
||||
|
||||
If you return additional status codes and responses directly, they won't be included in the OpenAPI schema (the API docs), because FastAPI doesn't have a way to know before hand what you are going to return.
|
||||
|
||||
But you can document that in your code, using: <a href="https://fastapi.tiangolo.com/tutorial/additional-responses/" target="_blank">Additional Responses</a>.
|
||||
@@ -1,160 +0,0 @@
|
||||
You can also use <a href="https://github.com/encode/databases" class="external-link" target="_blank">`encode/databases`</a> with **FastAPI** to connect to databases using `async` and `await`.
|
||||
|
||||
It is compatible with:
|
||||
|
||||
* PostgreSQL
|
||||
* MySQL
|
||||
* SQLite
|
||||
|
||||
In this example, we'll use **SQLite**, because it uses a single file and Python has integrated support. So, you can copy this example and run it as is.
|
||||
|
||||
Later, for your production application, you might want to use a database server like **PostgreSQL**.
|
||||
|
||||
!!! tip
|
||||
You could adopt ideas from the previous section about <a href="/tutorial/sql-databases/" target="_blank">SQLAlchemy ORM</a>, like using utility functions to perform operations in the database, independent of your **FastAPI** code.
|
||||
|
||||
This section doesn't apply those ideas, to be equivalent to the counterpart in <a href="https://www.starlette.io/database/" class="external-link" target="_blank">Starlette</a>.
|
||||
|
||||
## Import and set up `SQLAlchemy`
|
||||
|
||||
* Import `SQLAlchemy`.
|
||||
* Create a `metadata` object.
|
||||
* Create a table `notes` using the `metadata` object.
|
||||
|
||||
```Python hl_lines="4 14 16 17 18 19 20 21 22"
|
||||
{!./src/async_sql_databases/tutorial001.py!}
|
||||
```
|
||||
|
||||
!!! tip
|
||||
Notice that all this code is pure SQLAlchemy Core.
|
||||
|
||||
`databases` is not doing anything here yet.
|
||||
|
||||
## Import and set up `databases`
|
||||
|
||||
* Import `databases`.
|
||||
* Create a `DATABASE_URL`.
|
||||
* Create a `database` object.
|
||||
|
||||
```Python hl_lines="3 9 12"
|
||||
{!./src/async_sql_databases/tutorial001.py!}
|
||||
```
|
||||
|
||||
!!! tip
|
||||
If you were connecting to a different database (e.g. PostgreSQL), you would need to change the `DATABASE_URL`.
|
||||
|
||||
## Create the tables
|
||||
|
||||
In this case, we are creating the tables in the same Python file, but in production, you would probably want to create them with Alembic, integrated with migrations, etc.
|
||||
|
||||
Here, this section would run directly, right before starting your **FastAPI** application.
|
||||
|
||||
* Create an `engine`.
|
||||
* Create all the tables from the `metadata` object.
|
||||
|
||||
```Python hl_lines="25 26 27 28"
|
||||
{!./src/async_sql_databases/tutorial001.py!}
|
||||
```
|
||||
|
||||
## Create models
|
||||
|
||||
Create Pydantic models for:
|
||||
|
||||
* Notes to be created (`NoteIn`).
|
||||
* Notes to be returned (`Note`).
|
||||
|
||||
```Python hl_lines="31 32 33 36 37 38 39"
|
||||
{!./src/async_sql_databases/tutorial001.py!}
|
||||
```
|
||||
|
||||
By creating these Pydantic models, the input data will be validated, serialized (converted), and annotated (documented).
|
||||
|
||||
So, you will be able to see it all in the interactive API docs.
|
||||
|
||||
## Connect and disconnect
|
||||
|
||||
* Create your `FastAPI` application.
|
||||
* Create event handlers to connect and disconnect from the database.
|
||||
|
||||
```Python hl_lines="42 45 46 47 50 51 52"
|
||||
{!./src/async_sql_databases/tutorial001.py!}
|
||||
```
|
||||
|
||||
## Read notes
|
||||
|
||||
Create the *path operation function* to read notes:
|
||||
|
||||
```Python hl_lines="55 56 57 58"
|
||||
{!./src/async_sql_databases/tutorial001.py!}
|
||||
```
|
||||
|
||||
!!! Note
|
||||
Notice that as we communicate with the database using `await`, the *path operation function* is declared with `async`.
|
||||
|
||||
### Notice the `response_model=List[Note]`
|
||||
|
||||
It uses `typing.List`.
|
||||
|
||||
That documents (and validates, serializes, filters) the output data, as a `list` of `Note`s.
|
||||
|
||||
## Create notes
|
||||
|
||||
Create the *path operation function* to create notes:
|
||||
|
||||
```Python hl_lines="61 62 63 64 65"
|
||||
{!./src/async_sql_databases/tutorial001.py!}
|
||||
```
|
||||
|
||||
!!! Note
|
||||
Notice that as we communicate with the database using `await`, the *path operation function* is declared with `async`.
|
||||
|
||||
### About `{**note.dict(), "id": last_record_id}`
|
||||
|
||||
`note` is a Pydantic `Note` object.
|
||||
|
||||
`note.dict()` returns a `dict` with its data, something like:
|
||||
|
||||
```Python
|
||||
{
|
||||
"text": "Some note",
|
||||
"completed": False,
|
||||
}
|
||||
```
|
||||
|
||||
but it doesn't have the `id` field.
|
||||
|
||||
So we create a new `dict`, that contains the key-value pairs from `note.dict()` with:
|
||||
|
||||
```Python
|
||||
{**note.dict()}
|
||||
```
|
||||
|
||||
`**note.dict()` "unpacks" the key value pairs directly, so, `{**note.dict()}` would be, more or less, a copy of `note.dict()`.
|
||||
|
||||
And then, we extend that copy `dict`, adding another key-value pair: `"id": last_record_id`:
|
||||
|
||||
```Python
|
||||
{**note.dict(), "id": last_record_id}
|
||||
```
|
||||
|
||||
So, the final result returned would be something like:
|
||||
|
||||
```Python
|
||||
{
|
||||
"id": 1,
|
||||
"text": "Some note",
|
||||
"completed": False,
|
||||
}
|
||||
```
|
||||
|
||||
## Check it
|
||||
|
||||
You can copy this code as is, and see the docs at <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
|
||||
|
||||
There you can see all your API documented and interact with it:
|
||||
|
||||
<img src="/img/tutorial/async-sql-databases/image01.png">
|
||||
|
||||
## More info
|
||||
|
||||
You can read more about <a href="https://github.com/encode/databases" class="external-link" target="_blank">`encode/databases` at its GitHub page</a>.
|
||||
@@ -19,9 +19,6 @@ First, import `BackgroundTasks` and define a parameter in your *path operation f
|
||||
|
||||
**FastAPI** will create the object of type `BackgroundTasks` for you and pass it as that parameter.
|
||||
|
||||
!!! tip
|
||||
You declare a parameter of `BackgroundTasks` and use it in a very similar way as to when <a href="/tutorial/using-request-directly/" target="_blank">using the `Request` directly</a>.
|
||||
|
||||
## Create a task function
|
||||
|
||||
Create a function to be run as the background task.
|
||||
@@ -86,7 +83,7 @@ If you need to perform heavy background computation and you don't necessarily ne
|
||||
|
||||
They tend to require more complex configurations, a message/job queue manager, like RabbitMQ or Redis, but they allow you to run background tasks in multiple processes, and especially, in multiple servers.
|
||||
|
||||
To see an example, check the <a href="https://fastapi.tiangolo.com/project-generation/" target="_blank">Project Generators</a>, they all include Celery already configured.
|
||||
To see an example, check the [Project Generators](../project-generation.md){.internal-link target=_blank}, they all include Celery already configured.
|
||||
|
||||
But if you need to access variables and objects from the same **FastAPI** app, or you need to perform small background tasks (like sending an email notification), you can simply just use `BackgroundTasks`.
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ You import it and create an "instance" the same way you would with the class `Fa
|
||||
{!./src/bigger_applications/app/routers/users.py!}
|
||||
```
|
||||
|
||||
### Path operations with `APIRouter`
|
||||
### *Path operations* with `APIRouter`
|
||||
|
||||
And then you use it to declare your *path operations*.
|
||||
|
||||
@@ -87,7 +87,7 @@ We are going to include this `APIrouter` in the main `FastAPI` app, but first, l
|
||||
|
||||
Let's say you also have the endpoints dedicated to handling "Items" from your application in the module at `app/routers/items.py`.
|
||||
|
||||
You have path operations for:
|
||||
You have *path operations* for:
|
||||
|
||||
* `/items/`
|
||||
* `/items/{item_id}`
|
||||
@@ -223,9 +223,9 @@ Now, let's include the router from the `items` submodule.
|
||||
|
||||
But, remember that we were lazy and didn't add `/items/` nor `tags` to all the *path operations*?
|
||||
|
||||
We can add a prefix to all the path operations using the parameter `prefix` of `app.include_router()`.
|
||||
We can add a prefix to all the *path operations* using the parameter `prefix` of `app.include_router()`.
|
||||
|
||||
As the path of each path operation has to start with `/`, like in:
|
||||
As the path of each *path operation* has to start with `/`, like in:
|
||||
|
||||
```Python hl_lines="1"
|
||||
@router.get("/{item_id}")
|
||||
@@ -261,8 +261,8 @@ The end result is that the item paths are now:
|
||||
* The *path operation* that declared a custom `403` response will have both the predefined responses (`404`) and the `403` declared in it directly.
|
||||
* All these *path operations* will have the list of `dependencies` evaluated/executed before them.
|
||||
* If you also declare dependencies in a specific *path operation*, **they will be executed too**.
|
||||
* The router dependencies are executed first, then the <a href="https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-decorator/" target="_blank">`dependencies` in the decorator</a>, and then the normal parameter dependencies.
|
||||
* You can also add <a href="https://fastapi.tiangolo.com/tutorial/security/oauth2-scopes/" target="_blank">`Security` dependencies with `scopes`</a>.
|
||||
* The router dependencies are executed first, then the [`dependencies` in the decorator](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}, and then the normal parameter dependencies.
|
||||
* You can also add [`Security` dependencies with `scopes`](../advanced/security/oauth2-scopes.md){.internal-link target=_blank}.
|
||||
|
||||
!!! tip
|
||||
Having `dependencies` in a decorator can be used, for example, to require authentication for a whole group of *path operations*. Even if the dependencies are not added individually to each one of them.
|
||||
@@ -271,7 +271,7 @@ The end result is that the item paths are now:
|
||||
The `prefix`, `tags`, `responses` and `dependencies` parameters are (as in many other cases) just a feature from **FastAPI** to help you avoid code duplication.
|
||||
|
||||
!!! tip
|
||||
You could also add path operations directly, for example with: `@app.get(...)`.
|
||||
You could also add *path operations* directly, for example with: `@app.get(...)`.
|
||||
|
||||
Apart from `app.include_router()`, in the same **FastAPI** app.
|
||||
|
||||
@@ -284,9 +284,9 @@ The end result is that the item paths are now:
|
||||
|
||||
The `APIRouter`s are not "mounted", they are not isolated from the rest of the application.
|
||||
|
||||
This is because we want to include their path operations in the OpenAPI schema and the user interfaces.
|
||||
This is because we want to include their *path operations* in the OpenAPI schema and the user interfaces.
|
||||
|
||||
As we cannot just isolate them and "mount" them independently of the rest, the path operations are "cloned" (re-created), not included directly.
|
||||
As we cannot just isolate them and "mount" them independently of the rest, the *path operations* are "cloned" (re-created), not included directly.
|
||||
|
||||
## Check the automatic API docs
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
The same way you can declare additional validation and metadata in path operation function parameters with `Query`, `Path` and `Body`, you can declare validation and metadata inside of Pydantic models using Pydantic's `Field`.
|
||||
The same way you can declare additional validation and metadata in *path operation function* parameters with `Query`, `Path` and `Body`, you can declare validation and metadata inside of Pydantic models using Pydantic's `Field`.
|
||||
|
||||
## Import `Field`
|
||||
|
||||
@@ -28,10 +28,10 @@ You can then use `Field` with model attributes:
|
||||
|
||||
`Body` also returns objects of a subclass of `FieldInfo` directly. And there are others you will see later that are subclasses of the `Body` class.
|
||||
|
||||
Remember that when you import `Query`, `Path`, and others from `fastapi`, <a href="https://fastapi.tiangolo.com/tutorial/path-params-numeric-validations/#recap" target="_blank">those are actually functions that return classes of the same name</a>.
|
||||
Remember that when you import `Query`, `Path`, and others from `fastapi`, those are actually functions that return special classes.
|
||||
|
||||
!!! tip
|
||||
Notice how each model's attribute with a type, default value and `Field` has the same structure as a path operation function's parameter, with `Field` instead of `Path`, `Query` and `Body`.
|
||||
Notice how each model's attribute with a type, default value and `Field` has the same structure as a *path operation function's* parameter, with `Field` instead of `Path`, `Query` and `Body`.
|
||||
|
||||
## JSON Schema extras
|
||||
|
||||
|
||||
@@ -13,10 +13,9 @@ And you can also declare body parameters as optional, by setting the default to
|
||||
!!! note
|
||||
Notice that, in this case, the `item` that would be taken from the body is optional. As it has a `None` default value.
|
||||
|
||||
|
||||
## Multiple body parameters
|
||||
|
||||
In the previous example, the path operations would expect a JSON body with the attributes of an `Item`, like:
|
||||
In the previous example, the *path operations* would expect a JSON body with the attributes of an `Item`, like:
|
||||
|
||||
```JSON
|
||||
{
|
||||
@@ -160,9 +159,9 @@ instead of:
|
||||
|
||||
## Recap
|
||||
|
||||
You can add multiple body parameters to your path operation function, even though a request can only have a single body.
|
||||
You can add multiple body parameters to your *path operation function*, even though a request can only have a single body.
|
||||
|
||||
But **FastAPI** will handle it, give you the correct data in your function, and validate and document the correct schema in the path operation.
|
||||
But **FastAPI** will handle it, give you the correct data in your function, and validate and document the correct schema in the *path operation*.
|
||||
|
||||
You can also declare singular values to be received as part of the body.
|
||||
|
||||
|
||||
@@ -116,7 +116,7 @@ Again, doing just that declaration, with **FastAPI** you get:
|
||||
|
||||
Apart from normal singular types like `str`, `int`, `float`, etc. You can use more complex singular types that inherit from `str`.
|
||||
|
||||
To see all the options you have, checkout the docs for <a href="https://pydantic-docs.helpmanual.io/#exotic-types" class="external-link" target="_blank">Pydantic's exotic types</a>. You will see some examples in the next chapter.
|
||||
To see all the options you have, checkout the docs for <a href="https://pydantic-docs.helpmanual.io/usage/types/" class="external-link" target="_blank">Pydantic's exotic types</a>. You will see some examples in the next chapter.
|
||||
|
||||
For example, as in the `Image` model we have a `url` field, we can declare it to be instead of a `str`, a Pydantic's `HttpUrl`:
|
||||
|
||||
|
||||
@@ -94,4 +94,4 @@ In summary, to apply partial updates you would:
|
||||
|
||||
So, if you want to receive partial updates that can omit all the attributes, you need to have a model with all the attributes marked as optional (with default values or `None`).
|
||||
|
||||
To distinguish from the models with all optional values for **updates** and models with required values for **creation**, you can use the ideas described in <a href="https://fastapi.tiangolo.com/tutorial/extra-models/" target="_blank">Extra Models</a>.
|
||||
To distinguish from the models with all optional values for **updates** and models with required values for **creation**, you can use the ideas described in [Extra Models](extra-models.md){.internal-link target=_blank}.
|
||||
|
||||
@@ -53,7 +53,7 @@ For example, this model above declares a JSON "`object`" (or Python `dict`) like
|
||||
|
||||
## Declare it as a parameter
|
||||
|
||||
To add it to your path operation, declare it the same way you declared path and query parameters:
|
||||
To add it to your *path operation*, declare it the same way you declared path and query parameters:
|
||||
|
||||
```Python hl_lines="16"
|
||||
{!./src/body/tutorial001.py!}
|
||||
@@ -80,7 +80,7 @@ The JSON Schemas of your models will be part of your OpenAPI generated schema, a
|
||||
|
||||
<img src="/img/tutorial/body/image01.png">
|
||||
|
||||
And will be also used in the API docs inside each path operation that needs them:
|
||||
And will be also used in the API docs inside each *path operation* that needs them:
|
||||
|
||||
<img src="/img/tutorial/body/image02.png">
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ The first value is the default value, you can pass all the extra validation or a
|
||||
!!! note "Technical Details"
|
||||
`Cookie` is a "sister" class of `Path` and `Query`. It also inherits from the same common `Param` class.
|
||||
|
||||
But remember that when you import `Query`, `Path`, `Cookie` and others from `fastapi`, <a href="https://fastapi.tiangolo.com/tutorial/path-params-numeric-validations/#recap" target="_blank">those are actually functions that return classes of the same name</a>.
|
||||
But remember that when you import `Query`, `Path`, `Cookie` and others from `fastapi`, those are actually functions that return special classes.
|
||||
|
||||
!!! info
|
||||
To declare cookies, you need to use `Cookie`, because otherwise the parameters would be interpreted as query parameters.
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
In some cases, you may want to override the logic used by the `Request` and `APIRoute` classes.
|
||||
|
||||
In particular, this may be a good alternative to logic in a middleware.
|
||||
|
||||
For example, if you want to read or manipulate the request body before it is processed by your application.
|
||||
|
||||
!!! danger
|
||||
This is an "advanced" feature.
|
||||
|
||||
If you are just starting with **FastAPI** you might want to skip this section.
|
||||
|
||||
## Use cases
|
||||
|
||||
Some use cases include:
|
||||
|
||||
* Converting non-JSON request bodies to JSON (e.g. <a href="https://msgpack.org/index.html" class="external-link" target="_blank">`msgpack`</a>).
|
||||
* Decompressing gzip-compressed request bodies.
|
||||
* Automatically logging all request bodies.
|
||||
|
||||
## Handling custom request body encodings
|
||||
|
||||
Let's see how to make use of a custom `Request` subclass to decompress gzip requests.
|
||||
|
||||
And an `APIRoute` subclass to use that custom request class.
|
||||
|
||||
### Create a custom `GzipRequest` class
|
||||
|
||||
First, we create a `GzipRequest` class, which will overwrite the `Request.body()` method to decompress the body in the presence of an appropriate header.
|
||||
|
||||
If there's no `gzip` in the header, it will not try to decompress the body.
|
||||
|
||||
That way, the same route class can handle gzip compressed or uncompressed requests.
|
||||
|
||||
```Python hl_lines="10 11 12 13 14 15 16 17"
|
||||
{!./src/custom_request_and_route/tutorial001.py!}
|
||||
```
|
||||
|
||||
### Create a custom `GzipRoute` class
|
||||
|
||||
Next, we create a custom subclass of `fastapi.routing.APIRoute` that will make use of the `GzipRequest`.
|
||||
|
||||
This time, it will overwrite the method `APIRoute.get_route_handler()`.
|
||||
|
||||
This method returns a function. And that function is what will receive a request and return a response.
|
||||
|
||||
Here we use it to create a `GzipRequest` from the original request.
|
||||
|
||||
```Python hl_lines="20 21 22 23 24 25 26 27 28"
|
||||
{!./src/custom_request_and_route/tutorial001.py!}
|
||||
```
|
||||
|
||||
!!! note "Technical Details"
|
||||
A `Request` has a `request.scope` attribute, that's just a Python `dict` containing the metadata related to the request.
|
||||
|
||||
A `Request` also has a `request.receive`, that's a function to "receive" the body of the request.
|
||||
|
||||
The `scope` `dict` and `receive` function are both part of the ASGI specification.
|
||||
|
||||
And those two things, `scope` and `receive`, are what is needed to create a new `Request` instance.
|
||||
|
||||
To learn more about the `Request` check <a href="https://www.starlette.io/requests/" class="external-link" target="_blank">Starlette's docs about Requests</a>.
|
||||
|
||||
The only thing the function returned by `GzipRequest.get_route_handler` does differently is convert the `Request` to a `GzipRequest`.
|
||||
|
||||
Doing this, our `GzipRequest` will take care of decompressing the data (if necessary) before passing it to our *path operations*.
|
||||
|
||||
After that, all of the processing logic is the same.
|
||||
|
||||
But because of our changes in `GzipRequest.body`, the request body will be automatically decompressed when it is loaded by **FastAPI** when needed.
|
||||
|
||||
## Accessing the request body in an exception handler
|
||||
|
||||
!!! tip
|
||||
To solve this same problem, it's probably a lot easier to [use the `body` in a custom handler for `RequestValidationError`](https://fastapi.tiangolo.com/tutorial/handling-errors/#use-the-requestvalidationerror-body).
|
||||
|
||||
But this example is still valid and it shows how to interact with the internal components.
|
||||
|
||||
We can also use this same approach to access the request body in an exception handler.
|
||||
|
||||
All we need to do is handle the request inside a `try`/`except` block:
|
||||
|
||||
```Python hl_lines="15 17"
|
||||
{!./src/custom_request_and_route/tutorial002.py!}
|
||||
```
|
||||
|
||||
If an exception occurs, the`Request` instance will still be in scope, so we can read and make use of the request body when handling the error:
|
||||
|
||||
```Python hl_lines="18 19 20"
|
||||
{!./src/custom_request_and_route/tutorial002.py!}
|
||||
```
|
||||
|
||||
## Custom `APIRoute` class in a router
|
||||
|
||||
You can also set the `route_class` parameter of an `APIRouter`:
|
||||
|
||||
```Python hl_lines="28"
|
||||
{!./src/custom_request_and_route/tutorial003.py!}
|
||||
```
|
||||
|
||||
In this example, the *path operations* under the `router` will use the custom `TimedRoute` class, and will have an extra `X-Response-Time` header in the response with the time it took to generate the response:
|
||||
|
||||
```Python hl_lines="15 16 17 18 19 20 21 22"
|
||||
{!./src/custom_request_and_route/tutorial003.py!}
|
||||
```
|
||||
@@ -1,102 +0,0 @@
|
||||
!!! 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`.
|
||||
|
||||
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>.
|
||||
|
||||
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`).
|
||||
|
||||
But you can also declare the `Response` that you want to be used, in the *path operation decorator*.
|
||||
|
||||
The contents that you return from your *path operation function* will be put inside of that `Response`.
|
||||
|
||||
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
|
||||
If you use a response class with no media type, FastAPI will expect your response to have no content, so it will not document the response format in its generated OpenAPI docs.
|
||||
|
||||
## Use `UJSONResponse`
|
||||
|
||||
For example, if you are squeezing performance, you can install and use `ujson` and set the response to be Starlette's `UJSONResponse`.
|
||||
|
||||
Import the `Response` class (sub-class) you want to use and declare it in the *path operation decorator*.
|
||||
|
||||
```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 `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 `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`.
|
||||
* Pass `HTMLResponse` as the parameter `content_type` of your path operation.
|
||||
|
||||
```Python hl_lines="2 7"
|
||||
{!./src/custom_response/tutorial002.py!}
|
||||
```
|
||||
|
||||
!!! note
|
||||
Notice that you import it directly from `starlette.responses`, not from `fastapi`.
|
||||
|
||||
!!! info
|
||||
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`
|
||||
|
||||
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="2 7 19"
|
||||
{!./src/custom_response/tutorial003.py!}
|
||||
```
|
||||
|
||||
!!! warning
|
||||
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 `response_class` parameter AND return a `Response` object.
|
||||
|
||||
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 21"
|
||||
{!./src/custom_response/tutorial004.py!}
|
||||
```
|
||||
|
||||
In this example, the function `generate_html_response()` already generates a Starlette `Response` instead of the HTML in a `str`.
|
||||
|
||||
By returning the result of calling `generate_html_response()`, you are already returning a `Response` that will override the default **FastAPI** behavior.
|
||||
|
||||
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`:
|
||||
|
||||
<img src="/img/tutorial/custom-response/image01.png">
|
||||
|
||||
## Additional documentation
|
||||
|
||||
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>.
|
||||
@@ -1,73 +0,0 @@
|
||||
!!! warning
|
||||
This is, more or less, an "advanced" chapter.
|
||||
|
||||
If you are just starting with **FastAPI** you might want to skip this chapter and come back to it later.
|
||||
|
||||
## Parameterized dependencies
|
||||
|
||||
All the dependencies we have seen are a fixed function or class.
|
||||
|
||||
But there could be cases where you want to be able to set parameters on the dependency, without having to declare many different functions or classes.
|
||||
|
||||
Let's imagine that we want to have a dependency that checks if the query parameter `q` contains some fixed content.
|
||||
|
||||
But we want to be able to parameterize that fixed content.
|
||||
|
||||
## A "callable" instance
|
||||
|
||||
In Python there's a way to make an instance of a class a "callable".
|
||||
|
||||
Not the class itself (which is already a callable), but an instance of that class.
|
||||
|
||||
To do that, we declare a method `__call__`:
|
||||
|
||||
```Python hl_lines="10"
|
||||
{!./src/dependencies/tutorial011.py!}
|
||||
```
|
||||
|
||||
In this case, this `__call__` is what **FastAPI** will use to check for additional parameters and sub-dependencies, and this is what will be called to pass a value to the parameter in your *path operation function* later.
|
||||
|
||||
## Parameterize the instance
|
||||
|
||||
And now, we can use `__init__` to declare the parameters of the instance that we can use to "parameterize" the dependency:
|
||||
|
||||
```Python hl_lines="7"
|
||||
{!./src/dependencies/tutorial011.py!}
|
||||
```
|
||||
|
||||
In this case, **FastAPI** won't ever touch or care about `__init__`, we will use it directly in our code.
|
||||
|
||||
## Create an instance
|
||||
|
||||
We could create an instance of this class with:
|
||||
|
||||
```Python hl_lines="16"
|
||||
{!./src/dependencies/tutorial011.py!}
|
||||
```
|
||||
|
||||
And that way we are able to "parameterize" our dependency, that now has `"bar"` inside of it, as the attribute `checker.fixed_content`.
|
||||
|
||||
## Use the instance as a dependency
|
||||
|
||||
Then, we could use this `checker` in a `Depends(checker)`, instead of `Depends(FixedContentQueryChecker)`, because the dependency is the instance, `checker`, not the class itself.
|
||||
|
||||
And when solving the dependency, **FastAPI** will call this `checker` like:
|
||||
|
||||
```Python
|
||||
checker(q="somequery")
|
||||
```
|
||||
|
||||
...and pass whatever that returns as the value of the dependency in our path operation function as the parameter `fixed_content_included`:
|
||||
|
||||
```Python hl_lines="20"
|
||||
{!./src/dependencies/tutorial011.py!}
|
||||
```
|
||||
|
||||
!!! tip
|
||||
All this might seem contrived. And it might not be very clear how is it useful yet.
|
||||
|
||||
These examples are intentionally simple, but show how it all works.
|
||||
|
||||
In the chapters about security, you will be using utility functions that are implemented in this same way.
|
||||
|
||||
If you understood all this, you already know how those utility tools for security work underneath.
|
||||
@@ -2,13 +2,13 @@ Before diving deeper into the **Dependency Injection** system, let's upgrade the
|
||||
|
||||
## A `dict` from the previous example
|
||||
|
||||
In the previous example, we are returning a `dict` from our dependency ("dependable"):
|
||||
In the previous example, we were returning a `dict` from our dependency ("dependable"):
|
||||
|
||||
```Python hl_lines="7"
|
||||
{!./src/dependencies/tutorial001.py!}
|
||||
```
|
||||
|
||||
But then we get a `dict` in the parameter `commons` of the path operation function.
|
||||
But then we get a `dict` in the parameter `commons` of the *path operation function*.
|
||||
|
||||
And we know that editors can't provide a lot of support (like completion) for `dict`s, because they can't know their keys and value types.
|
||||
|
||||
@@ -63,9 +63,9 @@ Then, in **FastAPI**, you could use a Python class as a dependency.
|
||||
|
||||
What FastAPI actually checks is that it is a "callable" (function, class or anything else) and the parameters defined.
|
||||
|
||||
If you pass a "callable" as a dependency in **FastAPI**, it will analyze the parameters for that "callable", and process them in the same way as the parameters for a path operation function. Including sub-dependencies.
|
||||
If you pass a "callable" as a dependency in **FastAPI**, it will analyze the parameters for that "callable", and process them in the same way as the parameters for a *path operation function*. Including sub-dependencies.
|
||||
|
||||
That also applies to callables with no parameters at all. The same as it would be for path operation functions with no parameters.
|
||||
That also applies to callables with no parameters at all. The same as it would be for *path operation functions* with no parameters.
|
||||
|
||||
Then, we can change the dependency "dependable" `common_parameters` from above to the class `CommonQueryParameters`:
|
||||
|
||||
@@ -173,7 +173,7 @@ commons: CommonQueryParams = Depends(CommonQueryParams)
|
||||
commons: CommonQueryParams = Depends()
|
||||
```
|
||||
|
||||
So, you can declare the dependency as the type of the variable, and use `Depends()` as the "default" value, without any parameter, instead of having to write the full class *again* inside of `Depends(CommonQueryParams)`.
|
||||
So, you can declare the dependency as the type of the variable, and use `Depends()` as the "default" value (the value after the `=`) for that function's parameter, without any parameter, instead of having to write the full class *again* inside of `Depends(CommonQueryParams)`.
|
||||
|
||||
So, the same example would look like:
|
||||
|
||||
|
||||
@@ -57,4 +57,4 @@ So, you can re-use a normal dependency (that returns a value) you already use so
|
||||
|
||||
## Dependencies for a group of *path operations*
|
||||
|
||||
Later, when reading about how to <a href="https://fastapi.tiangolo.com/tutorial/bigger-applications/" target="_blank">structure bigger applications</a>, possibly with multiple files, you will learn how to declare a single `dependencies` parameter for a group of *path operations*.
|
||||
Later, when reading about how to structure bigger applications ([Bigger Applications - Multiple Files](../../tutorial/bigger-applications.md){.internal-link target=_blank}), possibly with multiple files, you will learn how to declare a single `dependencies` parameter for a group of *path operations*.
|
||||
|
||||
@@ -10,11 +10,11 @@ To do this, use `yield` instead of `return`, and write the extra steps after.
|
||||
!!! info
|
||||
For this to work, you need to use **Python 3.7** or above, or in **Python 3.6**, install the "backports":
|
||||
|
||||
```bash
|
||||
```
|
||||
pip install async-exit-stack async-generator
|
||||
```
|
||||
|
||||
This installs <a href="https://github.com/sorcio/async_exit_stack" class="external-link" target="_blank">async-exit-stack</a> and <a href="https://github.com/python-trio/async_generator" target="_blank">async-generator</a>.
|
||||
This installs <a href="https://github.com/sorcio/async_exit_stack" class="external-link" target="_blank">async-exit-stack</a> and <a href="https://github.com/python-trio/async_generator" class="external-link" target="_blank">async-generator</a>.
|
||||
|
||||
!!! note "Technical Details"
|
||||
Any function that is valid to use with:
|
||||
@@ -116,7 +116,7 @@ with open("./somefile.txt") as f:
|
||||
print(contents)
|
||||
```
|
||||
|
||||
Underneath, the `open("./somefile.txt")` returns an object that is a called a "Context Manager".
|
||||
Underneath, the `open("./somefile.txt")` creates an object that is a called a "Context Manager".
|
||||
|
||||
When the `with` block finishes, it makes sure to close the file, even if there were exceptions.
|
||||
|
||||
@@ -129,9 +129,9 @@ When you create a dependency with `yield`, **FastAPI** will internally convert i
|
||||
|
||||
If you are just starting with **FastAPI** you might want to skip it for now.
|
||||
|
||||
In Python, you can create context managers by <a href="https://docs.python.org/3/reference/datamodel.html#context-managers" class="external-link" target="_blank">creating a class with two methods: `__enter__()` and `__exit__()`</a>.
|
||||
In Python, you can create Context Managers by <a href="https://docs.python.org/3/reference/datamodel.html#context-managers" class="external-link" target="_blank">creating a class with two methods: `__enter__()` and `__exit__()`</a>.
|
||||
|
||||
You can also use them with **FastAPI** dependencies with `yield` by using
|
||||
You can also use them inside of **FastAPI** dependencies with `yield` by using
|
||||
`with` or `async with` statements inside of the dependency function:
|
||||
|
||||
```Python hl_lines="1 2 3 4 5 6 7 8 9 13"
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
It is designed to be very simple to use, and to make it very easy for any developer to integrate other components with **FastAPI**.
|
||||
|
||||
## "Dependency Injection"?
|
||||
## What is "Dependency Injection"
|
||||
|
||||
**"Dependency Injection"** means, in programming, that there is a way for your code (in this case, your path operation functions) to declare things that it requires to work and use: "dependencies".
|
||||
**"Dependency Injection"** means, in programming, that there is a way for your code (in this case, your *path operation functions*) to declare things that it requires to work and use: "dependencies".
|
||||
|
||||
And then, that system (in this case **FastAPI**) will take care of doing whatever is needed to provide your code with those needed dependencies ("inject" the dependencies).
|
||||
|
||||
@@ -13,7 +13,7 @@ This is very useful when you need to:
|
||||
* Have shared logic (the same code logic again and again).
|
||||
* Share database connections.
|
||||
* Enforce security, authentication, role requirements, etc.
|
||||
* etc.
|
||||
* And many other things...
|
||||
|
||||
All these, while minimizing code repetition.
|
||||
|
||||
@@ -27,7 +27,7 @@ But this way we can focus on how the **Dependency Injection** system works.
|
||||
|
||||
Let's first focus on the dependency.
|
||||
|
||||
It is just a function that can take all the same parameters that a path operation function can take:
|
||||
It is just a function that can take all the same parameters that a *path operation function* can take:
|
||||
|
||||
```Python hl_lines="6 7"
|
||||
{!./src/dependencies/tutorial001.py!}
|
||||
@@ -37,9 +37,9 @@ That's it.
|
||||
|
||||
**2 lines**.
|
||||
|
||||
And it has the same shape and structure that all your path operation functions.
|
||||
And it has the same shape and structure that all your *path operation functions*.
|
||||
|
||||
You can think of it as a path operation function without the "decorator" (without the `@app.get("/some-path")`).
|
||||
You can think of it as a *path operation function* without the "decorator" (without the `@app.get("/some-path")`).
|
||||
|
||||
And it can return anything you want.
|
||||
|
||||
@@ -59,7 +59,7 @@ And then it just returns a `dict` containing those values.
|
||||
|
||||
### Declare the dependency, in the "dependant"
|
||||
|
||||
The same way you use `Body`, `Query`, etc. with your path operation function parameters, use `Depends` with a new parameter:
|
||||
The same way you use `Body`, `Query`, etc. with your *path operation function* parameters, use `Depends` with a new parameter:
|
||||
|
||||
```Python hl_lines="11 16"
|
||||
{!./src/dependencies/tutorial001.py!}
|
||||
@@ -71,7 +71,7 @@ You only give `Depends` a single parameter.
|
||||
|
||||
This parameter must be something like a function.
|
||||
|
||||
And that function takes parameters in the same way that path operation functions do.
|
||||
And that function takes parameters in the same way that *path operation functions* do.
|
||||
|
||||
!!! tip
|
||||
You'll see what other "things", apart from functions, can be used as dependencies in the next chapter.
|
||||
@@ -80,26 +80,25 @@ Whenever a new request arrives, **FastAPI** will take care of:
|
||||
|
||||
* Calling your dependency ("dependable") function with the correct parameters.
|
||||
* Get the result from your function.
|
||||
* Assign that result to the parameter in your path operation function.
|
||||
* Assign that result to the parameter in your *path operation function*.
|
||||
|
||||
!!! note
|
||||
!!! check
|
||||
Notice that you don't have to create a special class and pass it somewhere to **FastAPI** to "register" it or anything similar.
|
||||
|
||||
You just pass it to `Depends` and **FastAPI** knows how to do the rest.
|
||||
|
||||
## To `async` or not to `async`
|
||||
|
||||
As dependencies will also be called by **FastAPI** (the same as your path operation functions), the same rules apply while defining your functions.
|
||||
As dependencies will also be called by **FastAPI** (the same as your *path operation functions*), the same rules apply while defining your functions.
|
||||
|
||||
You can use `async def` or normal `def`.
|
||||
|
||||
And you can declare dependencies with `async def` inside of normal `def` path operation functions, or `def` dependencies inside of `async def` path operation functions, etc.
|
||||
And you can declare dependencies with `async def` inside of normal `def` *path operation functions*, or `def` dependencies inside of `async def` *path operation functions*, etc.
|
||||
|
||||
It doesn't matter. **FastAPI** will know what to do.
|
||||
|
||||
!!! note
|
||||
If you don't know, check the _"In a hurry?"_ section about <a href="https://fastapi.tiangolo.com/async/#in-a-hurry" target="_blank">`async` and `await` in the docs</a>.
|
||||
|
||||
If you don't know, check the [Async: *"In a hurry?"*](../../async.md){.internal-link target=_blank} section about `async` and `await` in the docs.
|
||||
|
||||
## Integrated with OpenAPI
|
||||
|
||||
@@ -118,7 +117,7 @@ Actually, all (or most) of the web frameworks work in this same way.
|
||||
|
||||
You never call those functions directly. They are called by your framework (in this case, **FastAPI**).
|
||||
|
||||
With the Dependency Injection system, you can also tell **FastAPI** that your path operation function also "depends" on something else that should be executed before your *path operation function*, and **FastAPI** will take care of executing it and "injecting" the results.
|
||||
With the Dependency Injection system, you can also tell **FastAPI** that your *path operation function* also "depends" on something else that should be executed before your *path operation function*, and **FastAPI** will take care of executing it and "injecting" the results.
|
||||
|
||||
Other common terms for this same idea of "dependency injection" are:
|
||||
|
||||
@@ -130,9 +129,9 @@ Other common terms for this same idea of "dependency injection" are:
|
||||
|
||||
## **FastAPI** plug-ins
|
||||
|
||||
Integrations and "plug-in"s can be built using the **Dependency Injection** system. But in fact, there is actually **no need to create "plug-ins"**, as by using dependencies it's possible to declare an infinite number of integrations and interactions that become available to your path operation functions.
|
||||
Integrations and "plug-in"s can be built using the **Dependency Injection** system. But in fact, there is actually **no need to create "plug-ins"**, as by using dependencies it's possible to declare an infinite number of integrations and interactions that become available to your *path operation functions*.
|
||||
|
||||
And dependencies can be created in a very simple and intuitive way that allow you to just import the Python packages you need, and integrate them with your API functions in a couple of lines of code, _literally_.
|
||||
And dependencies can be created in a very simple and intuitive way that allow you to just import the Python packages you need, and integrate them with your API functions in a couple of lines of code, *literally*.
|
||||
|
||||
You will see examples of this in the next chapters, about relational and NoSQL databases, security, etc.
|
||||
|
||||
@@ -159,6 +158,6 @@ In the end, a hierarchical tree of dependencies is built, and the **Dependency I
|
||||
|
||||
## Integrated with **OpenAPI**
|
||||
|
||||
All these dependencies, while declaring their requirements, add parameters, validations, etc. to your path operations.
|
||||
All these dependencies, while declaring their requirements, also add parameters, validations, etc. to your *path operations*.
|
||||
|
||||
**FastAPI** will take care of adding it all to the OpenAPI schema, so that it is shown in the interactive documentation systems.
|
||||
@@ -1,6 +1,6 @@
|
||||
You can create dependencies that have sub-dependencies.
|
||||
You can create dependencies that have **sub-dependencies**.
|
||||
|
||||
They can be as "deep" as you need them to be.
|
||||
They can be as **deep** as you need them to be.
|
||||
|
||||
**FastAPI** will take care of solving them.
|
||||
|
||||
@@ -40,7 +40,7 @@ Then we can use the dependency with:
|
||||
```
|
||||
|
||||
!!! info
|
||||
Notice that we are only declaring one dependency in the path operation function, the `query_or_cookie_extractor`.
|
||||
Notice that we are only declaring one dependency in the *path operation function*, the `query_or_cookie_extractor`.
|
||||
|
||||
But **FastAPI** will know that it has to solve `query_extractor` first, to pass the results of that to `query_or_cookie_extractor` while calling it.
|
||||
|
||||
@@ -61,7 +61,7 @@ async def needy_dependency(fresh_value: str = Depends(get_value, use_cache=False
|
||||
|
||||
Apart from all the fancy words used here, the **Dependency Injection** system is quite simple.
|
||||
|
||||
Just functions that look the same as the path operation functions.
|
||||
Just functions that look the same as the *path operation functions*.
|
||||
|
||||
But still, it is very powerful, and allows you to declare arbitrarily deeply nested dependency "graphs" (trees).
|
||||
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
|
||||
You can define event handlers (functions) that need to be executed before the application starts up, or when the application is shutting down.
|
||||
|
||||
These functions can be declared with `async def` or normal `def`.
|
||||
|
||||
## `startup` event
|
||||
|
||||
To add a function that should be run before the application starts, declare it with the event `"startup"`:
|
||||
|
||||
```Python hl_lines="8"
|
||||
{!./src/events/tutorial001.py!}
|
||||
```
|
||||
|
||||
In this case, the `startup` event handler function will initialize the items "database" (just a `dict`) with some values.
|
||||
|
||||
You can add more than one event handler function.
|
||||
|
||||
And your application won't start receiving requests until all the `startup` event handlers have completed.
|
||||
|
||||
## `shutdown` event
|
||||
|
||||
To add a function that should be run when the application is shutting down, declare it with the event `"shutdown"`:
|
||||
|
||||
```Python hl_lines="6"
|
||||
{!./src/events/tutorial002.py!}
|
||||
```
|
||||
|
||||
Here, the `shutdown` event handler function will write a text line `"Application shutdown"` to a file `log.txt`.
|
||||
|
||||
!!! info
|
||||
In the `open()` function, the `mode="a"` means "append", so, the line will be added after whatever is on that file, without overwriting the previous contents.
|
||||
|
||||
!!! tip
|
||||
Notice that in this case we are using a standard Python `open()` function that interacts with a file.
|
||||
|
||||
So, it involves I/O (input/output), that requires "waiting" for things to be written to disk.
|
||||
|
||||
But `open()` doesn't use `async` and `await`.
|
||||
|
||||
So, we declare the event handler function with standard `def` instead of `async def`.
|
||||
|
||||
!!! info
|
||||
You can read more about these event handlers in <a href="https://www.starlette.io/events/" class="external-link" target="_blank">Starlette's Events' docs</a>.
|
||||
@@ -1,242 +0,0 @@
|
||||
!!! warning
|
||||
This is a rather advanced feature. You probably can skip it.
|
||||
|
||||
If you are just following the tutorial - user guide, you can probably skip this section.
|
||||
|
||||
If you already know that you need to modify the generated OpenAPI schema, continue reading.
|
||||
|
||||
There are some cases where you might need to modify the generated OpenAPI schema.
|
||||
|
||||
In this section you will see how.
|
||||
|
||||
## The normal process
|
||||
|
||||
The normal (default) process, is as follows.
|
||||
|
||||
A `FastAPI` application (instance) has an `.openapi()` method that is expected to return the OpenAPI schema.
|
||||
|
||||
As part of the application object creation, a *path operation* for `/openapi.json` (or for whatever you set your `openapi_url`) is registered.
|
||||
|
||||
It just returns a JSON response with the result of the application's `.openapi()` method.
|
||||
|
||||
By default, what the method `.openapi()` does is check the property `.openapi_schema` to see if it has contents and return them.
|
||||
|
||||
If it doesn't, it generates them using the utility function at `fastapi.openapi.utils.get_openapi`.
|
||||
|
||||
And that function `get_openapi()` receives as parameters:
|
||||
|
||||
* `title`: The OpenAPI title, shown in the docs.
|
||||
* `version`: The version of your API, e.g. `2.5.0`.
|
||||
* `openapi_version`: The version of the OpenAPI specification used. By default, the latest: `3.0.2`.
|
||||
* `description`: The description of your API.
|
||||
* `routes`: A list of routes, these are each of the registered *path operations*. They are taken from `app.routes`.
|
||||
* `openapi_prefix`: The URL prefix to be used in your OpenAPI.
|
||||
|
||||
## Overriding the defaults
|
||||
|
||||
Using the information above, you can use the same utility function to generate the OpenAPI schema and override each part that you need.
|
||||
|
||||
For example, let's add <a href="https://github.com/Rebilly/ReDoc/blob/master/docs/redoc-vendor-extensions.md#x-logo" class="external-link" target="_blank">ReDoc's OpenAPI extension to include a custom logo</a>.
|
||||
|
||||
### Normal **FastAPI**
|
||||
|
||||
First, write all your **FastAPI** application as normally:
|
||||
|
||||
```Python hl_lines="1 4 7 8 9"
|
||||
{!./src/extending_openapi/tutorial001.py!}
|
||||
```
|
||||
|
||||
### Generate the OpenAPI schema
|
||||
|
||||
Then, use the same utility function to generate the OpenAPI schema, inside a `custom_openapi()` function:
|
||||
|
||||
```Python hl_lines="2 15 16 17 18 19 20"
|
||||
{!./src/extending_openapi/tutorial001.py!}
|
||||
```
|
||||
|
||||
### Modify the OpenAPI schema
|
||||
|
||||
Now you can add the ReDoc extension, adding a custom `x-logo` to the `info` "object" in the OpenAPI schema:
|
||||
|
||||
```Python hl_lines="21 22 23"
|
||||
{!./src/extending_openapi/tutorial001.py!}
|
||||
```
|
||||
|
||||
### Cache the OpenAPI schema
|
||||
|
||||
You can use the property `.openapi_schema` as a "cache", to store your generated schema.
|
||||
|
||||
That way, your application won't have to generate the schema every time a user opens your API docs.
|
||||
|
||||
It will be generated only once, and then the same cached schema will be used for the next requests.
|
||||
|
||||
```Python hl_lines="13 14 24 25"
|
||||
{!./src/extending_openapi/tutorial001.py!}
|
||||
```
|
||||
|
||||
### Override the method
|
||||
|
||||
Now you can replace the `.openapi()` method with your new function.
|
||||
|
||||
```Python hl_lines="28"
|
||||
{!./src/extending_openapi/tutorial001.py!}
|
||||
```
|
||||
|
||||
### Check it
|
||||
|
||||
Once you go to <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a> you will see that you are using your custom logo (in this example, **FastAPI**'s logo):
|
||||
|
||||
<img src="/img/tutorial/extending-openapi/image01.png">
|
||||
|
||||
## Self-hosting JavaScript and CSS for docs
|
||||
|
||||
The API docs use **Swagger UI** and **ReDoc**, and each of those need some JavaScript and CSS files.
|
||||
|
||||
By default, those files are served from a <abbr title="Content Delivery Network: A service, normally composed of several servers, that provides static files, like JavaScript and CSS. It's commonly used to serve those files from the server closer to the client, improving performance.">CDN</abbr>.
|
||||
|
||||
But it's possible to customize it, you can set a specific CDN, or serve the files yourself.
|
||||
|
||||
That's useful, for example, if you need your app to keep working even while offline, without open Internet access, or in a local network.
|
||||
|
||||
Here you'll see how to serve those files yourself, in the same FastAPI app, and configure the docs to use them.
|
||||
|
||||
### Project file structure
|
||||
|
||||
Let's say your project file structure looks like this:
|
||||
|
||||
```
|
||||
.
|
||||
├── app
|
||||
│ ├── __init__.py
|
||||
│ ├── main.py
|
||||
```
|
||||
|
||||
Now create a directory to store those static files.
|
||||
|
||||
Your new file structure could look like this:
|
||||
|
||||
```
|
||||
.
|
||||
├── app
|
||||
│ ├── __init__.py
|
||||
│ ├── main.py
|
||||
└── static/
|
||||
```
|
||||
|
||||
### Download the files
|
||||
|
||||
Download the static files needed for the docs and put them on that `static/` directory.
|
||||
|
||||
You can probably right-click each link and select an option similar to `Save link as...`.
|
||||
|
||||
**Swagger UI** uses the files:
|
||||
|
||||
* <a href="https://cdn.jsdelivr.net/npm/swagger-ui-dist@3/swagger-ui-bundle.js" class="external-link" target="_blank">`swagger-ui-bundle.js`</a>
|
||||
* <a href="https://cdn.jsdelivr.net/npm/swagger-ui-dist@3/swagger-ui.css" class="external-link" target="_blank">`swagger-ui.css`</a>
|
||||
|
||||
And **ReDoc** uses the file:
|
||||
|
||||
* <a href="https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js" class="external-link" target="_blank">`redoc.standalone.js`</a>
|
||||
|
||||
After that, your file structure could look like:
|
||||
|
||||
```
|
||||
.
|
||||
├── app
|
||||
│ ├── __init__.py
|
||||
│ ├── main.py
|
||||
└── static
|
||||
├── redoc.standalone.js
|
||||
├── swagger-ui-bundle.js
|
||||
└── swagger-ui.css
|
||||
```
|
||||
|
||||
### Install `aiofiles`
|
||||
|
||||
Now you need to install `aiofiles`:
|
||||
|
||||
```bash
|
||||
pip install aiofiles
|
||||
```
|
||||
|
||||
### Serve the static files
|
||||
|
||||
* Import `StaticFiles` from Starlette.
|
||||
* "Mount" it the same way you would <a href="https://fastapi.tiangolo.com/tutorial/sub-applications-proxy/" target="_blank">mount a Sub-Application</a>.
|
||||
|
||||
```Python hl_lines="7 11"
|
||||
{!./src/extending_openapi/tutorial002.py!}
|
||||
```
|
||||
|
||||
### Test the static files
|
||||
|
||||
Start your application and go to <a href="http://127.0.0.1:8000/static/redoc.standalone.js" class="external-link" target="_blank">http://127.0.0.1:8000/static/redoc.standalone.js</a>.
|
||||
|
||||
You should see a very long JavaScript file for **ReDoc**.
|
||||
|
||||
It could start with something like:
|
||||
|
||||
```JavaScript
|
||||
/*!
|
||||
* ReDoc - OpenAPI/Swagger-generated API Reference Documentation
|
||||
* -------------------------------------------------------------
|
||||
* Version: "2.0.0-rc.18"
|
||||
* Repo: https://github.com/Redocly/redoc
|
||||
*/
|
||||
!function(e,t){"object"==typeof exports&&"object"==typeof m
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
That confirms that you are being able to serve static files from your app, and that you placed the static files for the docs in the correct place.
|
||||
|
||||
Now we can configure the app to use those static files for the docs.
|
||||
|
||||
### Disable the automatic docs
|
||||
|
||||
The first step is to disable the automatic docs, as those use the CDN by default.
|
||||
|
||||
To disable them, set their URLs to `None` when creating your `FastAPI` app:
|
||||
|
||||
```Python hl_lines="9"
|
||||
{!./src/extending_openapi/tutorial002.py!}
|
||||
```
|
||||
|
||||
### Include the custom docs
|
||||
|
||||
Now you can create the *path operations* for the custom docs.
|
||||
|
||||
You can re-use FastAPI's internal functions to create the HTML pages for the docs, and pass them the needed arguments:
|
||||
|
||||
* `openapi_url`: the URL where the HTML page for the docs can get the OpenAPI schema for your API. You can use here the attribute `app.openapi_url`.
|
||||
* `title`: the title of your API.
|
||||
* `oauth2_redirect_url`: you can use `app.swagger_ui_oauth2_redirect_url` here to use the default.
|
||||
* `swagger_js_url`: the URL where the HTML for your Swagger UI docs can get the **JavaScript** file. This is the one that your own app is now serving.
|
||||
* `swagger_css_url`: the URL where the HTML for your Swagger UI docs can get the **CSS** file. This is the one that your own app is now serving.
|
||||
|
||||
And similarly for ReDoc...
|
||||
|
||||
```Python hl_lines="2 3 4 5 6 14 15 16 17 18 19 20 21 22 25 26 27 30 31 32 33 34 35 36"
|
||||
{!./src/extending_openapi/tutorial002.py!}
|
||||
```
|
||||
|
||||
!!! tip
|
||||
The *path operation* for `swagger_ui_redirect` is a helper for when you use OAuth2.
|
||||
|
||||
If you integrate your API with an OAuth2 provider, you will be able to authenticate and come back to the API docs with the acquired credentials. And interact with it using the real OAuth2 authentication.
|
||||
|
||||
Swagger UI will handle it behind the scenes for you, but it needs this "redirect" helper.
|
||||
|
||||
### Create a *path operation* to test it
|
||||
|
||||
Now, to be able to test that everything works, create a path operation:
|
||||
|
||||
```Python hl_lines="39 40 41"
|
||||
{!./src/extending_openapi/tutorial002.py!}
|
||||
```
|
||||
|
||||
### Test it
|
||||
|
||||
Now, you should be able to disconnect your WiFi, go to your docs at <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>, and reload the page.
|
||||
|
||||
And even without Internet, you would be able to see the docs for your API and interact with it.
|
||||
@@ -50,7 +50,7 @@ Here are some of the additional data types you can use:
|
||||
|
||||
## Example
|
||||
|
||||
Here's an example path operation with parameters using some of the above types.
|
||||
Here's an example *path operation* with parameters using some of the above types.
|
||||
|
||||
```Python hl_lines="1 2 11 12 13 14 15"
|
||||
{!./src/extra_data_types/tutorial001.py!}
|
||||
|
||||
@@ -9,7 +9,7 @@ This is especially the case for user models, because:
|
||||
!!! danger
|
||||
Never store user's plaintext passwords. Always store a "secure hash" that you can then verify.
|
||||
|
||||
If you don't know, you will learn what a "password hash" is in the <a href="/tutorial/security/simple-oauth2/#password-hashing" target="_blank">security chapters</a>.
|
||||
If you don't know, you will learn what a "password hash" is in the [security chapters](security/simple-oauth2.md#password-hashing){.internal-link target=_blank}.
|
||||
|
||||
## Multiple models
|
||||
|
||||
|
||||
@@ -160,7 +160,7 @@ And put it in a file `main.py`, then you would call `uvicorn` like:
|
||||
uvicorn main:my_awesome_api --reload
|
||||
```
|
||||
|
||||
### Step 3: create a path operation
|
||||
### Step 3: create a *path operation*
|
||||
|
||||
#### Path
|
||||
|
||||
@@ -218,7 +218,7 @@ So, in OpenAPI, each of the HTTP methods is called an "operation".
|
||||
|
||||
We are going to call them "**operations**" too.
|
||||
|
||||
#### Define a path operation function
|
||||
#### Define a *path operation function*
|
||||
|
||||
```Python hl_lines="6"
|
||||
{!./src/first_steps/tutorial001.py!}
|
||||
@@ -289,7 +289,7 @@ You could also define it as a normal function instead of `async def`:
|
||||
```
|
||||
|
||||
!!! note
|
||||
If you don't know the difference, check the _"In a hurry?"_ section about <a href="https://fastapi.tiangolo.com/async/#in-a-hurry" target="_blank">`async` and `await` in the docs</a>.
|
||||
If you don't know the difference, check the [Async: *"In a hurry?"*](../async.md#in-a-hurry){.internal-link target=_blank}.
|
||||
|
||||
### Step 5: return the content
|
||||
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
|
||||
**FastAPI** has optional support for GraphQL (provided by Starlette directly), using the `graphene` library.
|
||||
|
||||
You can combine normal FastAPI path operations with GraphQL on the same application.
|
||||
|
||||
## Import and use `graphene`
|
||||
|
||||
GraphQL is implemented with Graphene, you can check <a href="https://docs.graphene-python.org/en/latest/quickstart/" class="external-link" target="_blank">Graphene's docs</a> for more details.
|
||||
|
||||
Import `graphene` and define your GraphQL data:
|
||||
|
||||
```Python hl_lines="1 6 7 8 9 10"
|
||||
{!./src/graphql/tutorial001.py!}
|
||||
```
|
||||
|
||||
## Add Starlette's `GraphQLApp`
|
||||
|
||||
Then import and add Starlette's `GraphQLApp`:
|
||||
|
||||
```Python hl_lines="3 14"
|
||||
{!./src/graphql/tutorial001.py!}
|
||||
```
|
||||
|
||||
!!! info
|
||||
Here we are using `.add_route`, that is the way to add a route in Starlette (inherited by FastAPI) without declaring the specific operation (as would be with `.get()`, `.post()`, etc).
|
||||
|
||||
## Check it
|
||||
|
||||
Run it with Uvicorn and open your browser at <a href="http://127.0.0.1:8000" class="external-link" target="_blank">http://127.0.0.1:8000</a>.
|
||||
|
||||
You will see GraphiQL web user interface:
|
||||
|
||||
<img src="/img/tutorial/graphql/image01.png">
|
||||
|
||||
## More details
|
||||
|
||||
For more details, including:
|
||||
|
||||
* Accessing request information
|
||||
* Adding background tasks
|
||||
* Using normal or async functions
|
||||
|
||||
check the official <a href="https://www.starlette.io/graphql/" class="external-link" target="_blank">Starlette GraphQL docs</a>.
|
||||
@@ -33,7 +33,7 @@ To return HTTP responses with errors to the client you use `HTTPException`.
|
||||
|
||||
Because it's a Python exception, you don't `return` it, you `raise` it.
|
||||
|
||||
This also means that if you are inside a utility function that you are calling inside of your path operation function, and you raise the `HTTPException` from inside of that utility function, it won't run the rest of the code in the path operation function, it will terminate that request right away and send the HTTP error from the `HTTPException` to the client.
|
||||
This also means that if you are inside a utility function that you are calling inside of your *path operation function*, and you raise the `HTTPException` from inside of that utility function, it won't run the rest of the code in the *path operation function*, it will terminate that request right away and send the HTTP error from the `HTTPException` to the client.
|
||||
|
||||
The benefit of raising an exception over `return`ing a value will be more evident in the section about Dependencies and Security.
|
||||
|
||||
@@ -231,7 +231,7 @@ So, you can keep raising **FastAPI**'s `HTTPException` as normally in your code.
|
||||
|
||||
But when you register an exception handler, you should register it for Starlette's `HTTPException`.
|
||||
|
||||
This way, if any part of Starlette's internal code, or a Starlette extension or plug-in, raises an `HTTPException`, your handler will be able to catch handle it.
|
||||
This way, if any part of Starlette's internal code, or a Starlette extension or plug-in, raises an `HTTPException`, your handler will be able to catch and handle it.
|
||||
|
||||
In this example, to be able to have both `HTTPException`s in the same code, Starlette's exceptions is renamed to `StarletteHTTPException`:
|
||||
|
||||
@@ -246,9 +246,9 @@ You could also just want to use the exception somehow, but then use the same def
|
||||
You can import and re-use the default exception handlers from `fastapi.exception_handlers`:
|
||||
|
||||
```Python hl_lines="2 3 4 5 15 21"
|
||||
{!./src/handling_errors/tutorial005.py!}
|
||||
{!./src/handling_errors/tutorial006.py!}
|
||||
```
|
||||
|
||||
In this example, you are just `print`ing the error with a very expressive notification.
|
||||
In this example, you are just `print`ing the error with a very expressive message.
|
||||
|
||||
But you get the idea, you can use the exception and then just re-use the default exception handlers.
|
||||
|
||||
@@ -21,7 +21,7 @@ The first value is the default value, you can pass all the extra validation or a
|
||||
!!! note "Technical Details"
|
||||
`Header` is a "sister" class of `Path`, `Query` and `Cookie`. It also inherits from the same common `Param` class.
|
||||
|
||||
But remember that when you import `Query`, `Path`, `Header`, and others from `fastapi`, <a href="https://fastapi.tiangolo.com/tutorial/path-params-numeric-validations/#recap" target="_blank">those are actually functions that return classes of the same name</a>.
|
||||
But remember that when you import `Query`, `Path`, `Header`, and others from `fastapi`, those are actually functions that return special classes.
|
||||
|
||||
!!! info
|
||||
To declare headers, you need to use `Header`, because otherwise the parameters would be interpreted as query parameters.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
This tutorial shows you how to use **FastAPI** with all its features, step by step.
|
||||
This tutorial shows you how to use **FastAPI** with most of its features, step by step.
|
||||
|
||||
Each section gradually builds on the previous ones, but it's structured to separate topics, so that you can go directly to any specific one to solve your specific API needs.
|
||||
|
||||
@@ -36,7 +36,7 @@ pip install fastapi[all]
|
||||
|
||||
!!! note
|
||||
You can also install it part by part.
|
||||
|
||||
|
||||
This is what you would probably do once you want to deploy your application to production:
|
||||
|
||||
```
|
||||
@@ -50,3 +50,13 @@ pip install fastapi[all]
|
||||
```
|
||||
|
||||
And the same for each of the optional dependencies that you want to use.
|
||||
|
||||
## Advanced User Guide
|
||||
|
||||
There is also an **Advanced User Guide** that you can read later after this **Tutorial - User guide**.
|
||||
|
||||
The **Advanced User Guide**, builds on this, uses the same concepts, and teaches you some extra features.
|
||||
|
||||
But you should first read the **Tutorial - User guide** (what you are reading right now).
|
||||
|
||||
It's designed so that you can build a complete application with just the **Tutorial - User guide**, and then extend it in different ways, depending on your needs, using some of the additional ideas from the **Advanced User Guide**.
|
||||
@@ -25,17 +25,15 @@ The middleware function receives:
|
||||
{!./src/middleware/tutorial001.py!}
|
||||
```
|
||||
|
||||
!!! tip
|
||||
This technique is used in the tutorial about <a href="https://fastapi.tiangolo.com/tutorial/sql-databases/" target="_blank">SQL (Relational) Databases</a>.
|
||||
|
||||
!!! tip
|
||||
Have in mind that custom proprietary headers can be added <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers" class="external-link" target="_blank">using the 'X-' prefix</a>.
|
||||
|
||||
But if you have custom headers that you want a client in a browser to be able to see, you need to add them to your <a href="https://fastapi.tiangolo.com/tutorial/cors/" target="_blank">CORS configurations</a>, using the parameter `expose_headers` documented in <a href="https://www.starlette.io/middleware/#corsmiddleware" class="external-link" target="_blank">Starlette's CORS docs</a>.
|
||||
But if you have custom headers that you want a client in a browser to be able to see, you need to add them to your CORS configurations ([CORS (Cross-Origin Resource Sharing)
|
||||
](cors.md){.internal-link target=_blank}) using the parameter `expose_headers` documented in <a href="https://www.starlette.io/middleware/#corsmiddleware" class="external-link" target="_blank">Starlette's CORS docs</a>.
|
||||
|
||||
### Before and after the `response`
|
||||
|
||||
You can add code to be run with the `request`, before any *path operation* receives it.
|
||||
You can add code to be run with the `request`, before any *path operation* receives it.
|
||||
|
||||
And also after the `response` is generated, before returning it.
|
||||
|
||||
|
||||
@@ -1,154 +0,0 @@
|
||||
**FastAPI** can also be integrated with any <abbr title="Distributed database (Big Data), also 'Not Only SQL'">NoSQL</abbr>.
|
||||
|
||||
Here we'll see an example using **<a href="https://www.couchbase.com/" class="external-link" target="_blank">Couchbase</a>**, a <abbr title="Document here refers to a JSON object (a dict), with keys and values, and those values can also be other JSON objects, arrays (lists), numbers, strings, booleans, etc.">document</abbr> based NoSQL database.
|
||||
|
||||
You can adapt it to any other NoSQL database like:
|
||||
|
||||
* **MongoDB**
|
||||
* **Cassandra**
|
||||
* **CouchDB**
|
||||
* **ArangoDB**
|
||||
* **ElasticSearch**, etc.
|
||||
|
||||
!!! tip
|
||||
There is an official project generator with **FastAPI** and **Couchbase**, all based on **Docker**, including a frontend and more tools: <a href="https://github.com/tiangolo/full-stack-fastapi-couchbase" class="external-link" target="_blank">https://github.com/tiangolo/full-stack-fastapi-couchbase</a>
|
||||
|
||||
## Import Couchbase components
|
||||
|
||||
For now, don't pay attention to the rest, only the imports:
|
||||
|
||||
```Python hl_lines="6 7 8"
|
||||
{!./src/nosql_databases/tutorial001.py!}
|
||||
```
|
||||
|
||||
## Define a constant to use as a "document type"
|
||||
|
||||
We will use it later as a fixed field `type` in our documents.
|
||||
|
||||
This is not required by Couchbase, but is a good practice that will help you afterwards.
|
||||
|
||||
```Python hl_lines="10"
|
||||
{!./src/nosql_databases/tutorial001.py!}
|
||||
```
|
||||
|
||||
## Add a function to get a `Bucket`
|
||||
|
||||
In **Couchbase**, a bucket is a set of documents, that can be of different types.
|
||||
|
||||
They are generally all related to the same application.
|
||||
|
||||
The analogy in the relational database world would be a "database" (a specific database, not the database server).
|
||||
|
||||
The analogy in **MongoDB** would be a "collection".
|
||||
|
||||
In the code, a `Bucket` represents the main entrypoint of communication with the database.
|
||||
|
||||
This utility function will:
|
||||
|
||||
* Connect to a **Couchbase** cluster (that might be a single machine).
|
||||
* Set defaults for timeouts.
|
||||
* Authenticate in the cluster.
|
||||
* Get a `Bucket` instance.
|
||||
* Set defaults for timeouts.
|
||||
* Return it.
|
||||
|
||||
```Python hl_lines="13 14 15 16 17 18 19 20 21 22"
|
||||
{!./src/nosql_databases/tutorial001.py!}
|
||||
```
|
||||
|
||||
## Create Pydantic models
|
||||
|
||||
As **Couchbase** "documents" are actually just "JSON objects", we can model them with Pydantic.
|
||||
|
||||
### `User` model
|
||||
|
||||
First, let's create a `User` model:
|
||||
|
||||
```Python hl_lines="25 26 27 28 29"
|
||||
{!./src/nosql_databases/tutorial001.py!}
|
||||
```
|
||||
|
||||
We will use this model in our path operation function, so, we don't include in it the `hashed_password`.
|
||||
|
||||
### `UserInDB` model
|
||||
|
||||
Now, let's create a `UserInDB` model.
|
||||
|
||||
This will have the data that is actually stored in the database.
|
||||
|
||||
We don't create it as a subclass of Pydantic's `BaseModel` but as a subclass of our own `User`, because it will have all the attributes in `User` plus a couple more:
|
||||
|
||||
```Python hl_lines="32 33 34"
|
||||
{!./src/nosql_databases/tutorial001.py!}
|
||||
```
|
||||
|
||||
!!! note
|
||||
Notice that we have a `hashed_password` and a `type` field that will be stored in the database.
|
||||
|
||||
But it is not part of the general `User` model (the one we will return in the path operation).
|
||||
|
||||
## Get the user
|
||||
|
||||
Now create a function that will:
|
||||
|
||||
* Take a username.
|
||||
* Generate a document ID from it.
|
||||
* Get the document with that ID.
|
||||
* Put the contents of the document in a `UserInDB` model.
|
||||
|
||||
By creating a function that is only dedicated to getting your user from a `username` (or any other parameter) independent of your path operation function, you can more easily re-use it in multiple parts and also add <abbr title="Automated test, written in code, that checks if another piece of code is working correctly.">unit tests</abbr> for it:
|
||||
|
||||
```Python hl_lines="37 38 39 40 41 42 43"
|
||||
{!./src/nosql_databases/tutorial001.py!}
|
||||
```
|
||||
|
||||
### f-strings
|
||||
|
||||
If you are not familiar with the `f"userprofile::{username}"`, it is a Python "<a href="https://docs.python.org/3/glossary.html#term-f-string" class="external-link" target="_blank">f-string</a>".
|
||||
|
||||
Any variable that is put inside of `{}` in an f-string will be expanded / injected in the string.
|
||||
|
||||
### `dict` unpacking
|
||||
|
||||
If you are not familiar with the `UserInDB(**result.value)`, <a href="https://docs.python.org/3/glossary.html#term-argument" class="external-link" target="_blank">it is using `dict` "unpacking"</a>.
|
||||
|
||||
It will take the `dict` at `result.value`, and take each of its keys and values and pass them as key-values to `UserInDB` as keyword arguments.
|
||||
|
||||
So, if the `dict` contains:
|
||||
|
||||
```Python
|
||||
{
|
||||
"username": "johndoe",
|
||||
"hashed_password": "some_hash",
|
||||
}
|
||||
```
|
||||
|
||||
It will be passed to `UserInDB` as:
|
||||
|
||||
```Python
|
||||
UserInDB(username="johndoe", hashed_password="some_hash")
|
||||
```
|
||||
|
||||
## Create your **FastAPI** code
|
||||
|
||||
### Create the `FastAPI` app
|
||||
|
||||
```Python hl_lines="47"
|
||||
{!./src/nosql_databases/tutorial001.py!}
|
||||
```
|
||||
|
||||
### Create the path operation function
|
||||
|
||||
As our code is calling Couchbase and we are not using the <a href="https://docs.couchbase.com/python-sdk/2.5/async-programming.html#asyncio-python-3-5" class="external-link" target="_blank">experimental Python <code>await</code> support</a>, we should declare our function with normal `def` instead of `async def`.
|
||||
|
||||
Also, Couchbase recommends not using a single `Bucket` object in multiple "<abbr title="A sequence of code being executed by the program, while at the same time, or at intervals, there can be others being executed too.">thread</abbr>s", so, we can get just get the bucket directly and pass it to our utility functions:
|
||||
|
||||
```Python hl_lines="50 51 52 53 54"
|
||||
{!./src/nosql_databases/tutorial001.py!}
|
||||
```
|
||||
|
||||
## Recap
|
||||
|
||||
You can integrate any third party NoSQL database, just using their standard packages.
|
||||
|
||||
The same applies to any other external tool, system or API.
|
||||
@@ -1,186 +0,0 @@
|
||||
You could create an API with a *path operation* that could trigger a request to an *external API* created by someone else (probably the same developer that would be *using* your API).
|
||||
|
||||
The process that happens when your API app calls the *external API* is named a "callback". Because the software that the external developer wrote sends a request to your API and then your API *calls back*, sending a request to an *external API* (that was probably created by the same developer).
|
||||
|
||||
In this case, you could want to document how that external API *should* look like. What *path operation* it should have, what body it should expect, what response it should return, etc.
|
||||
|
||||
## An app with callbacks
|
||||
|
||||
Let's see all this with an example.
|
||||
|
||||
Imagine you develop an app that allows creating invoices.
|
||||
|
||||
These invoices will have an `id`, `title` (optional), `customer`, and `total`.
|
||||
|
||||
The user of your API (an external developer) will create an invoice in your API with a POST request.
|
||||
|
||||
Then your API will (let's imagine):
|
||||
|
||||
* Send the invoice to some customer of the external developer.
|
||||
* Collect the money.
|
||||
* Send a notification back to the API user (the external developer).
|
||||
* This will be done by sending a POST request (from *your API*) to some *external API* provided by that external developer (this is the "callback").
|
||||
|
||||
## The normal **FastAPI** app
|
||||
|
||||
Let's first see how the normal API app would look like before adding the callback.
|
||||
|
||||
It will have a *path operation* that will receive an `Invoice` body, and a query parameter `callback_url` that will contain the URL for the callback.
|
||||
|
||||
This part is pretty normal, most of the code is probably already familiar to you:
|
||||
|
||||
```Python hl_lines="8 9 10 11 12 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53"
|
||||
{!./src/openapi_callbacks/tutorial001.py!}
|
||||
```
|
||||
|
||||
!!! tip
|
||||
The `callback_url` query parameter uses a Pydantic <a href="https://pydantic-docs.helpmanual.io/usage/types/#urls" class="external-link" target="_blank">URL</a> type.
|
||||
|
||||
The only new thing is the `callbacks=messages_callback_router.routes` as an argument to the *path operation decorator*. We'll see what that is next.
|
||||
|
||||
## Documenting the callback
|
||||
|
||||
The actual callback code will depend heavily on your own API app.
|
||||
|
||||
And it will probably vary a lot from one app to the next.
|
||||
|
||||
It could be just one or two lines of code, like:
|
||||
|
||||
```Python
|
||||
callback_url = "https://example.com/api/v1/invoices/events/"
|
||||
requests.post(callback_url, json={"description": "Invoice paid", "paid": True})
|
||||
```
|
||||
|
||||
But possibly the most important part of the callback is making sure that your API user (the external developer) implements the *external API* correctly, according to the data that *your API* is going to send in the request body of the callback, etc.
|
||||
|
||||
So, what we will do next is add the code to document how that *external API* should look like to receive the callback from *your API*.
|
||||
|
||||
That documentation will show up in the Swagger UI at `/docs` in your API, and it will let external developers know how to build the *external API*.
|
||||
|
||||
This example doesn't implement the callback itself (that could be just a line of code), only the documentation part.
|
||||
|
||||
!!! tip
|
||||
The actual callback is just an HTTP request.
|
||||
|
||||
When implementing the callback yourself, you could use something like <a href="https://www.encode.io/httpx/" target="_blank">HTTPX</a> or <a href="https://requests.readthedocs.io/" class="external-link" target="_blank">Requests</a>.
|
||||
|
||||
## Write the callback documentation code
|
||||
|
||||
This code won't be executed in your app, we only need it to *document* how that *external API* should look like.
|
||||
|
||||
But, you already know how to easily create automatic documentation for an API with **FastAPI**.
|
||||
|
||||
So we are going to use that same knowledge to document how the *external API* should look like... by creating the *path operation(s)* that the external API should implement (the ones your API will call).
|
||||
|
||||
!!! tip
|
||||
When writing the code to document a callback, it might be useful to imagine that you are that *external developer*. And that you are currently implementing the *external API*, not *your API*.
|
||||
|
||||
Temporarily adopting this point of view (of the *external developer*) can help you feel like it's more obvious where to put the parameters, the Pydantic model for the body, for the response, etc. for that *external API*.
|
||||
|
||||
### Create a callback `APIRouter`
|
||||
|
||||
First create a new `APIRouter` that will contain one or more callbacks.
|
||||
|
||||
This router will never be added to an actual `FastAPI` app (i.e. it will never be passed to `app.include_router(...)`).
|
||||
|
||||
Because of that, you need to declare what will be the `default_response_class`, and set it to `JSONResponse`.
|
||||
|
||||
!!! Note "Technical Details"
|
||||
The `response_class` is normally set by the `FastAPI` app during the call to `app.include_router(some_router)`.
|
||||
|
||||
But as we are never calling `app.include_router(some_router)`, we need to set the `default_response_class` during creation of the `APIRouter`.
|
||||
|
||||
```Python hl_lines="3 24"
|
||||
{!./src/openapi_callbacks/tutorial001.py!}
|
||||
```
|
||||
|
||||
### Create the callback *path operation*
|
||||
|
||||
To create the callback *path operation* use the same `APIRouter` you created above.
|
||||
|
||||
It should look just like a normal FastAPI *path operation*:
|
||||
|
||||
* It should probably have a declaration of the body it should receive, e.g. `body: InvoiceEvent`.
|
||||
* And it could also have a declaration of the response it should return, e.g. `response_model=InvoiceEventReceived`.
|
||||
|
||||
```Python hl_lines="15 16 17 20 21 27 28 29 30 31"
|
||||
{!./src/openapi_callbacks/tutorial001.py!}
|
||||
```
|
||||
|
||||
There are 2 main differences from a normal *path operation*:
|
||||
|
||||
* It doesn't need to have any actual code, because your app will never call this code. It's only used to document the *external API*. So, the function could just have `pass`.
|
||||
* The *path* can contain an <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#key-expression" class="external-link" target="_blank">OpenAPI 3 expression</a> (see more below) where it can use variables with parameters and parts of the original request sent to *your API*.
|
||||
|
||||
### The callback path expression
|
||||
|
||||
The callback *path* can have an <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#key-expression" class="external-link" target="_blank">OpenAPI 3 expression</a> that can contain parts of the original request sent to *your API*.
|
||||
|
||||
In this case, it's the `str`:
|
||||
|
||||
```Python
|
||||
"{$callback_url}/invoices/{$request.body.id}"
|
||||
```
|
||||
|
||||
So, if your API user (the external developer) sends a request to *your API* to:
|
||||
|
||||
```
|
||||
https://yourapi.com/invoices/?callback_url=https://www.external.org/events
|
||||
```
|
||||
|
||||
with a JSON body of:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"id": "2expen51ve",
|
||||
"customer": "Mr. Richie Rich",
|
||||
"total": "9999"
|
||||
}
|
||||
```
|
||||
|
||||
Then *your API* will process the invoice, and at some point later, send a callback request to the `callback_url` (the *external API*):
|
||||
|
||||
```
|
||||
https://www.external.org/events/invoices/2expen51ve
|
||||
```
|
||||
|
||||
with a JSON body containing something like:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"description": "Payment celebration",
|
||||
"paid": true
|
||||
}
|
||||
```
|
||||
|
||||
and it would expect a response from that *external API* with a JSON body like:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"ok": true
|
||||
}
|
||||
```
|
||||
|
||||
!!! tip
|
||||
Notice how the callback URL used contains the URL received as a query parameter in `callback_url` (`https://www.external.org/events`) and also the invoice `id` from inside of the JSON body (`2expen51ve`).
|
||||
|
||||
### Add the callback router
|
||||
|
||||
At this point you have the *callback path operation(s)* needed (the one(s) that the *external developer* should implement in the *external API*) in the callback router you created above.
|
||||
|
||||
Now use the parameter `callbacks` in *your API's path operation decorator* to pass the attribute `.routes` (that's actually just a `list` of routes/*path operations*) from that callback router:
|
||||
|
||||
```Python hl_lines="34"
|
||||
{!./src/openapi_callbacks/tutorial001.py!}
|
||||
```
|
||||
|
||||
!!! tip
|
||||
Notice that you are not passing the router itself (`invoices_callback_router`) to `callback=`, but the attribute `.routes`, as in `invoices_callback_router.routes`.
|
||||
|
||||
### Check the docs
|
||||
|
||||
Now you can start your app with Uvicorn and go to <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
|
||||
|
||||
You will see your docs including a "Callback" section for your *path operation* that shows how the *external API* should look like:
|
||||
|
||||
<img src="/img/tutorial/openapi-callbacks/image01.png">
|
||||
@@ -1,50 +0,0 @@
|
||||
## OpenAPI operationId
|
||||
|
||||
!!! danger
|
||||
If you are not an "expert" in OpenAPI, you probably don't need this.
|
||||
|
||||
You can set the OpenAPI `operationId` to be used in your path operation with the parameter `operation_id`.
|
||||
|
||||
You would have to make sure that it is unique for each operation.
|
||||
|
||||
```Python hl_lines="6"
|
||||
{!./src/path_operation_advanced_configuration/tutorial001.py!}
|
||||
```
|
||||
|
||||
### Using the *path operation function* name as the operationId
|
||||
|
||||
If you want to use your APIs' function names as `operationId`s, you can iterate over all of them and override each *path operation's* `operation_id` using their `APIRoute.name`.
|
||||
|
||||
You should do it after adding all your *path operations*.
|
||||
|
||||
```Python hl_lines="2 12 13 14 15 16 17 18 19 20 21 24"
|
||||
{!./src/path_operation_advanced_configuration/tutorial002.py!}
|
||||
```
|
||||
|
||||
!!! tip
|
||||
If you manually call `app.openapi()`, you should update the `operationId`s before that.
|
||||
|
||||
!!! warning
|
||||
If you do this, you have to make sure each one of your *path operation functions* has a unique name.
|
||||
|
||||
Even if they are in different modules (Python files).
|
||||
|
||||
## Exclude from OpenAPI
|
||||
|
||||
To exclude a path operation from the generated OpenAPI schema (and thus, from the automatic documentation systems), use the parameter `include_in_schema` and set it to `False`;
|
||||
|
||||
```Python hl_lines="6"
|
||||
{!./src/path_operation_advanced_configuration/tutorial003.py!}
|
||||
```
|
||||
|
||||
## Advanced description from docstring
|
||||
|
||||
You can limit the lines used from the docstring of a *path operation function* for OpenAPI.
|
||||
|
||||
Adding an `\f` (an escaped "form feed" character) causes **FastAPI** to truncate the output used for OpenAPI at this point.
|
||||
|
||||
It won't show up in the documentation, but other tools (such as Sphinx) will be able to use the rest.
|
||||
|
||||
```Python hl_lines="19 20 21 22 23 24 25 26 27 28 29"
|
||||
{!./src/path_operation_advanced_configuration/tutorial004.py!}
|
||||
```
|
||||
@@ -1,11 +1,11 @@
|
||||
There are several parameters that you can pass to your path operation decorator to configure it.
|
||||
There are several parameters that you can pass to your *path operation decorator* to configure it.
|
||||
|
||||
!!! warning
|
||||
Notice that these parameters are passed directly to the path operation decorator, not to your path operation function.
|
||||
Notice that these parameters are passed directly to the *path operation decorator*, not to your *path operation function*.
|
||||
|
||||
## Response Status Code
|
||||
|
||||
You can define the (HTTP) `status_code` to be used in the response of your path operation.
|
||||
You can define the (HTTP) `status_code` to be used in the response of your *path operation*.
|
||||
|
||||
You can pass directly the `int` code, like `404`.
|
||||
|
||||
@@ -19,7 +19,7 @@ That status code will be used in the response and will be added to the OpenAPI s
|
||||
|
||||
## Tags
|
||||
|
||||
You can add tags to your path operation, pass the parameter `tags` with a `list` of `str` (commonly just one `str`):
|
||||
You can add tags to your *path operation*, pass the parameter `tags` with a `list` of `str` (commonly just one `str`):
|
||||
|
||||
```Python hl_lines="17 22 27"
|
||||
{!./src/path_operation_configuration/tutorial002.py!}
|
||||
@@ -39,9 +39,9 @@ You can add a `summary` and `description`:
|
||||
|
||||
## Description from docstring
|
||||
|
||||
As descriptions tend to be long and cover multiple lines, you can declare the path operation description in the function <abbr title="a multi-line string as the first expression inside a function (not assigned to any variable) used for documentation">docstring</abbr> and **FastAPI** will read it from there.
|
||||
As descriptions tend to be long and cover multiple lines, you can declare the *path operation* description in the function <abbr title="a multi-line string as the first expression inside a function (not assigned to any variable) used for documentation">docstring</abbr> and **FastAPI** will read it from there.
|
||||
|
||||
You can write <a href="https://en.wikipedia.org/wiki/Markdown" class="target-blank" target="_blank">Markdown</a> in the docstring, it will be interpreted and displayed correctly (taking into account docstring indentation).
|
||||
You can write <a href="https://en.wikipedia.org/wiki/Markdown" class="external-link" target="_blank">Markdown</a> in the docstring, it will be interpreted and displayed correctly (taking into account docstring indentation).
|
||||
|
||||
```Python hl_lines="19 20 21 22 23 24 25 26 27"
|
||||
{!./src/path_operation_configuration/tutorial004.py!}
|
||||
@@ -60,18 +60,18 @@ You can specify the response description with the parameter `response_descriptio
|
||||
```
|
||||
|
||||
!!! info
|
||||
Notice that `response_description` refers specifically to the response, the `description` refers to the path operation in general.
|
||||
Notice that `response_description` refers specifically to the response, the `description` refers to the *path operation* in general.
|
||||
|
||||
!!! info
|
||||
OpenAPI specifies that each path operation requires a response description.
|
||||
!!! check
|
||||
OpenAPI specifies that each *path operation* requires a response description.
|
||||
|
||||
So, if you don't provide one, **FastAPI** will automatically generate one of "Successful response".
|
||||
|
||||
<img src="/img/tutorial/path-operation-configuration/image03.png">
|
||||
|
||||
## Deprecate a path operation
|
||||
## Deprecate a *path operation*
|
||||
|
||||
If you need to mark a path operation as <abbr title="obsolete, recommended not to use it">deprecated</abbr>, but without removing it, pass the parameter `deprecated`:
|
||||
If you need to mark a *path operation* as <abbr title="obsolete, recommended not to use it">deprecated</abbr>, but without removing it, pass the parameter `deprecated`:
|
||||
|
||||
```Python hl_lines="16"
|
||||
{!./src/path_operation_configuration/tutorial006.py!}
|
||||
@@ -81,10 +81,10 @@ It will be clearly marked as deprecated in the interactive docs:
|
||||
|
||||
<img src="/img/tutorial/path-operation-configuration/image04.png">
|
||||
|
||||
Check how deprecated and non-deprecated path operations look like:
|
||||
Check how deprecated and non-deprecated *path operations* look like:
|
||||
|
||||
<img src="/img/tutorial/path-operation-configuration/image05.png">
|
||||
|
||||
## Recap
|
||||
|
||||
You can configure and add metadata for your path operations easily by passing parameters to the path operation decorators.
|
||||
You can configure and add metadata for your *path operations* easily by passing parameters to the *path operation decorators*.
|
||||
|
||||
@@ -66,6 +66,7 @@ Here, with `ge=1`, `item_id` will need to be an integer number "`g`reater than o
|
||||
```Python hl_lines="8"
|
||||
{!./src/path_params_numeric_validations/tutorial004.py!}
|
||||
```
|
||||
|
||||
## Number validations: greater than and less than or equal
|
||||
|
||||
The same applies for:
|
||||
@@ -93,7 +94,7 @@ And the same for <abbr title="less than"><code>lt</code></abbr>.
|
||||
|
||||
## Recap
|
||||
|
||||
With `Query`, `Path` (and others you haven't seen yet) you can declare [metadata and string validations (the previous chapter)](/tutorial/query-params-str-validations).
|
||||
With `Query`, `Path` (and others you haven't seen yet) you can declare metadata and string validations in the same ways as with [Query Parameters and String Validations](query-params-str-validations.md){.internal-link target=_blank}.
|
||||
|
||||
And you can also declare numeric validations:
|
||||
|
||||
|
||||
@@ -105,7 +105,7 @@ Like `/users/me`, let's say that it's to get data about the current user.
|
||||
|
||||
And then you can also have a path `/users/{user_id}` to get data about a specific user by some user ID.
|
||||
|
||||
Because path operations are evaluated in order, you need to make sure that the path for `/users/me` is declared before the one for `/users/{user_id}`:
|
||||
Because *path operations* are evaluated in order, you need to make sure that the path for `/users/me` is declared before the one for `/users/{user_id}`:
|
||||
|
||||
```Python hl_lines="6 11"
|
||||
{!./src/path_params/tutorial003.py!}
|
||||
|
||||
@@ -50,7 +50,7 @@ And then, we can pass more parameters to `Query`. In this case, the `max_length`
|
||||
q: str = Query(None, max_length=50)
|
||||
```
|
||||
|
||||
This will validate the data, show a clear error when the data is not valid, and document the parameter in the OpenAPI schema path operation.
|
||||
This will validate the data, show a clear error when the data is not valid, and document the parameter in the OpenAPI schema *path operation*.
|
||||
|
||||
## Add more validations
|
||||
|
||||
|
||||
@@ -188,7 +188,7 @@ In this case, there are 3 query parameters:
|
||||
* `limit`, an optional `int`.
|
||||
|
||||
!!! tip
|
||||
You could also use `Enum`s <a href="https://fastapi.tiangolo.com/tutorial/path-params/#predefined-values" target="_blank">the same way as with *path parameters*</a>.
|
||||
You could also use `Enum`s the same way as with [Path Parameters](path-params.md#predefined-values){.internal-link target=_blank}.
|
||||
|
||||
## Optional type declarations
|
||||
|
||||
|
||||
@@ -26,9 +26,9 @@ Create file parameters the same way you would for `Body` or `Form`:
|
||||
!!! info
|
||||
`File` is a class that inherits directly from `Form`.
|
||||
|
||||
But remember that when you import `Query`, `Path`, `File` and others from `fastapi`, <a href="https://fastapi.tiangolo.com/tutorial/path-params-numeric-validations/#recap" target="_blank">those are actually functions that return classes of the same name</a>.
|
||||
But remember that when you import `Query`, `Path`, `File` and others from `fastapi`, those are actually functions that return special classes.
|
||||
|
||||
!!! info
|
||||
!!! tip
|
||||
To declare File bodies, you need to use `File`, because otherwise the parameters would be interpreted as query parameters or body (JSON) parameters.
|
||||
|
||||
The files will be uploaded as "form data".
|
||||
@@ -62,7 +62,7 @@ Using `UploadFile` has several advantages over `bytes`:
|
||||
|
||||
* `filename`: A `str` with the original file name that was uploaded (e.g. `myimage.jpg`).
|
||||
* `content_type`: A `str` with the content type (MIME type / media type) (e.g. `image/jpeg`).
|
||||
* `file`: A <a href="https://docs.python.org/3/library/tempfile.html#tempfile.SpooledTemporaryFile" target="_blank">`SpooledTemporaryFile`</a> (a <a href="https://docs.python.org/3/glossary.html#term-file-like-object" target="_blank">file-like</a> object). This is the actual Python file that you can pass directly to other functions or libraries that expect a "file-like" object.
|
||||
* `file`: A <a href="https://docs.python.org/3/library/tempfile.html#tempfile.SpooledTemporaryFile" class="external-link" target="_blank">`SpooledTemporaryFile`</a> (a <a href="https://docs.python.org/3/glossary.html#term-file-like-object" class="external-link" target="_blank">file-like</a> object). This is the actual Python file that you can pass directly to other functions or libraries that expect a "file-like" object.
|
||||
|
||||
`UploadFile` has the following `async` methods. They all call the corresponding file methods underneath (using the internal `SpooledTemporaryFile`).
|
||||
|
||||
@@ -107,7 +107,7 @@ The way HTML forms (`<form></form>`) sends the data to the server normally uses
|
||||
If you want to read more about these encodings and form fields, head to the <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST" class="external-link" target="_blank"><abbr title="Mozilla Developer Network">MDN</abbr> web docs for <code>POST</code></a>.
|
||||
|
||||
!!! warning
|
||||
You can declare multiple `File` and `Form` parameters in a path operation, but you can't also declare `Body` fields that you expect to receive as JSON, as the request will have the body encoded using `multipart/form-data` instead of `application/json`.
|
||||
You can declare multiple `File` and `Form` parameters in a *path operation*, but you can't also declare `Body` fields that you expect to receive as JSON, as the request will have the body encoded using `multipart/form-data` instead of `application/json`.
|
||||
|
||||
This is not a limitation of **FastAPI**, it's part of the HTTP protocol.
|
||||
|
||||
@@ -126,7 +126,7 @@ To use that, declare a `List` of `bytes` or `UploadFile`:
|
||||
You will receive, as declared, a `list` of `bytes` or `UploadFile`s.
|
||||
|
||||
!!! note
|
||||
Notice that, as of 2019-04-14, Swagger UI doesn't support multiple file uploads in the same form field. For more information, check <a href="https://github.com/swagger-api/swagger-ui/issues/4276" target="_blank">#4276</a> and <a href="https://github.com/swagger-api/swagger-ui/issues/3641" class="external-link" target="_blank">#3641</a>.
|
||||
Notice that, as of 2019-04-14, Swagger UI doesn't support multiple file uploads in the same form field. For more information, check <a href="https://github.com/swagger-api/swagger-ui/issues/4276" class="external-link" target="_blank">#4276</a> and <a href="https://github.com/swagger-api/swagger-ui/issues/3641" class="external-link" target="_blank">#3641</a>.
|
||||
|
||||
Nevertheless, **FastAPI** is already compatible with it, using the standard OpenAPI.
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ The files and form fields will be uploaded as form data and you will receive the
|
||||
And you can declare some of the files as `bytes` and some as `UploadFile`.
|
||||
|
||||
!!! warning
|
||||
You can declare multiple `File` and `Form` parameters in a path operation, but you can't also declare `Body` fields that you expect to receive as JSON, as the request will have the body encoded using `multipart/form-data` instead of `application/json`.
|
||||
You can declare multiple `File` and `Form` parameters in a *path operation*, but you can't also declare `Body` fields that you expect to receive as JSON, as the request will have the body encoded using `multipart/form-data` instead of `application/json`.
|
||||
|
||||
This is not a limitation of **FastAPI**, it's part of the HTTP protocol.
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ With `Form` you can declare the same metadata and validation as with `Body` (and
|
||||
!!! info
|
||||
`Form` is a class that inherits directly from `Body`.
|
||||
|
||||
!!! info
|
||||
!!! tip
|
||||
To declare form bodies, you need to use `Form` explicitly, because without it the parameters would be interpreted as query parameters or body (JSON) parameters.
|
||||
|
||||
## About "Form Fields"
|
||||
@@ -47,7 +47,7 @@ The way HTML forms (`<form></form>`) sends the data to the server normally uses
|
||||
If you want to read more about these encodings and form fields, head to the <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST" class="external-link" target="_blank"><abbr title="Mozilla Developer Network">MDN</abbr> web docs for <code>POST</code></a>.
|
||||
|
||||
!!! warning
|
||||
You can declare multiple `Form` parameters in a path operation, but you can't also declare `Body` fields that you expect to receive as JSON, as the request will have the body encoded using `application/x-www-form-urlencoded` instead of `application/json`.
|
||||
You can declare multiple `Form` parameters in a *path operation*, but you can't also declare `Body` fields that you expect to receive as JSON, as the request will have the body encoded using `application/x-www-form-urlencoded` instead of `application/json`.
|
||||
|
||||
This is not a limitation of **FastAPI**, it's part of the HTTP protocol.
|
||||
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
You probably read before that you can set a <a href="https://fastapi.tiangolo.com/tutorial/response-status-code/" target="_blank">default Response Status Code</a>.
|
||||
|
||||
But in some cases you need to return a different status code than the default.
|
||||
|
||||
## Use case
|
||||
|
||||
For example, imagine that you want to return an HTTP status code of "OK" `200` by default.
|
||||
|
||||
But if the data didn't exist, you want to create it, and return an HTTP status code of "CREATED" `201`.
|
||||
|
||||
But you still want to be able to filter and convert the data you return with a `response_model`.
|
||||
|
||||
For those cases, you can use a `Response` parameter.
|
||||
|
||||
## Use a `Response` parameter
|
||||
|
||||
You can declare a parameter of type `Response` in your *path operation function* (as you can do for cookies and headers).
|
||||
|
||||
And then you can set the `status_code` in that *temporal* response object.
|
||||
|
||||
```Python hl_lines="2 11 14"
|
||||
{!./src/response_change_status_code/tutorial001.py!}
|
||||
```
|
||||
|
||||
And then you can return any object you need, as you normally would (a `dict`, a database model, etc).
|
||||
|
||||
And if you declared a `response_model`, it will still be used to filter and convert the object you returned.
|
||||
|
||||
**FastAPI** will use that *temporal* response to extract the status code (also cookies and headers), and will put them in the final response that contains the value you returned, filtered by any `response_model`.
|
||||
|
||||
You can also declare the `Response` parameter in dependencies, and set the status code in them. But have in mind that the last one to be set will win.
|
||||
@@ -1,40 +0,0 @@
|
||||
## Use a `Response` parameter
|
||||
|
||||
You can declare a parameter of type `Response` in your *path operation function*, the same way you can declare a `Request` parameter.
|
||||
|
||||
And then you can set headers in that *temporal* response object.
|
||||
|
||||
```Python hl_lines="2 8 9"
|
||||
{!./src/response_cookies/tutorial002.py!}
|
||||
```
|
||||
|
||||
And then you can return any object you need, as you normally would (a `dict`, a database model, etc).
|
||||
|
||||
And if you declared a `response_model`, it will still be used to filter and convert the object you returned.
|
||||
|
||||
**FastAPI** will use that *temporal* response to extract the cookies (also headers and status code), and will put them in the final response that contains the value you returned, filtered by any `response_model`.
|
||||
|
||||
You can also declare the `Response` parameter in dependencies, and set cookies (and headers) in them.
|
||||
|
||||
## Return a `Response` directly
|
||||
|
||||
You can also create cookies when returning a `Response` directly in your code.
|
||||
|
||||
To do that, you can create a response as described in <a href="https://fastapi.tiangolo.com/tutorial/response-directly/" target="_blank">Return a Response directly</a>.
|
||||
|
||||
Then set Cookies in it, and then return it:
|
||||
|
||||
```Python hl_lines="10 11 12"
|
||||
{!./src/response_cookies/tutorial001.py!}
|
||||
```
|
||||
|
||||
!!! tip
|
||||
Have in mind that if you return a response directly instead of using the `Response` parameter, FastAPI will return it directly.
|
||||
|
||||
So, you will have to make sure your data is of the correct type. E.g. it is compatible with JSON, if you are returning a `JSONResponse`.
|
||||
|
||||
And also that you are not sending any data that should have been filtered by a `response_model`.
|
||||
|
||||
### More info
|
||||
|
||||
To see all the available parameters and options, check the <a href="https://www.starlette.io/responses/#set-cookie" class="external-link" target="_blank">documentation in Starlette</a>.
|
||||
@@ -1,63 +0,0 @@
|
||||
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/" class="external-link" 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/" class="external-link" target="_blank">Starlette `Response`s</a>.
|
||||
|
||||
Let's say that you want to return <a href="https://en.wikipedia.org/wiki/XML" class="external-link" 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="https://fastapi.tiangolo.com/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.
|
||||
@@ -1,33 +0,0 @@
|
||||
## Use a `Response` parameter
|
||||
|
||||
You can declare a parameter of type `Response` in your *path operation function* (as you can do for cookies), the same way you can declare a `Request` parameter.
|
||||
|
||||
And then you can set headers in that *temporal* response object.
|
||||
|
||||
```Python hl_lines="2 8 9"
|
||||
{!./src/response_headers/tutorial002.py!}
|
||||
```
|
||||
|
||||
And then you can return any object you need, as you normally would (a `dict`, a database model, etc).
|
||||
|
||||
And if you declared a `response_model`, it will still be used to filter and convert the object you returned.
|
||||
|
||||
**FastAPI** will use that *temporal* response to extract the headers (also cookies and status code), and will put them in the final response that contains the value you returned, filtered by any `response_model`.
|
||||
|
||||
You can also declare the `Response` parameter in dependencies, and set headers (and cookies) in them.
|
||||
|
||||
## Return a `Response` directly
|
||||
|
||||
You can also add headers when you return a `Response` directly.
|
||||
|
||||
Create a response as described in <a href="https://fastapi.tiangolo.com/tutorial/response-directly/" target="_blank">Return a Response directly</a> and pass the headers as an additional parameter:
|
||||
|
||||
```Python hl_lines="10 11 12"
|
||||
{!./src/response_headers/tutorial001.py!}
|
||||
```
|
||||
|
||||
## Custom Headers
|
||||
|
||||
Have in mind that custom proprietary headers can be added <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers" class="external-link" target="_blank">using the 'X-' prefix</a>.
|
||||
|
||||
But if you have custom headers that you want a client in a browser to be able to see, you need to add them to your <a href="https://fastapi.tiangolo.com/tutorial/cors/" target="_blank">CORS configurations</a>, using the parameter `expose_headers` documented in <a href="https://www.starlette.io/middleware/#corsmiddleware" class="external-link" target="_blank">Starlette's CORS docs</a>.
|
||||
@@ -1,4 +1,4 @@
|
||||
You can declare the model used for the response with the parameter `response_model` in any of the path operations:
|
||||
You can declare the model used for the response with the parameter `response_model` in any of the *path operations*:
|
||||
|
||||
* `@app.get()`
|
||||
* `@app.post()`
|
||||
@@ -11,7 +11,7 @@ You can declare the model used for the response with the parameter `response_mod
|
||||
```
|
||||
|
||||
!!! note
|
||||
Notice that `response_model` is a parameter of the "decorator" method (`get`, `post`, etc). Not of your path operation function, like all the parameters and body.
|
||||
Notice that `response_model` is a parameter of the "decorator" method (`get`, `post`, etc). Not of your *path operation function*, like all the parameters and body.
|
||||
|
||||
It receives the same type you would declare for a Pydantic model attribute, so, it can be a Pydantic model, but it can also be, e.g. a `list` of Pydantic models, like `List[Item]`.
|
||||
|
||||
@@ -19,7 +19,7 @@ FastAPI will use this `response_model` to:
|
||||
|
||||
* Convert the output data to its type declaration.
|
||||
* Validate the data.
|
||||
* Add a JSON Schema for the response, in the OpenAPI path operation.
|
||||
* Add a JSON Schema for the response, in the OpenAPI *path operation*.
|
||||
* Will be used by the automatic documentation systems.
|
||||
|
||||
But most importantly:
|
||||
@@ -47,7 +47,7 @@ Now, whenever a browser is creating a user with a password, the API will return
|
||||
|
||||
In this case, it might not be a problem, because the user himself is sending the password.
|
||||
|
||||
But if we use the same model for another path operation, we could be sending our user's passwords to every client.
|
||||
But if we use the same model for another *path operation*, we could be sending our user's passwords to every client.
|
||||
|
||||
!!! danger
|
||||
Never send the plain password of a user in a response.
|
||||
@@ -60,7 +60,7 @@ We can instead create an input model with the plaintext password and an output m
|
||||
{!./src/response_model/tutorial003.py!}
|
||||
```
|
||||
|
||||
Here, even though our path operation function is returning the same input user that contains the password:
|
||||
Here, even though our *path operation function* is returning the same input user that contains the password:
|
||||
|
||||
```Python hl_lines="22"
|
||||
{!./src/response_model/tutorial003.py!}
|
||||
@@ -192,6 +192,6 @@ If you forget to use a `set` and use a `list` or `tuple` instead, FastAPI will s
|
||||
|
||||
## Recap
|
||||
|
||||
Use the path operation decorator's parameter `response_model` to define response models and especially to ensure private data is filtered out.
|
||||
Use the *path operation decorator's* parameter `response_model` to define response models and especially to ensure private data is filtered out.
|
||||
|
||||
Use `response_model_exclude_unset` to return only the values explicitly set.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
The same way you can specify a response model, you can also declare the HTTP status code used for the response with the parameter `status_code` in any of the path operations:
|
||||
The same way you can specify a response model, you can also declare the HTTP status code used for the response with the parameter `status_code` in any of the *path operations*:
|
||||
|
||||
* `@app.get()`
|
||||
* `@app.post()`
|
||||
@@ -11,7 +11,7 @@ The same way you can specify a response model, you can also declare the HTTP sta
|
||||
```
|
||||
|
||||
!!! note
|
||||
Notice that `status_code` is a parameter of the "decorator" method (`get`, `post`, etc). Not of your path operation function, like all the parameters and body.
|
||||
Notice that `status_code` is a parameter of the "decorator" method (`get`, `post`, etc). Not of your *path operation function*, like all the parameters and body.
|
||||
|
||||
The `status_code` parameter receives a number with the HTTP status code.
|
||||
|
||||
@@ -76,4 +76,4 @@ They are just a convenience, they hold the same number, but that way you can use
|
||||
|
||||
## Changing the default
|
||||
|
||||
Later, in a more advanced part of the tutorial/user guide, you will see how to <a href="https://fastapi.tiangolo.com/tutorial/response-change-status-code/" target="_blank">return a different status code than the default</a> you are declaring here.
|
||||
Later, in the **Advanced User Guide**, you will see how to return a different status code than the default you are declaring here.
|
||||
|
||||
@@ -48,7 +48,7 @@ You will see something like this:
|
||||
!!! check "Authorize button!"
|
||||
You already have a shinny new "Authorize" button.
|
||||
|
||||
And your path operation has a little lock in the top-right corner that you can click.
|
||||
And your *path operation* has a little lock in the top-right corner that you can click.
|
||||
|
||||
And if you click it, you have a little authorization form to type a `username` and `password` (and other optional fields):
|
||||
|
||||
@@ -112,7 +112,7 @@ In this example we are going to use **OAuth2**, with the **Password** flow, usin
|
||||
{!./src/security/tutorial001.py!}
|
||||
```
|
||||
|
||||
It doesn't create that endpoint / path operation, but declares that that URL is the one that the client should use to get the token. That information is used in OpenAPI, and then in the interactive API documentation systems.
|
||||
It doesn't create that endpoint / *path operation*, but declares that that URL is the one that the client should use to get the token. That information is used in OpenAPI, and then in the interactive API documentation systems.
|
||||
|
||||
!!! info
|
||||
If you are a very strict "Pythonista" you might dislike the style of the parameter name `tokenUrl` instead of `token_url`.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
In the previous chapter the security system (which is based on the dependency injection system) was giving the path operation function a `token` as a `str`:
|
||||
In the previous chapter the security system (which is based on the dependency injection system) was giving the *path operation function* a `token` as a `str`:
|
||||
|
||||
```Python hl_lines="10"
|
||||
{!./src/security/tutorial001.py!}
|
||||
@@ -26,7 +26,7 @@ Remember that dependencies can have sub-dependencies?
|
||||
|
||||
`get_current_user` will have a dependency with the same `oauth2_scheme` we created before.
|
||||
|
||||
The same as we were doing before in the path operation directly, our new dependency `get_current_user` will receive a `token` as a `str` from the sub-dependency `oauth2_scheme`:
|
||||
The same as we were doing before in the *path operation* directly, our new dependency `get_current_user` will receive a `token` as a `str` from the sub-dependency `oauth2_scheme`:
|
||||
|
||||
```Python hl_lines="25"
|
||||
{!./src/security/tutorial002.py!}
|
||||
@@ -42,7 +42,7 @@ The same as we were doing before in the path operation directly, our new depende
|
||||
|
||||
## Inject the current user
|
||||
|
||||
So now we can use the same `Depends` with our `get_current_user` in the path operation:
|
||||
So now we can use the same `Depends` with our `get_current_user` in the *path operation*:
|
||||
|
||||
```Python hl_lines="31"
|
||||
{!./src/security/tutorial002.py!}
|
||||
@@ -65,7 +65,7 @@ This will help us inside of the function with all the completion and type checks
|
||||
|
||||
## Other models
|
||||
|
||||
You can now get the current user directly in the path operation functions and deal with the security mechanisms at the **Dependency Injection** level, using `Depends`.
|
||||
You can now get the current user directly in the *path operation functions* and deal with the security mechanisms at the **Dependency Injection** level, using `Depends`.
|
||||
|
||||
And you can use any model or data for the security requirements (in this case, a Pydantic model `User`).
|
||||
|
||||
@@ -82,7 +82,7 @@ Just use any kind of model, any kind of class, any kind of database that you nee
|
||||
|
||||
## Code size
|
||||
|
||||
This example might seem verbose. Have in mind that we are mixing security, data models utility functions and path operations in the same file.
|
||||
This example might seem verbose. Have in mind that we are mixing security, data models utility functions and *path operations* in the same file.
|
||||
|
||||
But here's the key point.
|
||||
|
||||
@@ -90,11 +90,11 @@ The security and dependency injection stuff is written once.
|
||||
|
||||
And you can make it as complex as you want. And still, have it written only once, in a single place. With all the flexibility.
|
||||
|
||||
But you can have thousands of endpoints (path operations) using the same security system.
|
||||
But you can have thousands of endpoints (*path operations*) using the same security system.
|
||||
|
||||
And all of them (or any portion of them that you want) can take the advantage of re-using these dependencies or any other dependencies you create.
|
||||
|
||||
And all these thousands of path operations can be as small as 3 lines:
|
||||
And all these thousands of *path operations* can be as small as 3 lines:
|
||||
|
||||
```Python hl_lines="30 31 32"
|
||||
{!./src/security/tutorial002.py!}
|
||||
@@ -102,10 +102,10 @@ And all these thousands of path operations can be as small as 3 lines:
|
||||
|
||||
## Recap
|
||||
|
||||
You can now get the current user directly in your path operation function.
|
||||
You can now get the current user directly in your *path operation function*.
|
||||
|
||||
We are already halfway there.
|
||||
|
||||
We just need to add a path operation for the user/client to actually send the `username` and `password`.
|
||||
We just need to add a *path operation* for the user/client to actually send the `username` and `password`.
|
||||
|
||||
That comes next.
|
||||
That comes next.
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
For the simplest cases, you can use HTTP Basic Auth.
|
||||
|
||||
In HTTP Basic Auth, the application expects a header that contains a username and a password.
|
||||
|
||||
If it doesn't receive it, it returns an HTTP 401 "Unauthorized" error.
|
||||
|
||||
And returns a header `WWW-Authenticate` with a value of `Basic`, and an optional `realm` parameter.
|
||||
|
||||
That tells the browser to show the integrated prompt for a username and password.
|
||||
|
||||
Then, when you type that username and password, the browser sends them in the header automatically.
|
||||
|
||||
## Simple HTTP Basic Auth
|
||||
|
||||
* Import `HTTPBasic` and `HTTPBasicCredentials`.
|
||||
* Create a "`security` scheme" using `HTTPBasic`.
|
||||
* Use that `security` with a dependency in your *path operation*.
|
||||
* It returns an object of type `HTTPBasicCredentials`:
|
||||
* It contains the `username` and `password` sent.
|
||||
|
||||
```Python hl_lines="2 6 10"
|
||||
{!./src/security/tutorial006.py!}
|
||||
```
|
||||
|
||||
When you try to open the URL for the first time (or click the "Execute" button in the docs) the browser will ask you for your username and password:
|
||||
|
||||
<img src="/img/tutorial/security/image12.png">
|
||||
|
||||
## Check the username
|
||||
|
||||
Here's a more complete example.
|
||||
|
||||
Use a dependency to check if the username and password are correct.
|
||||
|
||||
For this, use the Python standard module <a href="https://docs.python.org/3/library/secrets.html" class="external-link" target="_blank">`secrets`</a> to check the username and password:
|
||||
|
||||
```Python hl_lines="1 13 14 15"
|
||||
{!./src/security/tutorial007.py!}
|
||||
```
|
||||
|
||||
This will ensure that `credentials.username` is `"stanleyjobson"`, and that `credentials.password` is `"swordfish"`. This would be similar to:
|
||||
|
||||
```Python
|
||||
if not (credentials.username == "stanleyjobson") or not (credentials.password == "swordfish"):
|
||||
# Return some error
|
||||
...
|
||||
```
|
||||
|
||||
But by using the `secrets.compare_digest()` it will be secure against a type of attacks called "timing attacks".
|
||||
|
||||
### Timing Attacks
|
||||
|
||||
But what's a "timing attack"?
|
||||
|
||||
Let's imagine an attacker is trying to guess the username and password.
|
||||
|
||||
And that attacker sends a request with a username `johndoe` and a password `love123`.
|
||||
|
||||
Then the Python code in your application would be equivalent to something like:
|
||||
|
||||
```Python
|
||||
if "johndoe" == "stanleyjobson" and "love123" == "swordfish":
|
||||
...
|
||||
```
|
||||
|
||||
But right at the moment Python compares the first `j` in `johndoe` to the first `s` in `stanleyjobson`, it will return `False`, because it already knows that those two strings are not the same, thinking that "there's no need to waste more computation comparing the rest of the letters". And your application will say "incorrect user or password".
|
||||
|
||||
But then the attacker tries with username `stanleyjobsox` and password `love123`.
|
||||
|
||||
And your application code does something like:
|
||||
|
||||
```Python
|
||||
if "stanleyjobsox" == "stanleyjobson" and "love123" == "swordfish":
|
||||
...
|
||||
```
|
||||
|
||||
Python will have to compare the whole `stanleyjobso` in both `stanleyjobsox` and `stanleyjobson` before realizing that both strings are not the same. So it will take some extra microseconds to reply back "incorrect user or password".
|
||||
|
||||
#### The time to answer helps the attacker
|
||||
|
||||
At that point, by noticing that the server took some microseconds longer to send the "incorrect user or password" response, the attacker will know that she/he got _something_ right, some of the initial letters were right.
|
||||
|
||||
And then she/he can try again knowing that it's probably something more similar to `stanleyjobsox` than to `johndoe`.
|
||||
|
||||
#### A "professional" attack
|
||||
|
||||
Of course, the attacker would not try all this by hand, she/he would write a program to do it, possibly with thousands or millions of tests per second. And would get just one extra correct letter at a time.
|
||||
|
||||
But doing that, in some minutes or hours the attacker would have guessed the correct username and password, with the "help" of our application, just using the time taken to answer.
|
||||
|
||||
#### Fix it with `secrets.compare_digest()`
|
||||
|
||||
But in our code we are actually using `secrets.compare_digest()`.
|
||||
|
||||
In short, it will take the same time to compare `stanleyjobsox` to `stanleyjobson` than it takes to compare `johndoe` to `stanleyjobson`. And the same for the password.
|
||||
|
||||
That way, using `secrets.compare_digest()` in your application code, it will be safe against this whole range of security attacks.
|
||||
|
||||
### Return the error
|
||||
|
||||
After detecting that the credentials are incorrect, return an `HTTPException` with a status code 401 (the same returned when no credentials are provided) and add the header `WWW-Authenticate` to make the browser show the login prompt again:
|
||||
|
||||
```Python hl_lines="16 17 18 19 20"
|
||||
{!./src/security/tutorial007.py!}
|
||||
```
|
||||
@@ -18,9 +18,9 @@ It is not encrypted, so, anyone could recover the information from the contents.
|
||||
|
||||
But it's signed. So, when you receive a token that you emitted, you can verify that you actually emitted it.
|
||||
|
||||
That way, you can create a token with an expiration of, let's say, 1 week, and then, after a week, when the user comes back with the token, you know he's still signed into your system.
|
||||
That way, you can create a token with an expiration of, let's say, 1 week. And then when the user comes back the next day with the token, you know she/he is still signed into your system.
|
||||
|
||||
And after a week, the token will be expired. And if the user (or a third party) tried to modify the token to change the expiration, you would be able to discover it, because the signatures would not match.
|
||||
And after a week, the token will be expired and the user will not be authorized and will have to sign in again to get a new token. And if the user (or a third party) tried to modify the token to change the expiration, you would be able to discover it, because the signatures would not match.
|
||||
|
||||
If you want to play with JWT tokens and see how they work, check <a href="https://jwt.io/" class="external-link" target="_blank">https://jwt.io</a>.
|
||||
|
||||
@@ -131,7 +131,7 @@ If the token is invalid, return an HTTP error right away.
|
||||
{!./src/security/tutorial004.py!}
|
||||
```
|
||||
|
||||
## Update the `/token` path operation
|
||||
## Update the `/token` *path operation*
|
||||
|
||||
Create a `timedelta` with the expiration time of the token.
|
||||
|
||||
@@ -211,7 +211,7 @@ You can use them to add a specific set of permissions to a JWT token.
|
||||
|
||||
Then you can give this token to a user directly or a third party, to interact with your API with a set of restrictions.
|
||||
|
||||
You can learn how to use them and how they are integrated into **FastAPI** in the next chapter.
|
||||
You can learn how to use them and how they are integrated into **FastAPI** later in the **Advanced User Guide**.
|
||||
|
||||
## Recap
|
||||
|
||||
@@ -229,10 +229,8 @@ It gives you all the flexibility to choose the ones that fit your project the be
|
||||
|
||||
And you can use directly many well maintained and widely used packages like `passlib` and `pyjwt`, because **FastAPI** doesn't require any complex mechanisms to integrate external packages.
|
||||
|
||||
But it provides you the tools to simplify the process as much as possible without compromising flexibility, robustness or security.
|
||||
But it provides you the tools to simplify the process as much as possible without compromising flexibility, robustness, or security.
|
||||
|
||||
And you can use and implement secure, standard protocols, like OAuth2 in a relatively simple way.
|
||||
|
||||
In the next (optional) section you can see how to extend this even further, using OAuth2 "scopes", for a more fine-grained permission system, following these same standards.
|
||||
|
||||
OAuth2 with scopes (explained in the next section) is the mechanism used by many big authentication providers, like Facebook, Google, GitHub, Microsoft, Twitter, etc.
|
||||
You can learn more in the **Advanced User Guide** about how to use OAuth2 "scopes", for a more fine-grained permission system, following these same standards. OAuth2 with scopes is the mechanism used by many big authentication providers, like Facebook, Google, GitHub, Microsoft, Twitter, etc. to authorize third party applications to interact with their APIs on behalf of their users.
|
||||
|
||||
@@ -1,250 +0,0 @@
|
||||
You can use OAuth2 scopes directly with **FastAPI**, they are integrated to work seamlessly.
|
||||
|
||||
This would allow you to have a more fine-grained permission system, following the OAuth2 standard, integrated into your OpenAPI application (and the API docs).
|
||||
|
||||
OAuth2 with scopes is the mechanism used by many big authentication providers, like Facebook, Google, GitHub, Microsoft, Twitter, etc. They use it to provide specific permissions to users and applications.
|
||||
|
||||
Every time you "log in with" Facebook, Google, GitHub, Microsoft, Twitter, that application is using OAuth2 with scopes.
|
||||
|
||||
In this section you will see how to manage authentication and authorization with the same OAuth2 with scopes in your **FastAPI** application.
|
||||
|
||||
!!! warning
|
||||
This is a more or less advanced section. If you are just starting, you can skip it.
|
||||
|
||||
You don't necessarily need OAuth2 scopes, and you can handle authentication and authorization however you want.
|
||||
|
||||
But OAuth2 with scopes can be nicely integrated into your API (with OpenAPI) and your API docs.
|
||||
|
||||
Nevertheless, you still enforce those scopes, or any other security/authorization requirement, however you need, in your code.
|
||||
|
||||
In many cases, OAuth2 with scopes can be an overkill.
|
||||
|
||||
But if you know you need it, or you are curious, keep reading.
|
||||
|
||||
## OAuth2 scopes and OpenAPI
|
||||
|
||||
The OAuth2 specification defines "scopes" as a list of strings separated by spaces.
|
||||
|
||||
The content of each of these strings can have any format, but should not contain spaces.
|
||||
|
||||
These scopes represent "permissions".
|
||||
|
||||
In OpenAPI (e.g. the API docs), you can define "security schemes", the same as you saw in the previous sections.
|
||||
|
||||
When one of these security schemes uses OAuth2, you can also declare and use scopes.
|
||||
|
||||
## Global view
|
||||
|
||||
First, let's quickly see the parts that change from the previous section about OAuth2 and JWT. Now using OAuth2 scopes:
|
||||
|
||||
```Python hl_lines="2 5 9 13 48 66 107 109 110 111 112 113 114 115 116 117 123 124 125 126 130 131 132 133 134 135 136 141 155"
|
||||
{!./src/security/tutorial005.py!}
|
||||
```
|
||||
|
||||
Now let's review those changes step by step.
|
||||
|
||||
## OAuth2 Security scheme
|
||||
|
||||
The first change is that now we are declaring the OAuth2 security scheme with two available scopes, `me` and `items`.
|
||||
|
||||
The `scopes` parameter receives a `dict` with each scope as a key and the description as the value:
|
||||
|
||||
```Python hl_lines="64 65 66 67"
|
||||
{!./src/security/tutorial005.py!}
|
||||
```
|
||||
|
||||
Because we are now declaring those scopes, they will show up in the API docs when you log-in/authorize.
|
||||
|
||||
And you will be able to select which scopes you want to give access to: `me` and `items`.
|
||||
|
||||
This is the same mechanism used when you give permissions while logging in with Facebook, Google, GitHub, etc:
|
||||
|
||||
<img src="/img/tutorial/security/image11.png">
|
||||
|
||||
## JWT token with scopes
|
||||
|
||||
Now, modify the token *path operation* to return the scopes requested.
|
||||
|
||||
We are still using the same `OAuth2PasswordRequestForm`. It includes a property `scopes` with a `list` of `str`, with each scope it received in the request.
|
||||
|
||||
And we return the scopes as part of the JWT token.
|
||||
|
||||
!!! danger
|
||||
For simplicity, here we are just adding the scopes received directly to the token.
|
||||
|
||||
But in your application, for security, you should make sure you only add the scopes that the user is actually able to have, or the ones you have predefined.
|
||||
|
||||
```Python hl_lines="156"
|
||||
{!./src/security/tutorial005.py!}
|
||||
```
|
||||
|
||||
## Declare scopes in *path operations* and dependencies
|
||||
|
||||
Now we declare that the *path operation* for `/users/me/items/` requires the scope `items`.
|
||||
|
||||
For this, we import and use `Security` from `fastapi`.
|
||||
|
||||
You can use `Security` to declare dependencies (just like `Depends`), but `Security` also receives a parameter `scopes` with a list of scopes (strings).
|
||||
|
||||
In this case, we pass a dependency function `get_current_active_user` to `Security` (the same way we would do with `Depends`).
|
||||
|
||||
But we also pass a `list` of scopes, in this case with just one scope: `items` (it could have more).
|
||||
|
||||
And the dependency function `get_current_active_user` can also declare sub-dependencies, not only with `Depends` but also with `Security`. Declaring its own sub-dependency function (`get_current_user`), and more scope requirements.
|
||||
|
||||
In this case, it requires the scope `me` (it could require more than one scope).
|
||||
|
||||
!!! note
|
||||
You don't necessarily need to add different scopes in different places.
|
||||
|
||||
We are doing it here to demonstrate how **FastAPI** handles scopes declared at different levels.
|
||||
|
||||
```Python hl_lines="5 141 168"
|
||||
{!./src/security/tutorial005.py!}
|
||||
```
|
||||
|
||||
!!! info "Technical Details"
|
||||
`Security` is actually a subclass of `Depends`, and it has just one extra parameter that we'll see later.
|
||||
|
||||
But by using `Security` instead of `Depends`, **FastAPI** will know that it can declare security scopes, use them internally, and document the API with OpenAPI.
|
||||
|
||||
But when you import `Query`, `Path`, `Depends`, `Security` and others from `fastapi`, <a href="https://fastapi.tiangolo.com/tutorial/path-params-numeric-validations/#recap" target="_blank">those are actually functions that return classes of the same name</a>.
|
||||
|
||||
## Use `SecurityScopes`
|
||||
|
||||
Now update the dependency `get_current_user`.
|
||||
|
||||
This is the one used by the dependencies above.
|
||||
|
||||
Here's were we are using the same OAuth2 scheme we created before, declaring it as a dependency: `oauth2_scheme`.
|
||||
|
||||
Because this dependency function doesn't have any scope requirements itself, we can use `Depends` with `oauth2_scheme`, we don't have to use `Security` when we don't need to specify security scopes.
|
||||
|
||||
We also declare a special parameter of type `SecurityScopes`, imported from `fastapi.security`.
|
||||
|
||||
This `SecurityScopes` class is similar to `Request` (`Request` was used to get the request object directly).
|
||||
|
||||
```Python hl_lines="9 107"
|
||||
{!./src/security/tutorial005.py!}
|
||||
```
|
||||
|
||||
## Use the `scopes`
|
||||
|
||||
The parameter `security_scopes` will be of type `SecurityScopes`.
|
||||
|
||||
It will have a property `scopes` with a list containing all the scopes required by itself and all the dependencies that use this as a sub-dependency. That means, all the "dependants"... this might sound confusing, it is explained again later below.
|
||||
|
||||
The `security_scopes` object (of class `SecurityScopes`) also provides a `scope_str` attribute with a single string, containing those scopes separated by spaces (we are going to use it).
|
||||
|
||||
We create an `HTTPException` that we can re-use (`raise`) later at several points.
|
||||
|
||||
In this exception, we include the scopes required (if any) as a string separated by spaces (using `scope_str`). We put that string containing the scopes in in the `WWW-Authenticate` header (this is part of the spec).
|
||||
|
||||
```Python hl_lines="107 109 110 111 112 113 114 115 116 117"
|
||||
{!./src/security/tutorial005.py!}
|
||||
```
|
||||
|
||||
## Verify the `username` and data shape
|
||||
|
||||
We verify that we get a `username`, and extract the scopes.
|
||||
|
||||
And then we validate that data with the Pydantic model (catching the `ValidationError` exception), and if we get an error reading the JWT token or validating the data with Pydantic, we raise the `HTTPException` we created before.
|
||||
|
||||
For that, we update the Pydantic model `TokenData` with a new property `scopes`.
|
||||
|
||||
By validating the data with Pydantic we can make sure that we have, for example, exactly a `list` of `str` with the scopes and a `str` with the `username`.
|
||||
|
||||
Instead of, for example, a `dict`, or something else, as it could break the application at some point later, making it a security risk.
|
||||
|
||||
We also verify that we have a user with that username, and if not, we raise that same exception we created before.
|
||||
|
||||
```Python hl_lines="48 118 119 120 121 122 123 124 125 126 127 128 129"
|
||||
{!./src/security/tutorial005.py!}
|
||||
```
|
||||
|
||||
## Verify the `scopes`
|
||||
|
||||
We now verify that all the scopes required, by this dependency and all the dependants (including *path operations*), are included in the scopes provided in the token received, otherwise raise an `HTTPException`.
|
||||
|
||||
For this, we use `security_scopes.scopes`, that contains a `list` with all these scopes as `str`.
|
||||
|
||||
```Python hl_lines="130 131 132 133 134 135 136"
|
||||
{!./src/security/tutorial005.py!}
|
||||
```
|
||||
|
||||
## Dependency tree and scopes
|
||||
|
||||
Let's review again this dependency tree and the scopes.
|
||||
|
||||
As the `get_current_active_user` dependency has as a sub-dependency on `get_current_user`, the scope `"me"` declared at `get_current_active_user` will be included in the list of required scopes in the `security_scopes.scopes` passed to `get_current_user`.
|
||||
|
||||
The *path operation* itself also declares a scope, `"items"`, so this will also be in the list of `security_scopes.scopes` passed to `get_current_user`.
|
||||
|
||||
Here's how the hierarchy of dependencies and scopes looks like:
|
||||
|
||||
* The *path operation* `read_own_items` has:
|
||||
* Required scopes `["items"]` with the dependency:
|
||||
* `get_current_active_user`:
|
||||
* The dependency function `get_current_active_user` has:
|
||||
* Required scopes `["me"]` with the dependency:
|
||||
* `get_current_user`:
|
||||
* The dependency function `get_current_user` has:
|
||||
* No scopes required by itself.
|
||||
* A dependency using `oauth2_scheme`.
|
||||
* A `security_scopes` parameter of type `SecurityScopes`:
|
||||
* This `security_scopes` parameter has a property `scopes` with a `list` containing all these scopes declared above, so:
|
||||
* `security_scopes.scopes` will contain `["me", "items"]` for the *path operation* `read_own_items`.
|
||||
* `security_scopes.scopes` will contain `["me"]` for the *path operation* `read_users_me`, because it is declared in the dependency `get_current_active_user`.
|
||||
* `security_scopes.scopes` will contain `[]` (nothing) for the *path operation* `read_system_status`, because it didn't declare any `Security` with `scopes`, and its dependency, `get_current_user`, doesn't declare any `scope` either.
|
||||
|
||||
!!! tip
|
||||
The important and "magic" thing here is that `get_current_user` will have a different list of `scopes` to check for each *path operation*.
|
||||
|
||||
All depending on the `scopes` declared in each *path operation* and each dependency in the dependency tree for that specific path operation.
|
||||
|
||||
## More details about `SecurityScopes`
|
||||
|
||||
You can use `SecurityScopes` at any point, and in multiple places, it doesn't have to be at the "root" dependency.
|
||||
|
||||
It will always have the security scopes declared in the current `Security` dependencies and all the dependants for **that specific** *path operation* and **that specific** dependency tree.
|
||||
|
||||
Because the `SecurityScopes` will have all the scopes declared by dependants, you can use it to verify that a token has the required scopes in a central dependency function, and then declare different scope requirements in different *path operations*.
|
||||
|
||||
They will be checked independently for each path operation.
|
||||
|
||||
## Check it
|
||||
|
||||
If you open the API docs, you can authenticate and specify which scopes you want to authorize.
|
||||
|
||||
<img src="/img/tutorial/security/image11.png">
|
||||
|
||||
If you don't select any scope, you will be "authenticated", but when you try to access `/users/me/` or `/users/me/items/` you will get an error saying that you don't have enough permissions. You will still be able to access `/status/`.
|
||||
|
||||
And if you select the scope `me` but not the scope `items`, you will be able to access `/users/me/` but not `/users/me/items/`.
|
||||
|
||||
That's what would happen to a third party application that tried to access one of these *path operations* with a token provided by a user, depending on how many permissions the user gave the application.
|
||||
|
||||
## About third party integrations
|
||||
|
||||
In this example we are using the OAuth2 "password" flow.
|
||||
|
||||
This is appropriate when we are logging in to our own application, probably with our own frontend.
|
||||
|
||||
Because we can trust it to receive the `username` and `password`, as we control it.
|
||||
|
||||
But if you are building an OAuth2 application that others would connect to (i.e., if you are building an authentication provider equivalent to Facebook, Google, GitHub, etc.) you should use one of the other flows.
|
||||
|
||||
The most common is the implicit flow.
|
||||
|
||||
The most secure is the code flow, but is more complex to implement as it requires more steps. As it is more complex, many providers end up suggesting the implicit flow.
|
||||
|
||||
!!! note
|
||||
It's common that each authentication provider names their flows in a different way, to make it part of their brand.
|
||||
|
||||
But in the end, they are implementing the same OAuth2 standard.
|
||||
|
||||
**FastAPI** includes utilities for all these OAuth2 authentication flows in `fastapi.security.oauth2`.
|
||||
|
||||
## `Security` in decorator `dependencies`
|
||||
|
||||
The same way you can define a `list` of <a href="https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/" target="_blank">`Depends` in the decorator's `dependencies` parameter</a>, you could also use `Security` with `scopes` there.
|
||||
@@ -12,7 +12,7 @@ But don't worry, you can show it as you wish to your final users in the frontend
|
||||
|
||||
And your database models can use any other names you want.
|
||||
|
||||
But for the login path operation, we need to use these names to be compatible with the spec (and be able to, for example, use the integrated API documentation system).
|
||||
But for the login *path operation*, we need to use these names to be compatible with the spec (and be able to, for example, use the integrated API documentation system).
|
||||
|
||||
The spec also states that the `username` and `password` must be sent as form data (so, no JSON here).
|
||||
|
||||
@@ -26,14 +26,14 @@ Each "scope" is just a string (without spaces).
|
||||
|
||||
They are normally used to declare specific security permissions, for example:
|
||||
|
||||
* `"users:read"` or `"users:write"` are common examples.
|
||||
* `users:read` or `users:write` are common examples.
|
||||
* `instagram_basic` is used by Facebook / Instagram.
|
||||
* `https://www.googleapis.com/auth/drive` is used by Google.
|
||||
|
||||
!!! info
|
||||
In OAuth2 a "scope" is just a string that declares a specific permission required.
|
||||
|
||||
It doesn't matter if it has other characters like `:`, or if it is a URL.
|
||||
It doesn't matter if it has other characters like `:` or if it is a URL.
|
||||
|
||||
Those details are implementation specific.
|
||||
|
||||
@@ -114,7 +114,7 @@ But you cannot convert from the gibberish back to the password.
|
||||
|
||||
If your database is stolen, the thief won't have your users' plaintext passwords, only the hashes.
|
||||
|
||||
So, the thief won't be able to try to use that password in another system (as many users use the same password everywhere, this would be dangerous).
|
||||
So, the thief won't be able to try to use those same passwords in another system (as many users use the same password everywhere, this would be dangerous).
|
||||
|
||||
```Python hl_lines="79 80 81 82"
|
||||
{!./src/security/tutorial003.py!}
|
||||
@@ -137,7 +137,7 @@ UserInDB(
|
||||
```
|
||||
|
||||
!!! info
|
||||
For a more complete explanation of `**user_dict` check back in <a href="/tutorial/extra-models/#about-user_indict" target="_blank">the documentation for **Extra Models**</a>.
|
||||
For a more complete explanation of `**user_dict` check back in [the documentation for **Extra Models**](../extra-models.md#about-user_indict){.internal-link target=_blank}.
|
||||
|
||||
## Return the token
|
||||
|
||||
|
||||
@@ -1,534 +0,0 @@
|
||||
!!! warning
|
||||
If you are just starting, the <a href="https://fastapi.tiangolo.com/tutorial/sql-databases/" target="_blank">SQLAlchemy tutorial</a> should be enough.
|
||||
|
||||
Feel free to skip this.
|
||||
|
||||
If you are starting a project from scratch, you are probably better off with <a href="https://fastapi.tiangolo.com/tutorial/sql-databases/" target="_blank">SQLAlchemy ORM</a>, or any other async ORM.
|
||||
|
||||
If you already have a code base that uses <a href="http://docs.peewee-orm.com/en/latest/" class="external-link" target="_blank">Peewee ORM</a>, you can check here how to use it with **FastAPI**.
|
||||
|
||||
!!! warning "Python 3.7+ required"
|
||||
You will need Python 3.7 or above to safely use Peewee with FastAPI.
|
||||
|
||||
## Peewee for async
|
||||
|
||||
Peewee was not designed for async frameworks, or with them in mind.
|
||||
|
||||
Peewee has some heavy assumptions about its defaults and about how it should be used.
|
||||
|
||||
If you are developing an application with an older non-async framework, and can work with all its defaults, **it can be a great tool**.
|
||||
|
||||
But if you need to change some of the defaults, support more than one predefined database, work with an async framework (like FastAPI), etc, you will need to add quite some complex extra code to override those defaults.
|
||||
|
||||
Nevertheless, it's possible to do it, and here you'll see exactly what code you have to add to be able to use Peewee with FastAPI.
|
||||
|
||||
!!! note "Technical Details"
|
||||
You can read more about Peewee's stand about async in Python <a href="http://docs.peewee-orm.com/en/latest/peewee/database.html#async-with-gevent" target="_blank">in the docs</a>, <a href="https://github.com/coleifer/peewee/issues/263#issuecomment-517347032" target="_blank">an issue</a>, <a href="https://github.com/coleifer/peewee/pull/2072#issuecomment-563215132" class="external-link" target="_blank">a PR</a>.
|
||||
|
||||
## The same app
|
||||
|
||||
We are going to create the same application as in the <a href="https://fastapi.tiangolo.com/tutorial/sql-databases/" target="_blank">SQLAlchemy tutorial</a>.
|
||||
|
||||
Most of the code is actually the same.
|
||||
|
||||
So, we are going to focus only on the differences.
|
||||
|
||||
## File structure
|
||||
|
||||
Let's say you have a directory named `my_super_project` that contains a sub-directory called `sql_app` with a structure like this:
|
||||
|
||||
```
|
||||
.
|
||||
└── sql_app
|
||||
├── __init__.py
|
||||
├── crud.py
|
||||
├── database.py
|
||||
├── main.py
|
||||
└── schemas.py
|
||||
```
|
||||
|
||||
This is almost the same structure as we had for the SQLAlchemy tutorial.
|
||||
|
||||
Now let's see what each file/module does.
|
||||
|
||||
## Create the Peewee parts
|
||||
|
||||
Let's refer to the file `sql_app/database.py`.
|
||||
|
||||
### The standard Peewee code
|
||||
|
||||
Let's first check all the normal Peewee code, create a Peewee database:
|
||||
|
||||
```Python hl_lines="3 5 22"
|
||||
{!./src/sql_databases_peewee/sql_app/database.py!}
|
||||
```
|
||||
|
||||
!!! tip
|
||||
Have in mind that if you wanted to use a different database, like PostgreSQL, you couldn't just change the string. You would need to use a different Peewee database class.
|
||||
|
||||
#### Note
|
||||
|
||||
The argument:
|
||||
|
||||
```Python
|
||||
check_same_thread=False
|
||||
```
|
||||
|
||||
is equivalent to the one in the SQLAlchemy tutorial:
|
||||
|
||||
```Python
|
||||
connect_args={"check_same_thread": False}
|
||||
```
|
||||
|
||||
...it is needed only for `SQLite`.
|
||||
|
||||
!!! info "Technical Details"
|
||||
|
||||
Exactly the same technical details as in the <a href="https://fastapi.tiangolo.com/tutorial/sql-databases/#note" target="_blank">SQLAlchemy tutorial</a> apply.
|
||||
|
||||
### Make Peewee async-compatible `PeeweeConnectionState`
|
||||
|
||||
The main issue with Peewee and FastAPI is that Peewee relies heavily on <a href="https://docs.python.org/3/library/threading.html#thread-local-data" class="external-link" target="_blank">Python's `threading.local`</a>, and it doesn't have a direct way to override it or let you handle connections/sessions directly (as is done in the SQLAlchemy tutorial).
|
||||
|
||||
And `threading.local` is not compatible with the new async features of modern Python.
|
||||
|
||||
!!! note "Technical Details"
|
||||
`threading.local` is used to have a "magic" variable that has a different value for each thread.
|
||||
|
||||
This was useful in older frameworks designed to have one single thread per request, no more, no less.
|
||||
|
||||
Using this, each request would have its own database connection/session, which is the actual final goal.
|
||||
|
||||
But FastAPI, using the new async features, could handle more than one request on the same thread. And at the same time, for a single request, it could run multiple things in different threads (in a threadpool), depending on if you use `async def` or normal `def`. This is what gives all the performance improvements to FastAPI.
|
||||
|
||||
But Python 3.7 and above provide a more advanced alternative to `threading.local`, that can also be used in the places where `threading.local` would be used, but is compatible with the new async features.
|
||||
|
||||
We are going to use that. It's called <a href="https://docs.python.org/3/library/contextvars.html" class="external-link" target="_blank">`contextvars`</a>.
|
||||
|
||||
We are going to override the internal parts of Peewee that use `threading.local` and replace them with `contextvars`, with the corresponding updates.
|
||||
|
||||
This might seem a bit complex (and it actually is), you don't really need to completely understand how it works to use it.
|
||||
|
||||
We will create a `PeeweeConnectionState`:
|
||||
|
||||
```Python hl_lines="10 11 12 13 14 15 16 17 18 19"
|
||||
{!./src/sql_databases_peewee/sql_app/database.py!}
|
||||
```
|
||||
|
||||
This class inherits from a special internal class used by Peewee.
|
||||
|
||||
It has all the logic to make Peewee use `contextvars` instead of `threading.local`.
|
||||
|
||||
`contextvars` works a bit differently than `threading.local`. But the rest of Peewee's internal code assumes that this class works with `threading.local`.
|
||||
|
||||
So, we need to do some extra tricks to make it work as if it was just using `threading.local`. The `__init__`, `__setattr__`, and `__getattr__` implement all the required tricks for this to be used by Peewee without knowing that it is now compatible with FastAPI.
|
||||
|
||||
!!! tip
|
||||
This will just make Peewee behave correctly when used with FastAPI. Not randomly opening or closing connections that are being used, creating errors, etc.
|
||||
|
||||
But it doesn't give Peewee async super-powers. You should still use normal `def` functions and not `async def`.
|
||||
|
||||
### Use the custom `PeeweeConnectionState` class
|
||||
|
||||
Now, overwrite the `._state` internal attribute in the Peewee database `db` object using the new `PeeweeConnectionState`:
|
||||
|
||||
```Python hl_lines="24"
|
||||
{!./src/sql_databases_peewee/sql_app/database.py!}
|
||||
```
|
||||
|
||||
!!! tip
|
||||
Make sure you overwrite `db._state` *after* creating `db`.
|
||||
|
||||
!!! tip
|
||||
You would do the same for any other Peewee database, including `PostgresqlDatabase`, `MySQLDatabase`, etc.
|
||||
|
||||
## Create the database models
|
||||
|
||||
Let's now see the file `sql_app/models.py`.
|
||||
|
||||
### Create Peewee models for our data
|
||||
|
||||
Now create the Peewee models (classes) for `User` and `Item`.
|
||||
|
||||
This is the same you would do if you followed the Peewee tutorial and updated the models to have the same data as in the SQLAlchemy tutorial.
|
||||
|
||||
!!! tip
|
||||
Peewee also uses the term "**model**" to refer to these classes and instances that interact with the database.
|
||||
|
||||
But Pydantic also uses the term "**model**" to refer to something different, the data validation, conversion, and documentation classes and instances.
|
||||
|
||||
Import `db` from `database` (the file `database.py` from above) and use it here.
|
||||
|
||||
```Python hl_lines="3 6 7 8 9 10 11 12 15 16 17 18 19 20 21"
|
||||
{!./src/sql_databases_peewee/sql_app/models.py!}
|
||||
```
|
||||
|
||||
!!! tip
|
||||
Peewee creates several magic attributes.
|
||||
|
||||
It will automatically add an `id` attribute as an integer to be the primary key.
|
||||
|
||||
It will chose the name of the tables based on the class names.
|
||||
|
||||
For the `Item`, it will create an attribute `owner_id` with the integer ID of the `User`. But we don't declare it anywhere.
|
||||
|
||||
## Create the Pydantic models
|
||||
|
||||
Now let's check the file `sql_app/schemas.py`.
|
||||
|
||||
!!! tip
|
||||
To avoid confusion between the Peewee *models* and the Pydantic *models*, we will have the file `models.py` with the Peewee models, and the file `schemas.py` with the Pydantic models.
|
||||
|
||||
These Pydantic models define more or less a "schema" (a valid data shape).
|
||||
|
||||
So this will help us avoiding confusion while using both.
|
||||
|
||||
### Create the Pydantic *models* / schemas
|
||||
|
||||
Create all the same Pydantic models as in the SQLAlchemy tutorial:
|
||||
|
||||
```Python hl_lines="16 17 18 21 22 25 26 27 28 29 30 34 35 38 39 42 43 44 45 46 47 48"
|
||||
{!./src/sql_databases_peewee/sql_app/schemas.py!}
|
||||
```
|
||||
|
||||
!!! tip
|
||||
Here we are creating the models with an `id`.
|
||||
|
||||
We didn't explicitly specify an `id` attribute in the Peewee models, but Peewee adds one automatically.
|
||||
|
||||
We are also adding the magic `owner_id` attribute to `Item`.
|
||||
|
||||
### Create a `PeeweeGetterDict` for the Pydantic *models* / schemas
|
||||
|
||||
When you access a relationship in a Peewee object, like in `some_user.items`, Peewee doesn't provide a `list` of `Item`.
|
||||
|
||||
It provides a special custom object of class `ModelSelect`.
|
||||
|
||||
It's possible to create a `list` of its items with `list(some_user.items)`.
|
||||
|
||||
But the object itself is not a `list`. And it's also not an actual Python <a href="https://docs.python.org/3/glossary.html#term-generator" class="external-link" target="_blank">generator</a>. Because of this, Pydantic doesn't know by default how to convert it to a `list` of Pydantic *models* / schemas.
|
||||
|
||||
But recent versions of Pydantic allow providing a custom class that inherits from `pydantic.utils.GetterDict`, to provide the functionality used when using the `orm_mode = True` to retrieve the values for ORM model attributes.
|
||||
|
||||
We are going to create a custom `PeeweeGetterDict` class and use it in all the same Pydantic *models* / schemas that use `orm_mode`:
|
||||
|
||||
```Python hl_lines="3 8 9 10 11 12 13 31 49"
|
||||
{!./src/sql_databases_peewee/sql_app/schemas.py!}
|
||||
```
|
||||
|
||||
Here we are checking if the attribute that is being accessed (e.g. `.items` in `some_user.items`) is an instance of `peewee.ModelSelect`.
|
||||
|
||||
And if that's the case, just return a `list` with it.
|
||||
|
||||
And then we use it in the Pydantic *models* / schemas that use `orm_mode = True`, with the configuration variable `getter_dict = PeeweeGetterDict`.
|
||||
|
||||
!!! tip
|
||||
We only need to create one `PeeweeGetterDict` class, and we can use it in all the Pydantic *models* / schemas.
|
||||
|
||||
## CRUD utils
|
||||
|
||||
Now let's see the file `sql_app/crud.py`.
|
||||
|
||||
### Create all the CRUD utils
|
||||
|
||||
Create all the same CRUD utils as in the SQLAlchemy tutorial, all the code is very similar:
|
||||
|
||||
```Python hl_lines="1 4 5 8 9 12 13 16 17 18 19 20 23 24 27 28 29 30"
|
||||
{!./src/sql_databases_peewee/sql_app/crud.py!}
|
||||
```
|
||||
|
||||
There are some differences with the code for the SQLAlchemy tutorial.
|
||||
|
||||
We don't pass a `db` attribute around. Instead we use the models directly. This is because the `db` object is a global object, that includes all the connection logic. That's why we had to do all the `contextvars` updates above.
|
||||
|
||||
Aso, when returning several objects, like in `get_users`, we directly call `list`, like in:
|
||||
|
||||
```Python
|
||||
list(models.User.select())
|
||||
```
|
||||
|
||||
This is for the same reason that we had to create a custom `PeeweeGetterDict`. But by returning something that is already a `list` instead of the `peewee.ModelSelect` the `response_model` in the path operation with `List[models.User]` (that we'll see later) will work correctly.
|
||||
|
||||
## Main **FastAPI** app
|
||||
|
||||
And now in the file `sql_app/main.py` let's integrate and use all the other parts we created before.
|
||||
|
||||
### Create the database tables
|
||||
|
||||
In a very simplistic way create the database tables:
|
||||
|
||||
```Python hl_lines="10 11 12"
|
||||
{!./src/sql_databases_peewee/sql_app/main.py!}
|
||||
```
|
||||
|
||||
### Create a dependency
|
||||
|
||||
Create a dependency that will connect the database right at the beginning of a request and disconnect it at the end:
|
||||
|
||||
```Python hl_lines="19 20 21 22 23 24 25"
|
||||
{!./src/sql_databases_peewee/sql_app/main.py!}
|
||||
```
|
||||
|
||||
Here we have an empty `yield` because we are actually not using the database object directly.
|
||||
|
||||
It is connecting to the database and storing the connection data in an internal variable that is independent for each request (using the `contextvars` tricks from above).
|
||||
|
||||
And then, in each *path operation function* that needs to access the database we add it as a dependency.
|
||||
|
||||
But we are not using the value given by this dependency (it actually doesn't give any value, as it has an empty `yield`). So, we don't add it to the *path operation function* but to the *path operation decorator* in the `dependencies` parameter:
|
||||
|
||||
```Python hl_lines="36 44 51 63 69 76"
|
||||
{!./src/sql_databases_peewee/sql_app/main.py!}
|
||||
```
|
||||
|
||||
### Context Variable Middleware
|
||||
|
||||
For all the `contextvars` parts to work, we need to make sure there's a new "context" each time there's a new request, so that we have a specific context variable Peewee can use to save its state (database connection, transactions, etc).
|
||||
|
||||
For that, we need to create a middleware.
|
||||
|
||||
Right before the request, we are going to reset the database state. We will "set" a value to the context variable and then we will ask the Peewee database state to "reset" (this will create the default values it uses).
|
||||
|
||||
And then the rest of the request is processed with that new context variable we just set, all automatically and more or less "magically".
|
||||
|
||||
For the **next request**, as we will reset that context variable again in the middleware, that new request will have its own database state (connection, transactions, etc).
|
||||
|
||||
```Python hl_lines="28 29 30 31 32 33"
|
||||
{!./src/sql_databases_peewee/sql_app/main.py!}
|
||||
```
|
||||
|
||||
!!! tip
|
||||
As FastAPI is an async framework, one request could start being processed, and before finishing, another request could be received and start processing as well, and it all could be processed in the same thread.
|
||||
|
||||
But context variables are aware of these async features, so, a Peewee database state set in the middleware will keep its own data throughout the entire request.
|
||||
|
||||
And at the same time, the other concurrent request will have its own database state that will be independent for the whole request.
|
||||
|
||||
#### Peewee Proxy
|
||||
|
||||
|
||||
If you are using a <a href="http://docs.peewee-orm.com/en/latest/peewee/database.html#dynamically-defining-a-database" class="external-link" target="_blank">Peewee Proxy</a>, the actual database is at `db.obj`.
|
||||
|
||||
So, you would reset it with:
|
||||
|
||||
```Python hl_lines="3 4"
|
||||
@app.middleware("http")
|
||||
async def reset_db_middleware(request: Request, call_next):
|
||||
database.db.obj._state._state.set(db_state_default.copy())
|
||||
database.db.obj._state.reset()
|
||||
response = await call_next(request)
|
||||
return response
|
||||
```
|
||||
|
||||
### Create your **FastAPI** *path operations*
|
||||
|
||||
Now, finally, here's the standard **FastAPI** *path operations* code.
|
||||
|
||||
```Python hl_lines="36 37 38 39 40 41 44 45 46 47 50 51 52 53 54 55 56 57 60 61 62 63 64 65 66 69 70 71 72 75 76 77 78 79 80 81 82 83"
|
||||
{!./src/sql_databases_peewee/sql_app/main.py!}
|
||||
```
|
||||
|
||||
### About `def` vs `async def`
|
||||
|
||||
The same as with SQLAlchemy, we are not doing something like:
|
||||
|
||||
```Python
|
||||
user = await models.User.select().first()
|
||||
```
|
||||
|
||||
...but instead we are using:
|
||||
|
||||
```Python
|
||||
user = models.User.select().first()
|
||||
```
|
||||
|
||||
So, again, we should declare the *path operation functions* and the dependency without `async def`, just with a normal `def`, as:
|
||||
|
||||
```Python hl_lines="2"
|
||||
# Something goes here
|
||||
def read_users(skip: int = 0, limit: int = 100):
|
||||
# Something goes here
|
||||
```
|
||||
|
||||
## Testing Peewee with async
|
||||
|
||||
This example includes an extra *path operation* that simulates a long processing request with `time.sleep(sleep_time)`.
|
||||
|
||||
It will have the database connection open at the beginning and will just wait some seconds before replying back. And each new request will wait one second less.
|
||||
|
||||
This will easily let you test that your app with Peewee and FastAPI is behaving correctly with all the stuff about threads.
|
||||
|
||||
If you want to check how Peewee would break your app if used without modification, go the the `sql_app/database.py` file and comment the line:
|
||||
|
||||
```Python
|
||||
# db._state = PeeweeConnectionState()
|
||||
```
|
||||
|
||||
And in the file `sql_app/main.py` file, comment the middleware:
|
||||
|
||||
```Python
|
||||
# @app.middleware("http")
|
||||
# async def reset_db_middleware(request: Request, call_next):
|
||||
# database.db._state._state.set(db_state_default.copy())
|
||||
# database.db._state.reset()
|
||||
# response = await call_next(request)
|
||||
# return response
|
||||
```
|
||||
|
||||
Then run your app with Uvicorn:
|
||||
|
||||
```bash
|
||||
uvicorn sql_app.main:app --reload
|
||||
```
|
||||
|
||||
Open your browser at <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a> and create a couple of users.
|
||||
|
||||
Then open 10 tabs at <a href="http://127.0.0.1:8000/docs#/default/read_slow_users_slowusers__get" class="external-link" target="_blank">http://127.0.0.1:8000/docs#/default/read_slow_users_slowusers__get</a> at the same time.
|
||||
|
||||
Go to the *path operation* "Get `/slowusers/`" in all of the tabs. Use the "Try it out" button and execute the request in each tab, one right after the other.
|
||||
|
||||
The tabs will wait for a bit and then some of them will show `Internal Server Error`.
|
||||
|
||||
### What happens
|
||||
|
||||
The first tab will make your app create a connection to the database and wait for some seconds before replying back and closing the connection.
|
||||
|
||||
Then, for the request in the next tab, your app will wait for one second less, and so on.
|
||||
|
||||
This means that it will end up finishing some of the last tabs' requests than some of the previous ones.
|
||||
|
||||
Then one the last requests that wait less seconds will try to open a database connection, but as one of those previous requests for the other tabs will probably be handled in the same thread as the first one, it will have the same database connection that is already open, and Peewee will throw an error and you will see it in the terminal, and the response will have an `Internal Server Error`.
|
||||
|
||||
This will probably happen for more than one of those tabs.
|
||||
|
||||
If you had multiple clients talking to your app exactly at the same time, this is what could happen.
|
||||
|
||||
And as your app starts to handle more and more clients at the same time, the waiting time in a single request needs to be shorter and shorter to trigger the error.
|
||||
|
||||
### Fix Peewee with FastAPI
|
||||
|
||||
Now go back to the file `sql_app/database.py`, and uncomment the line:
|
||||
|
||||
```Python
|
||||
db._state = PeeweeConnectionState()
|
||||
```
|
||||
|
||||
And in the file `sql_app/main.py` file, uncomment the middleware:
|
||||
|
||||
```Python
|
||||
@app.middleware("http")
|
||||
async def reset_db_middleware(request: Request, call_next):
|
||||
database.db._state._state.set(db_state_default.copy())
|
||||
database.db._state.reset()
|
||||
response = await call_next(request)
|
||||
return response
|
||||
```
|
||||
|
||||
Terminate your running app and start it again.
|
||||
|
||||
Repeat the same process with the 10 tabs. This time all of them will wait and you will get all the results without errors.
|
||||
|
||||
...You fixed it!
|
||||
|
||||
## Review all the files
|
||||
|
||||
Remember you should have a directory named `my_super_project` (or however you want) that contains a sub-directory called `sql_app`.
|
||||
|
||||
`sql_app` should have the following files:
|
||||
|
||||
* `sql_app/__init__.py`: is an empty file.
|
||||
|
||||
* `sql_app/database.py`:
|
||||
|
||||
```Python hl_lines=""
|
||||
{!./src/sql_databases_peewee/sql_app/database.py!}
|
||||
```
|
||||
|
||||
* `sql_app/models.py`:
|
||||
|
||||
```Python hl_lines=""
|
||||
{!./src/sql_databases_peewee/sql_app/models.py!}
|
||||
```
|
||||
|
||||
* `sql_app/schemas.py`:
|
||||
|
||||
```Python hl_lines=""
|
||||
{!./src/sql_databases_peewee/sql_app/schemas.py!}
|
||||
```
|
||||
|
||||
* `sql_app/crud.py`:
|
||||
|
||||
```Python hl_lines=""
|
||||
{!./src/sql_databases_peewee/sql_app/crud.py!}
|
||||
```
|
||||
|
||||
* `sql_app/main.py`:
|
||||
|
||||
```Python hl_lines=""
|
||||
{!./src/sql_databases_peewee/sql_app/main.py!}
|
||||
```
|
||||
|
||||
## Technical Details
|
||||
|
||||
!!! warning
|
||||
These are very technical details that you probably don't need.
|
||||
|
||||
### The problem
|
||||
|
||||
Peewee uses <a href="https://docs.python.org/3/library/threading.html#thread-local-data" class="external-link" target="_blank">`threading.local`</a> by default to store it's database "state" data (connection, transactions, etc).
|
||||
|
||||
`threading.local` creates a value exclusive to the current thread, but an async framework would run all the "tasks" (e.g. requests) in the same thread, and possibly not in order.
|
||||
|
||||
On top of that, an async framework could run some sync code in a threadpool (using `asyncio.run_in_executor`), but belonging to the same "task" (e.g. to the same request).
|
||||
|
||||
This means that, with Peewee's current implementation, multiple tasks could be using the same `threading.local` variable and end up sharing the same connection and data, and at the same time, if they execute sync IO-blocking code in a threadpool (as with normal `def` functions in FastAPI, in *path operations* and dependencies), that code won't have access to the database state variables, even while it's part of the same "task" (request) and it should be able to get access to that.
|
||||
|
||||
### Context variables
|
||||
|
||||
Python 3.7 has <a href="https://docs.python.org/3/library/contextvars.html" class="external-link" target="_blank">`contextvars`</a> that can create a local variable very similar to `threading.local`, but also supporting these async features.
|
||||
|
||||
There are several things to have in mind.
|
||||
|
||||
The `ContextVar` has to be created at the top of the module, like `some_var = ContextVar("some_var", default="default value")`.
|
||||
|
||||
To set a value used in the current "context" (e.g. for the current request) use `some_var.set("new value")`.
|
||||
|
||||
To get a value anywhere inside of the context (e.g. in any part handling the current request) use `some_var.get()`.
|
||||
|
||||
### Set context variables in middleware
|
||||
|
||||
If some part of the async code sets the value with `some_var.set("updated in function")` (e.g. the middleware), the rest of the code in it will see that new value.
|
||||
|
||||
And if it calls any other function with `await some_function()` (e.g. `response = await call_next(request)` in our middleware) that internal `some_function()` (or `response = await call_next(request)` in our example) and everything it calls inside, will see that same new value `"updated in function"`.
|
||||
|
||||
So, in our case, if we set the Peewee state variable in the middleware and then call `response = await call_next(request)` all the rest of the internal code in our app (that is called by `call_next()`) will see this value we set in the middleware and will be able to reuse it.
|
||||
|
||||
But if the value is set in an internal function (e.g. in `get_db()`) that value will be seen only by that internal function and any code it calls, not by the parent function nor by any sibling function. So, we can't set the Peewee database state in `get_db()`, or the *path operation functions* wouldn't see the new Peewee database state for that "context".
|
||||
|
||||
### But `get_db` is an async context manager
|
||||
|
||||
You might be thinking that `get_db()` is actually not used as a function, it's converted to a context manager.
|
||||
|
||||
So the *path operation function* is part of it.
|
||||
|
||||
But the code after the `yield`, in the `finally` is not executed in the same "context".
|
||||
|
||||
So, if you reset the state in `get_db()`, the *path operation function* would see the database connection set there. But the `finally` block would not see the same context variable value, and so, as the database object would not have the same context variable for its state, it would not have the same connection, so you couldn't close it in the `finally` in `get_db()` after the request is done.
|
||||
|
||||
In the middleware we are setting the Peewee state to a context variable that holds a `dict`. So, it's set for every new request.
|
||||
|
||||
And as the database state variables are stored inside of that `dict` instead of new context variables, when Peewee sets the new database state (connection, transactions, etc) in any part of the internal code, underneath, all that will be set as keys in that `dict`. But the `dict` would still be the same we set in the middleware. That's what allows the `get_db()` dependency to make Peewee create a new connection (that is stored in that `dict`) and allows the `finally` block to still have access to the same connection.
|
||||
|
||||
Because the context variable is set outside all that, in the middleware.
|
||||
|
||||
### Connect and disconnect in dependency
|
||||
|
||||
Then the next question would be, why not just connect and disconnect the database in the middleware itself, instead of `get_db()`?
|
||||
|
||||
First, the middleware has to be `async`, and creating and closing the database connection is potentially blocking, so it could degrade performance.
|
||||
|
||||
But more importantly, the middleware returns a `response`, and this `response` is actually an awaitable function that will do all the work in your code, including background tasks.
|
||||
|
||||
If you closed the connection in the middleware right before returning the `response`, some of your code would not have the chance to use the database connection set in the context variable.
|
||||
|
||||
Because some other code will call that `response` with `await response(...)`. And inside of that `await response(...)` is that, for example, background tasks are run. But if the connection was already closed before `response` is awaited, then it won't be able to access it.
|
||||
@@ -369,7 +369,7 @@ Create utility functions to:
|
||||
```
|
||||
|
||||
!!! tip
|
||||
By creating functions that are only dedicated to interacting with the database (get a user or an item) independent of your path operation function, you can more easily reuse them in multiple parts and also add <abbr title="Automated tests, written in code, that check if another piece of code is working correctly.">unit tests</abbr> for them.
|
||||
By creating functions that are only dedicated to interacting with the database (get a user or an item) independent of your *path operation function*, you can more easily reuse them in multiple parts and also add <abbr title="Automated tests, written in code, that check if another piece of code is working correctly.">unit tests</abbr> for them.
|
||||
|
||||
### Create data
|
||||
|
||||
@@ -444,7 +444,7 @@ A "migration" is the set of steps needed whenever you change the structure of yo
|
||||
pip install async-exit-stack async-generator
|
||||
```
|
||||
|
||||
This installs <a href="https://github.com/sorcio/async_exit_stack" target="_blank">async-exit-stack</a> and <a href="https://github.com/python-trio/async_generator" class="external-link" target="_blank">async-generator</a>.
|
||||
This installs <a href="https://github.com/sorcio/async_exit_stack" class="external-link" target="_blank">async-exit-stack</a> and <a href="https://github.com/python-trio/async_generator" class="external-link" target="_blank">async-generator</a>.
|
||||
|
||||
You can also use the alternative method with a "middleware" explained at the end.
|
||||
|
||||
@@ -454,7 +454,7 @@ We need to have an independent database session/connection (`SessionLocal`) per
|
||||
|
||||
And then a new session will be created for the next request.
|
||||
|
||||
For that, we will create a new dependency with `yield`, as explained before in the section about <a href="https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-with-yield/" target="_blank">Dependencies with `yield`</a>.
|
||||
For that, we will create a new dependency with `yield`, as explained before in the section about [Dependencies with `yield`](dependencies/dependencies-with-yield.md){.internal-link target=_blank}.
|
||||
|
||||
Our dependency will create a new SQLAlchemy `SessionLocal` that will be used in a single request, and then close it once the request is finished.
|
||||
|
||||
@@ -492,9 +492,9 @@ Now, finally, here's the standard **FastAPI** *path operations* code.
|
||||
|
||||
We are creating the database session before each request in the dependency with `yield`, and then closing it afterwards.
|
||||
|
||||
And then we can create the required dependency in the path operation function, to get that session directly.
|
||||
And then we can create the required dependency in the *path operation function*, to get that session directly.
|
||||
|
||||
With that, we can just call `crud.get_user` directly from inside of the path operation function and use that session.
|
||||
With that, we can just call `crud.get_user` directly from inside of the *path operation function* and use that session.
|
||||
|
||||
!!! tip
|
||||
Notice that the values you return are SQLAlchemy models, or lists of SQLAlchemy models.
|
||||
@@ -508,7 +508,7 @@ With that, we can just call `crud.get_user` directly from inside of the path ope
|
||||
|
||||
### About `def` vs `async def`
|
||||
|
||||
Here we are using SQLAlchemy code inside of the path operation function and in the dependency, and, in turn, it will go and communicate with an external database.
|
||||
Here we are using SQLAlchemy code inside of the *path operation function* and in the dependency, and, in turn, it will go and communicate with an external database.
|
||||
|
||||
That could potentially require some "waiting".
|
||||
|
||||
@@ -534,7 +534,7 @@ def read_user(user_id: int, db: Session = Depends(get_db)):
|
||||
```
|
||||
|
||||
!!! note "Very Technical Details"
|
||||
If you are curious and have a deep technical knowledge, you can check <a href="https://fastapi.tiangolo.com/async/#very-technical-details" target="_blank">the very technical details of how this `async def` vs `def` is handled</a>.
|
||||
If you are curious and have a deep technical knowledge, you can check the very technical details of how this `async def` vs `def` is handled in the [Async](../async.md#very-technical-details){.internal-link target=_blank} docs.
|
||||
|
||||
## Migrations
|
||||
|
||||
@@ -612,7 +612,7 @@ It will look like this:
|
||||
|
||||
<img src="/img/tutorial/sql-databases/image02.png">
|
||||
|
||||
You can also use an online SQLite browser like <a href="https://inloop.github.io/sqlite-viewer/" target="_blank">SQLite Viewer</a> or <a href="https://extendsclass.com/sqlite-browser.html" class="external-link" target="_blank">ExtendsClass</a>.
|
||||
You can also use an online SQLite browser like <a href="https://inloop.github.io/sqlite-viewer/" class="external-link" target="_blank">SQLite Viewer</a> or <a href="https://extendsclass.com/sqlite-browser.html" class="external-link" target="_blank">ExtendsClass</a>.
|
||||
|
||||
## Alternative DB session with middleware
|
||||
|
||||
|
||||
@@ -11,13 +11,19 @@ pip install aiofiles
|
||||
## Use `StaticFiles`
|
||||
|
||||
* Import `StaticFiles` from Starlette.
|
||||
* "Mount" it the same way you would <a href="https://fastapi.tiangolo.com/tutorial/sub-applications-proxy/" target="_blank">mount a Sub-Application</a>.
|
||||
* "Mount" a `StaticFiles()` instance in a specific path.
|
||||
|
||||
```Python hl_lines="2 6"
|
||||
{!./src/static_files/tutorial001.py!}
|
||||
```
|
||||
|
||||
Then you could have a directory `./static/` with some files that will be served directly.
|
||||
### What is "Mounting"
|
||||
|
||||
"Mounting" means adding a complete "independent" application in a specific path, that then takes care of handling all the sub-paths.
|
||||
|
||||
This is different from using an `APIRouter` as a mounted application is completely independent. The OpenAPI and docs from your main application won't include anything from the mounted application, etc.
|
||||
|
||||
You can read more about this in the **Advanced User Guide**.
|
||||
|
||||
## Details
|
||||
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
There are at least two situations where you could need to create your **FastAPI** application using some specific paths.
|
||||
|
||||
But then you need to set them up to be served with a path prefix.
|
||||
|
||||
It could happen if you have a:
|
||||
|
||||
* **Proxy** server.
|
||||
* You are "**mounting**" a FastAPI application inside another FastAPI application (or inside another ASGI application, like Starlette).
|
||||
|
||||
## Proxy
|
||||
|
||||
Having a proxy in this case means that you could declare a path at `/app`, but then, you could need to add a layer on top (the Proxy) that would put your **FastAPI** application under a path like `/api/v1`.
|
||||
|
||||
In this case, the original path `/app` will actually be served at `/api/v1/app`.
|
||||
|
||||
Even though your application "thinks" it is serving at `/app`.
|
||||
|
||||
And the Proxy could be re-writing the path "on the fly" to keep your application convinced that it is serving at `/app`.
|
||||
|
||||
Up to here, everything would work as normally.
|
||||
|
||||
But then, when you open the integrated docs, they would expect to get the OpenAPI schema at `/openapi.json`, instead of `/api/v1/openapi.json`.
|
||||
|
||||
So, the frontend (that runs in the browser) would try to reach `/openapi.json` and wouldn't be able to get the OpenAPI schema.
|
||||
|
||||
So, it's needed that the frontend looks for the OpenAPI schema at `/api/v1/openapi.json`.
|
||||
|
||||
And it's also needed that the returned JSON OpenAPI schema has the defined path at `/api/v1/app` (behind the proxy) instead of `/app`.
|
||||
|
||||
---
|
||||
|
||||
For these cases, you can declare an `openapi_prefix` parameter in your `FastAPI` application.
|
||||
|
||||
See the section below, about "mounting", for an example.
|
||||
|
||||
## Mounting a **FastAPI** application
|
||||
|
||||
"Mounting" means adding a complete "independent" application in a specific path, that then takes care of handling all the sub-paths.
|
||||
|
||||
You could want to do this if you have several "independent" applications that you want to separate, having their own independent OpenAPI schema and user interfaces.
|
||||
|
||||
### Top-level application
|
||||
|
||||
First, create the main, top-level, **FastAPI** application, and its path operations:
|
||||
|
||||
```Python hl_lines="3 6 7 8"
|
||||
{!./src/sub_applications/tutorial001.py!}
|
||||
```
|
||||
|
||||
### Sub-application
|
||||
|
||||
Then, create your sub-application, and its path operations.
|
||||
|
||||
This sub-application is just another standard FastAPI application, but this is the one that will be "mounted".
|
||||
|
||||
When creating the sub-application, use the parameter `openapi_prefix`. In this case, with a prefix of `/subapi`:
|
||||
|
||||
```Python hl_lines="11 14 15 16"
|
||||
{!./src/sub_applications/tutorial001.py!}
|
||||
```
|
||||
|
||||
### Mount the sub-application
|
||||
|
||||
In your top-level application, `app`, mount the sub-application, `subapi`.
|
||||
|
||||
Here you need to make sure you use the same path that you used for the `openapi_prefix`, in this case, `/subapi`:
|
||||
|
||||
```Python hl_lines="11 19"
|
||||
{!./src/sub_applications/tutorial001.py!}
|
||||
```
|
||||
|
||||
## Check the automatic API docs
|
||||
|
||||
Now, run `uvicorn`, if your file is at `main.py`, it would be:
|
||||
|
||||
```bash
|
||||
uvicorn main:app --reload
|
||||
```
|
||||
|
||||
And open the docs at <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
|
||||
|
||||
You will see the automatic API docs for the main app, including only its own paths:
|
||||
|
||||
<img src="/img/tutorial/sub-applications/image01.png">
|
||||
|
||||
And then, open the docs for the sub-application, at <a href="http://127.0.0.1:8000/subapi/docs" class="external-link" target="_blank">http://127.0.0.1:8000/subapi/docs</a>.
|
||||
|
||||
You will see the automatic API docs for the sub-application, including only its own sub-paths, with their correct prefix:
|
||||
|
||||
<img src="/img/tutorial/sub-applications/image02.png">
|
||||
|
||||
If you try interacting with any of the two user interfaces, they will work, because the browser will be able to talk to the correct path (or sub-path).
|
||||
@@ -1,67 +0,0 @@
|
||||
You can use any template engine you want with **FastAPI**.
|
||||
|
||||
A common election is Jinja2, the same one used by Flask and other tools.
|
||||
|
||||
Starlette has utilities to configure it easily that you can use directly in your **FastAPI** application.
|
||||
|
||||
## Install dependencies
|
||||
|
||||
Install `jinja2`:
|
||||
|
||||
```bash
|
||||
pip install jinja2
|
||||
```
|
||||
|
||||
If you need to also serve static files (as in this example), install `aiofiles`:
|
||||
|
||||
```bash
|
||||
pip install aiofiles
|
||||
```
|
||||
|
||||
## Using `Jinja2Templates`
|
||||
|
||||
* Import `Jinja2Templates` form Starlette.
|
||||
* Create a `templates` object that you can re-use later.
|
||||
* Declare a `Request` parameter in the *path operation* that will return a template.
|
||||
* Use the `templates` you created to render and return a `TemplateResponse`, passing the `request` as one of the key-value pairs in the Jinja2 "context".
|
||||
|
||||
```Python hl_lines="4 11 15 16"
|
||||
{!./src/templates/tutorial001.py!}
|
||||
```
|
||||
|
||||
!!! note
|
||||
Notice that you have to pass the `request` as part of the key-value pairs in the context for Jinja2. So, you also have to declare it in your *path operation*.
|
||||
|
||||
## Writing templates
|
||||
|
||||
Then you can write a template at `templates/item.html` with:
|
||||
|
||||
```jinja hl_lines="7"
|
||||
{!./src/templates/templates/item.html!}
|
||||
```
|
||||
|
||||
It will show the `id` taken from the "context" `dict` you passed:
|
||||
|
||||
```Python
|
||||
{"request": request, "id": id}
|
||||
```
|
||||
|
||||
## Templates and static files
|
||||
|
||||
And you can also use `url_for()` inside of the template, and use it, for example, with the `StaticFiles` you mounted.
|
||||
|
||||
```jinja hl_lines="4"
|
||||
{!./src/templates/templates/item.html!}
|
||||
```
|
||||
|
||||
In this example, it would link to a CSS file at `static/styles.css` with:
|
||||
|
||||
```CSS hl_lines="4"
|
||||
{!./src/templates/static/styles.css!}
|
||||
```
|
||||
|
||||
And because you are using `StaticFiles`, that CSS file would be served automatically by your **FastAPI** application at the URL `/static/styles.css`.
|
||||
|
||||
## More details
|
||||
|
||||
For more details, including how to test templates, check <a href="https://www.starlette.io/templates/" class="external-link" target="_blank">Starlette's docs on templates</a>.
|
||||
@@ -1,59 +0,0 @@
|
||||
## Overriding dependencies during testing
|
||||
|
||||
There are some scenarios where you might want to override a dependency during testing.
|
||||
|
||||
You don't want the original dependency to run (nor any of the sub-dependencies it might have).
|
||||
|
||||
Instead, you want to provide a different dependency that will be used only during tests (possibly only some specific tests), and will provide a value that can be used where the value of the original dependency was used.
|
||||
|
||||
### Use cases: external service
|
||||
|
||||
An example could be that you have an external authentication provider that you need to call.
|
||||
|
||||
You send it a token and it returns an authenticated user.
|
||||
|
||||
This provider might be charging you per request, and calling it might take some extra time than if you had a fixed mock user for tests.
|
||||
|
||||
You probably want to test the external provider once, but not necessarily call it for every test that runs.
|
||||
|
||||
In this case, you can override the dependency that calls that provider, and use a custom dependency that returns a mock user, only for your tests.
|
||||
|
||||
### Use case: testing database
|
||||
|
||||
Other example could be that you are using a specific database only for testing.
|
||||
|
||||
Your normal dependency would return a database session.
|
||||
|
||||
But then, after each test, you could want to rollback all the operations or remove data.
|
||||
|
||||
Or you could want to alter the data before the tests run, etc.
|
||||
|
||||
In this case, you could use a dependency override to return your *custom* database session instead of the one that would be used normally.
|
||||
|
||||
### Use the `app.dependency_overrides` attribute
|
||||
|
||||
For these cases, your **FastAPI** application has an attribute `app.dependency_overrides`, it is a simple `dict`.
|
||||
|
||||
To override a dependency for testing, you put as a key the original dependency (a function), and as the value, your dependency override (another function).
|
||||
|
||||
And then **FastAPI** will call that override instead of the original dependency.
|
||||
|
||||
```Python hl_lines="24 25 28"
|
||||
{!./src/dependency_testing/tutorial001.py!}
|
||||
```
|
||||
|
||||
!!! tip
|
||||
You can set a dependency override for a dependency used anywhere in your **FastAPI** application.
|
||||
|
||||
The original dependency could be used in a *path operation function*, a *path operation decorator* (when you don't use the return value), a `.include_router()` call, etc.
|
||||
|
||||
FastAPI will still be able to override it.
|
||||
|
||||
Then you can reset your overrides (remove them) by setting `app.dependency_overrides` to be an empty `dict`:
|
||||
|
||||
```Python
|
||||
app.dependency_overrides = {}
|
||||
```
|
||||
|
||||
!!! tip
|
||||
If you want to override a dependency only during some tests, you can set the override at the beginning of the test (inside the test function) and reset it at the end (at the end of the test function).
|
||||
@@ -92,25 +92,7 @@ For more information about how to pass data to the backend (using `requests` or
|
||||
!!! info
|
||||
Note that the `TestClient` receives data that can be converted to JSON, not Pydantic models.
|
||||
|
||||
If you have a Pydantic model in your test and you want to send its data to the application during testing, you can use the <a href="https://fastapi.tiangolo.com/tutorial/encoder/" target="_blank">JSON compatible encoder: `jsonable_encoder`</a>.
|
||||
|
||||
## Testing WebSockets
|
||||
|
||||
You can use the same `TestClient` to test WebSockets.
|
||||
|
||||
For this, you use the `TestClient` in a `with` statement, connecting to the WebSocket:
|
||||
|
||||
```Python hl_lines="27 28 29 30 31"
|
||||
{!./src/app_testing/tutorial002.py!}
|
||||
```
|
||||
|
||||
## Testing Events, `startup` and `shutdown`
|
||||
|
||||
When you need your event handlers (`startup` and `shutdown`) to run in your tests, you can use the `TestClient` with a `with` statement:
|
||||
|
||||
```Python hl_lines="9 10 11 12 20 21 22 23 24"
|
||||
{!./src/app_testing/tutorial003.py!}
|
||||
```
|
||||
If you have a Pydantic model in your test and you want to send its data to the application during testing, you can use the `jsonable_encoder` descibed in [JSON Compatible Encoder](encoder.md){.internal-link target=_blank}.
|
||||
|
||||
## Run it
|
||||
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
Up to now, you have been declaring the parts of the request that you need with their types.
|
||||
|
||||
Taking data from:
|
||||
|
||||
* The path as parameters.
|
||||
* Headers.
|
||||
* Cookies.
|
||||
* etc.
|
||||
|
||||
And by doing so, **FastAPI** is validating that data, converting it and generating documentation for your API automatically.
|
||||
|
||||
But there are situations where you might need to access the `Request` object directly.
|
||||
|
||||
## Details about the `Request` object
|
||||
|
||||
As **FastAPI** is actually **Starlette** underneath, with a layer of several tools on top, you can use Starlette's <a href="https://www.starlette.io/requests/" class="external-link" target="_blank">`Request`</a> object directly when you need to.
|
||||
|
||||
It would also mean that if you get data from the `Request` object directly (for example, read the body) it won't be validated, converted or annotated (with OpenAPI, for the automatic documentation) by FastAPI.
|
||||
|
||||
Although any other parameter declared normally (for example, the body with a Pydantic model) would still be validated, converted, annotated, etc.
|
||||
|
||||
But there are specific cases where it's useful to get the `Request` object.
|
||||
|
||||
## Use the `Request` object directly
|
||||
|
||||
Let's imagine you want to get the client's IP address/host inside of your *path operation function*.
|
||||
|
||||
For that you need to access the request directly.
|
||||
|
||||
### Import the `Request`
|
||||
|
||||
First, import the `Request` class from Starlette:
|
||||
|
||||
```Python hl_lines="2"
|
||||
{!./src/using_request_directly/tutorial001.py!}
|
||||
```
|
||||
|
||||
### Declare the `Request` parameter
|
||||
|
||||
Then declare a *path operation function* parameter with the type being the `Request` class:
|
||||
|
||||
```Python hl_lines="8"
|
||||
{!./src/using_request_directly/tutorial001.py!}
|
||||
```
|
||||
|
||||
!!! tip
|
||||
Note that in this case, we are declaring a path parameter besides the request parameter.
|
||||
|
||||
So, the path parameter will be extracted, validated, converted to the specified type and annotated with OpenAPI.
|
||||
|
||||
The same way, you can declare any other parameter as normally, and additionally, get the `Request` too.
|
||||
|
||||
## `Request` documentation
|
||||
|
||||
You can read more details about the <a href="https://www.starlette.io/requests/" class="external-link" target="_blank">`Request` object in the official Starlette documentation site</a>.
|
||||
@@ -1,109 +0,0 @@
|
||||
|
||||
You can use <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API" class="external-link" target="_blank">WebSockets</a> with **FastAPI**.
|
||||
|
||||
## WebSockets client
|
||||
|
||||
### In production
|
||||
|
||||
In your production system, you probably have a frontend created with a modern framework like React, Vue.js or Angular.
|
||||
|
||||
And to communicate using WebSockets with your backend you would probably use your frontend's utilities.
|
||||
|
||||
Or you might have a native mobile application that communicates with your WebSocket backend directly, in native code.
|
||||
|
||||
Or you might have any other way to communicate with the WebSocket endpoint.
|
||||
|
||||
---
|
||||
|
||||
But for this example, we'll use a very simple HTML document with some JavaScript, all inside a long string.
|
||||
|
||||
This, of course, is not optimal and you wouldn't use it for production.
|
||||
|
||||
In production you would have one of the options above.
|
||||
|
||||
But it's the simplest way to focus on the server-side of WebSockets and have a working example:
|
||||
|
||||
```Python hl_lines="2 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 42 43 44"
|
||||
{!./src/websockets/tutorial001.py!}
|
||||
```
|
||||
|
||||
## Create a `websocket`
|
||||
|
||||
In your **FastAPI** application, create a `websocket`:
|
||||
|
||||
```Python hl_lines="3 47 48"
|
||||
{!./src/websockets/tutorial001.py!}
|
||||
```
|
||||
|
||||
!!! tip
|
||||
In this example we are importing `WebSocket` from `starlette.websockets` to use it in the type declaration in the WebSocket route function.
|
||||
|
||||
## Await for messages and send messages
|
||||
|
||||
In your WebSocket route you can `await` for messages and send messages.
|
||||
|
||||
```Python hl_lines="49 50 51 52 53"
|
||||
{!./src/websockets/tutorial001.py!}
|
||||
```
|
||||
|
||||
You can receive and send binary, text, and JSON data.
|
||||
|
||||
## Using `Depends` and others
|
||||
|
||||
In WebSocket endpoints you can import from `fastapi` and use:
|
||||
|
||||
* `Depends`
|
||||
* `Security`
|
||||
* `Cookie`
|
||||
* `Header`
|
||||
* `Path`
|
||||
* `Query`
|
||||
|
||||
They work the same way as for other FastAPI endpoints/*path operations*:
|
||||
|
||||
```Python hl_lines="55 56 57 58 59 60 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78"
|
||||
{!./src/websockets/tutorial002.py!}
|
||||
```
|
||||
|
||||
!!! info
|
||||
In a WebSocket it doesn't really make sense to raise an `HTTPException`. So it's better to close the WebSocket connection directly.
|
||||
|
||||
You can use a closing code from the <a href="https://tools.ietf.org/html/rfc6455#section-7.4.1" class="external-link" target="_blank">valid codes defined in the specification</a>.
|
||||
|
||||
In the future, there will be a `WebSocketException` that you will be able to `raise` from anywhere, and add exception handlers for it. It depends on the <a href="https://github.com/encode/starlette/pull/527" class="external-link" target="_blank">PR #527</a> in Starlette.
|
||||
|
||||
## More info
|
||||
|
||||
To learn more about the options, check Starlette's documentation for:
|
||||
|
||||
* <a href="https://www.starlette.io/applications/" class="external-link" target="_blank">Applications (`websocket_route`)</a>.
|
||||
* <a href="https://www.starlette.io/websockets/" class="external-link" target="_blank">The `WebSocket` class</a>.
|
||||
* <a href="https://www.starlette.io/endpoints/#websocketendpoint" class="external-link" target="_blank">Class-based WebSocket handling</a>.
|
||||
|
||||
## Test it
|
||||
|
||||
If your file is named `main.py`, run your application with:
|
||||
|
||||
```bash
|
||||
uvicorn main:app --reload
|
||||
```
|
||||
|
||||
Open your browser at <a href="http://127.0.0.1:8000" class="external-link" target="_blank">http://127.0.0.1:8000</a>.
|
||||
|
||||
You will see a simple page like:
|
||||
|
||||
<img src="/img/tutorial/websockets/image01.png">
|
||||
|
||||
You can type messages in the input box, and send them:
|
||||
|
||||
<img src="/img/tutorial/websockets/image02.png">
|
||||
|
||||
And your **FastAPI** application with WebSockets will respond back:
|
||||
|
||||
<img src="/img/tutorial/websockets/image03.png">
|
||||
|
||||
You can send (and receive) many messages:
|
||||
|
||||
<img src="/img/tutorial/websockets/image04.png">
|
||||
|
||||
And all of them will use the same WebSocket connection.
|
||||
Reference in New Issue
Block a user