37 changed files with 4626 additions and 0 deletions
@ -0,0 +1,503 @@ |
|||
# LLM test file { #llm-test-file } |
|||
|
|||
This document tests if the <abbr title="Large Language Model">LLM</abbr>, which translates the documentation, understands the `general_prompt` in `scripts/translate.py` and the language specific prompt in `docs/{language code}/llm-prompt.md`. The language specific prompt is appended to `general_prompt`. |
|||
|
|||
Tests added here will be seen by all designers of language specific prompts. |
|||
|
|||
Use as follows: |
|||
|
|||
* Have a language specific prompt – `docs/{language code}/llm-prompt.md`. |
|||
* Do a fresh translation of this document into your desired target language (see e.g. the `translate-page` command of the `translate.py`). This will create the translation under `docs/{language code}/docs/_llm-test.md`. |
|||
* Check if things are okay in the translation. |
|||
* If necessary, improve your language specific prompt, the general prompt, or the English document. |
|||
* Then manually fix the remaining issues in the translation, so that it is a good translation. |
|||
* Retranslate, having the good translation in place. The ideal result would be that the LLM makes no changes anymore to the translation. That means that the general prompt and your language specific prompt are as good as they can be (It will sometimes make a few seemingly random changes, the reason is that <a href="https://doublespeak.chat/#/handbook#deterministic-output" class="external-link" target="_blank">LLMs are not deterministic algorithms</a>). |
|||
|
|||
The tests: |
|||
|
|||
## Code snippets { #code-snippets} |
|||
|
|||
//// tab | Test |
|||
|
|||
This is a code snippet: `foo`. And this is another code snippet: `bar`. And another one: `baz quux`. |
|||
|
|||
//// |
|||
|
|||
//// tab | Info |
|||
|
|||
Content of code snippets should be left as is. |
|||
|
|||
See section `### Content of code snippets` in the general prompt in `scripts/translate.py`. |
|||
|
|||
//// |
|||
|
|||
## Quotes { #quotes } |
|||
|
|||
//// tab | Test |
|||
|
|||
Yesterday, my friend wrote: "If you spell incorrectly correctly, you have spelled it incorrectly". To which I answered: "Correct, but 'incorrectly' is incorrectly not '"incorrectly"'". |
|||
|
|||
/// note |
|||
|
|||
The LLM will probably translate this wrong. Interesting is only if it keeps the fixed translation when retranslating. |
|||
|
|||
/// |
|||
|
|||
//// |
|||
|
|||
//// tab | Info |
|||
|
|||
The prompt designer may choose if they want to convert neutral quotes to typographic quotes. It is okay to leave them as is. |
|||
|
|||
See for example section `### Quotes` in `docs/de/llm-prompt.md`. |
|||
|
|||
//// |
|||
|
|||
## Quotes in code snippets { #quotes-in-code-snippets} |
|||
|
|||
//// tab | Test |
|||
|
|||
`pip install "foo[bar]"` |
|||
|
|||
Examples for string literals in code snippets: `"this"`, `'that'`. |
|||
|
|||
A difficult example for string literals in code snippets: `f"I like {'oranges' if orange else "apples"}"` |
|||
|
|||
Hardcore: `Yesterday, my friend wrote: "If you spell incorrectly correctly, you have spelled it incorrectly". To which I answered: "Correct, but 'incorrectly' is incorrectly not '"incorrectly"'"` |
|||
|
|||
//// |
|||
|
|||
//// tab | Info |
|||
|
|||
... However, quotes inside code snippets must stay as is. |
|||
|
|||
//// |
|||
|
|||
## code blocks { #code-blocks } |
|||
|
|||
//// tab | Test |
|||
|
|||
A Bash code example... |
|||
|
|||
```bash |
|||
# Print a greeting to the universe |
|||
echo "Hello universe" |
|||
``` |
|||
|
|||
...and a console code example... |
|||
|
|||
```console |
|||
$ <font color="#4E9A06">fastapi</font> run <u style="text-decoration-style:solid">main.py</u> |
|||
<span style="background-color:#009485"><font color="#D3D7CF"> FastAPI </font></span> Starting server |
|||
Searching for package file structure |
|||
``` |
|||
|
|||
...and another console code example... |
|||
|
|||
```console |
|||
// Create a directory "Code" |
|||
$ mkdir code |
|||
// Switch into that directory |
|||
$ cd code |
|||
``` |
|||
|
|||
...and a Python code example... |
|||
|
|||
```Python |
|||
wont_work() # This won't work 😱 |
|||
works(foo="bar") # This works 🎉 |
|||
``` |
|||
|
|||
...and that's it. |
|||
|
|||
//// |
|||
|
|||
//// tab | Info |
|||
|
|||
Code in code blocks should not be modified, with the exception of comments. |
|||
|
|||
See section `### Content of code blocks` in the general prompt in `scripts/translate.py`. |
|||
|
|||
//// |
|||
|
|||
## Tabs and colored boxes { #tabs-and-colored-boxes } |
|||
|
|||
//// tab | Test |
|||
|
|||
/// info |
|||
Some text |
|||
/// |
|||
|
|||
/// note |
|||
Some text |
|||
/// |
|||
|
|||
/// note | Technical details |
|||
Some text |
|||
/// |
|||
|
|||
/// check |
|||
Some text |
|||
/// |
|||
|
|||
/// tip |
|||
Some text |
|||
/// |
|||
|
|||
/// warning |
|||
Some text |
|||
/// |
|||
|
|||
/// danger |
|||
Some text |
|||
/// |
|||
|
|||
//// |
|||
|
|||
//// tab | Info |
|||
|
|||
Tabs and `Info`/`Note`/`Warning`/etc. blocks should have the translation of their title added after a vertical bar (`|`). |
|||
|
|||
See sections `### Special blocks` and `### Tab blocks` in the general prompt in `scripts/translate.py`. |
|||
|
|||
//// |
|||
|
|||
## Web- and internal links { #web-and-internal-links } |
|||
|
|||
//// tab | Test |
|||
|
|||
The link text should get translated, the link address should remain unchaged: |
|||
|
|||
* [Link to heading above](#code-snippets) |
|||
* [Internal link](index.md#installation){.internal-link target=_blank} |
|||
* <a href="https://sqlmodel.tiangolo.com/" class="external-link" target="_blank">External link</a> |
|||
* <a href="https://fastapi.tiangolo.com/css/styles.css" class="external-link" target="_blank">Link to a style</a> |
|||
* <a href="https://fastapi.tiangolo.com/js/logic.js" class="external-link" target="_blank">Link to a script</a> |
|||
* <a href="https://fastapi.tiangolo.com/img/foo.jpg" class="external-link" target="_blank">Link to an image</a> |
|||
|
|||
The link text should get translated, the link address should point to the translation: |
|||
|
|||
* <a href="https://fastapi.tiangolo.com/" class="external-link" target="_blank">FastAPI link</a> |
|||
|
|||
//// |
|||
|
|||
//// tab | Info |
|||
|
|||
Links should be translated, but their address shall remain unchanged. An exception are absolute links to pages of the FastAPI documentation. In that case it should link to the translation. |
|||
|
|||
See section `### Links` in the general prompt in `scripts/translate.py`. |
|||
|
|||
//// |
|||
|
|||
## HTML "abbr" elements { #html-abbr-elements } |
|||
|
|||
//// tab | Test |
|||
|
|||
Here some things wrapped in HTML "abbr" elements (Some are invented): |
|||
|
|||
### The abbr gives a full phrase { #the-abbr-gives-a-full-phrase } |
|||
|
|||
* <abbr title="Getting Things Done">GTD</abbr> |
|||
* <abbr title="less than"><code>lt</code></abbr> |
|||
* <abbr title="XML Web Token">XWT</abbr> |
|||
* <abbr title="Parallel Server Gateway Interface">PSGI</abbr> |
|||
|
|||
### The abbr gives an explanation { #the-abbr-gives-an-explanation } |
|||
|
|||
* <abbr title="A group of machines that are configured to be connected and work together in some way.">cluster</abbr> |
|||
* <abbr title="A method of machine learning that uses artificial neural networks with numerous hidden layers between input and output layers, thereby developing a comprehensive internal structure">Deep Learning</abbr> |
|||
|
|||
### The abbr gives a full phrase and an explanation { #the-abbr-gives-a-full-phrase-and-an-explanation } |
|||
|
|||
* <abbr title="Mozilla Developer Network: documentation for developers, written by the Firefox people">MDN</abbr> |
|||
* <abbr title="Input/Output: disk reading or writing, network communications.">I/O</abbr>. |
|||
|
|||
//// |
|||
|
|||
//// tab | Info |
|||
|
|||
"title" attributes of "abbr" elements are translated following some specific instructions. |
|||
|
|||
Translations can add their own "abbr" elements which the LLM should not remove. E.g. to explain English words. |
|||
|
|||
See section `### HTML abbr elements` in the general prompt in `scripts/translate.py`. |
|||
|
|||
//// |
|||
|
|||
## Headings { #headings } |
|||
|
|||
//// tab | Test |
|||
|
|||
### Develop a webapp - a tutorial { #develop-a-webapp-a-tutorial } |
|||
|
|||
Hello. |
|||
|
|||
### Type hints and -annotations { #type-hints-and-annotations } |
|||
|
|||
Hello again. |
|||
|
|||
### Super- and subclasses { #super-and-subclasses } |
|||
|
|||
Hello again. |
|||
|
|||
//// |
|||
|
|||
//// tab | Info |
|||
|
|||
The only hard rule for headings is that the LLM leaves the hash part inside curly brackets unchanged, which ensures that links do not break. |
|||
|
|||
See section `### Headings` in the general prompt in `scripts/translate.py`. |
|||
|
|||
For some language specific instructions, see e.g. section `### Headings` in `docs/de/llm-prompt.md`. |
|||
|
|||
//// |
|||
|
|||
## Terms used in the docs { #terms-used-in-the-docs } |
|||
|
|||
//// tab | Test |
|||
|
|||
* you |
|||
* your |
|||
|
|||
* e.g. |
|||
* etc. |
|||
|
|||
* `foo` as an `int` |
|||
* `bar` as a `str` |
|||
* `baz` as a `list` |
|||
|
|||
* the Tutorial - User guide |
|||
* the Advanced User Guide |
|||
* the SQLModel docs |
|||
* the API docs |
|||
* the automatic docs |
|||
|
|||
* Data Science |
|||
* Deep Learning |
|||
* Machine Learning |
|||
* Dependency Injection |
|||
* HTTP Basic authentication |
|||
* HTTP Digest |
|||
* ISO format |
|||
* the JSON Schema standard |
|||
* the JSON schema |
|||
* the schema definition |
|||
* Password Flow |
|||
* Mobile |
|||
|
|||
* deprecated |
|||
* designed |
|||
* invalid |
|||
* on the fly |
|||
* standard |
|||
* default |
|||
* case-sensitive |
|||
* case-insensitive |
|||
|
|||
* to serve the application |
|||
* to serve the page |
|||
|
|||
* the app |
|||
* the application |
|||
|
|||
* the request |
|||
* the response |
|||
* the error response |
|||
|
|||
* the path operation |
|||
* the path operation decorator |
|||
* the path operation function |
|||
|
|||
* the body |
|||
* the request body |
|||
* the response body |
|||
* the JSON body |
|||
* the form body |
|||
* the file body |
|||
* the function body |
|||
|
|||
* the parameter |
|||
* the body parameter |
|||
* the path parameter |
|||
* the query parameter |
|||
* the cookie parameter |
|||
* the header parameter |
|||
* the form parameter |
|||
* the function parameter |
|||
|
|||
* the event |
|||
* the startup event |
|||
* the startup of the server |
|||
* the shutdown event |
|||
* the lifespan event |
|||
|
|||
* the handler |
|||
* the event handler |
|||
* the exception handler |
|||
* to handle |
|||
|
|||
* the model |
|||
* the Pydantic model |
|||
* the data model |
|||
* the database model |
|||
* the form model |
|||
* the model object |
|||
|
|||
* the class |
|||
* the base class |
|||
* the parent class |
|||
* the subclass |
|||
* the child class |
|||
* the sibling class |
|||
* the class method |
|||
|
|||
* the header |
|||
* the headers |
|||
* the authorization header |
|||
* the `Authorization` header |
|||
* the forwarded header |
|||
|
|||
* the dependency injection system |
|||
* the dependency |
|||
* the dependable |
|||
* the dependant |
|||
|
|||
* I/O bound |
|||
* CPU bound |
|||
* concurrency |
|||
* parallelism |
|||
* multiprocessing |
|||
|
|||
* the env var |
|||
* the environment variable |
|||
* the `PATH` |
|||
* the `PATH` variable |
|||
|
|||
* the authentication |
|||
* the authentication provider |
|||
* the authorization |
|||
* the authorization form |
|||
* the authorization provider |
|||
* the user authenticates |
|||
* the system authenticates the user |
|||
|
|||
* the CLI |
|||
* the command line interface |
|||
|
|||
* the server |
|||
* the client |
|||
|
|||
* the cloud provider |
|||
* the cloud service |
|||
|
|||
* the development |
|||
* the development stages |
|||
|
|||
* the dict |
|||
* the dictionary |
|||
* the enumeration |
|||
* the enum |
|||
* the enum member |
|||
|
|||
* the encoder |
|||
* the decoder |
|||
* to encode |
|||
* to decode |
|||
|
|||
* the exception |
|||
* to raise |
|||
|
|||
* the expression |
|||
* the statement |
|||
|
|||
* the frontend |
|||
* the backend |
|||
|
|||
* the GitHub discussion |
|||
* the GitHub issue |
|||
|
|||
* the performance |
|||
* the performance optimization |
|||
|
|||
* the return type |
|||
* the return value |
|||
|
|||
* the security |
|||
* the security scheme |
|||
|
|||
* the task |
|||
* the background task |
|||
* the task function |
|||
|
|||
* the template |
|||
* the template engine |
|||
|
|||
* the type annotation |
|||
* the type hint |
|||
|
|||
* the server worker |
|||
* the Uvicorn worker |
|||
* the Gunicorn Worker |
|||
* the worker process |
|||
* the worker class |
|||
* the workload |
|||
|
|||
* the deployment |
|||
* to deploy |
|||
|
|||
* the SDK |
|||
* the software development kit |
|||
|
|||
* the `APIRouter` |
|||
* the `requirements.txt` |
|||
* the Bearer Token |
|||
* the breaking change |
|||
* the bug |
|||
* the button |
|||
* the callable |
|||
* the code |
|||
* the commit |
|||
* the context manager |
|||
* the coroutine |
|||
* the database session |
|||
* the disk |
|||
* the domain |
|||
* the engine |
|||
* the fake X |
|||
* the HTTP GET method |
|||
* the item |
|||
* the library |
|||
* the lifespan |
|||
* the lock |
|||
* the middleware |
|||
* the mobile application |
|||
* the module |
|||
* the mounting |
|||
* the network |
|||
* the origin |
|||
* the override |
|||
* the payload |
|||
* the processor |
|||
* the property |
|||
* the proxy |
|||
* the pull request |
|||
* the query |
|||
* the RAM |
|||
* the remote machine |
|||
* the status code |
|||
* the string |
|||
* the tag |
|||
* the web framework |
|||
* the wildcard |
|||
* to return |
|||
* to validate |
|||
|
|||
//// |
|||
|
|||
//// tab | Info |
|||
|
|||
This is a not complete and not normative list of (mostly) technical terms seen in the docs. It may be helpful for the prompt designer to figure out for which terms the LLM needs a helping hand. For example when it keeps reverting a good translation to a suboptimal translation. Or when it has problems conjugating/declinating a term in your language. |
|||
|
|||
See e.g. section `### List of English terms and their preferred German translations` in `docs/de/llm-prompt.md`. |
|||
|
|||
//// |
|||
@ -0,0 +1,247 @@ |
|||
# Additional Responses in OpenAPI { #additional-responses-in-openapi } |
|||
|
|||
/// 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` { #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: |
|||
|
|||
{* ../../docs_src/additional_responses/tutorial001.py hl[18,22] *} |
|||
|
|||
/// note |
|||
|
|||
Keep 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-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-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 { #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: |
|||
|
|||
{* ../../docs_src/additional_responses/tutorial002.py hl[19:24,28] *} |
|||
|
|||
/// 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 { #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`: |
|||
|
|||
{* ../../docs_src/additional_responses/tutorial003.py hl[20:31] *} |
|||
|
|||
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 { #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 reuse some predefined responses in your *path operations* and combine them with additional custom ones. |
|||
|
|||
For example: |
|||
|
|||
{* ../../docs_src/additional_responses/tutorial004.py hl[13:17,26] *} |
|||
|
|||
## More information about OpenAPI responses { #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.1.0.md#responses-object" 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.1.0.md#response-object" 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`. |
|||
@ -0,0 +1,65 @@ |
|||
# Advanced Dependencies { #advanced-dependencies } |
|||
|
|||
## Parameterized dependencies { #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 { #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__`: |
|||
|
|||
{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[12] *} |
|||
|
|||
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 { #parameterize-the-instance } |
|||
|
|||
And now, we can use `__init__` to declare the parameters of the instance that we can use to "parameterize" the dependency: |
|||
|
|||
{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[9] *} |
|||
|
|||
In this case, **FastAPI** won't ever touch or care about `__init__`, we will use it directly in our code. |
|||
|
|||
## Create an instance { #create-an-instance } |
|||
|
|||
We could create an instance of this class with: |
|||
|
|||
{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[18] *} |
|||
|
|||
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 { #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`: |
|||
|
|||
{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[22] *} |
|||
|
|||
/// 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, there are 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. |
|||
|
|||
/// |
|||
@ -0,0 +1,458 @@ |
|||
# Behind a Proxy { #behind-a-proxy } |
|||
|
|||
In many situations, you would use a **proxy** like Traefik or Nginx in front of your FastAPI app. |
|||
|
|||
These proxies could handle HTTPS certificates and other things. |
|||
|
|||
## Proxy Forwarded Headers { #proxy-forwarded-headers } |
|||
|
|||
A **proxy** in front of your application would normally set some headers on the fly before sending the requests to your **server** to let the server know that the request was **forwarded** by the proxy, letting it know the original (public) URL, including the domain, that it is using HTTPS, etc. |
|||
|
|||
The **server** program (for example **Uvicorn** via **FastAPI CLI**) is capable of interpreting these headers, and then passing that information to your application. |
|||
|
|||
But for security, as the server doesn't know it is behind a trusted proxy, it won't interpret those headers. |
|||
|
|||
/// note | Technical Details |
|||
|
|||
The proxy headers are: |
|||
|
|||
* <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-For" class="external-link" target="_blank">X-Forwarded-For</a> |
|||
* <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-Proto" class="external-link" target="_blank">X-Forwarded-Proto</a> |
|||
* <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-Host" class="external-link" target="_blank">X-Forwarded-Host</a> |
|||
|
|||
/// |
|||
|
|||
### Enable Proxy Forwarded Headers { #enable-proxy-forwarded-headers } |
|||
|
|||
You can start FastAPI CLI with the *CLI Option* `--forwarded-allow-ips` and pass the IP addresses that should be trusted to read those forwarded headers. |
|||
|
|||
If you set it to `--forwarded-allow-ips="*"` it would trust all the incoming IPs. |
|||
|
|||
If your **server** is behind a trusted **proxy** and only the proxy talks to it, this would make it accept whatever is the IP of that **proxy**. |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ fastapi run --forwarded-allow-ips="*" |
|||
|
|||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
### Redirects with HTTPS { #redirects-with-https } |
|||
|
|||
For example, let's say you define a *path operation* `/items/`: |
|||
|
|||
{* ../../docs_src/behind_a_proxy/tutorial001_01.py hl[6] *} |
|||
|
|||
If the client tries to go to `/items`, by default, it would be redirected to `/items/`. |
|||
|
|||
But before setting the *CLI Option* `--forwarded-allow-ips` it could redirect to `http://localhost:8000/items/`. |
|||
|
|||
But maybe your application is hosted at `https://mysuperapp.com`, and the redirection should be to `https://mysuperapp.com/items/`. |
|||
|
|||
By setting `--proxy-headers` now FastAPI would be able to redirect to the right location. 😎 |
|||
|
|||
``` |
|||
https://mysuperapp.com/items/ |
|||
``` |
|||
|
|||
/// tip |
|||
|
|||
If you want to learn more about HTTPS, check the guide [About HTTPS](../deployment/https.md){.internal-link target=_blank}. |
|||
|
|||
/// |
|||
|
|||
### How Proxy Forwarded Headers Work |
|||
|
|||
Here's a visual representation of how the **proxy** adds forwarded headers between the client and the **application server**: |
|||
|
|||
```mermaid |
|||
sequenceDiagram |
|||
participant Client |
|||
participant Proxy as Proxy/Load Balancer |
|||
participant Server as FastAPI Server |
|||
|
|||
Client->>Proxy: HTTPS Request<br/>Host: mysuperapp.com<br/>Path: /items |
|||
|
|||
Note over Proxy: Proxy adds forwarded headers |
|||
|
|||
Proxy->>Server: HTTP Request<br/>X-Forwarded-For: [client IP]<br/>X-Forwarded-Proto: https<br/>X-Forwarded-Host: mysuperapp.com<br/>Path: /items |
|||
|
|||
Note over Server: Server interprets headers<br/>(if --forwarded-allow-ips is set) |
|||
|
|||
Server->>Proxy: HTTP Response<br/>with correct HTTPS URLs |
|||
|
|||
Proxy->>Client: HTTPS Response |
|||
``` |
|||
|
|||
The **proxy** intercepts the original client request and adds the special *forwarded* headers (`X-Forwarded-*`) before passing the request to the **application server**. |
|||
|
|||
These headers preserve information about the original request that would otherwise be lost: |
|||
|
|||
* **X-Forwarded-For**: The original client's IP address |
|||
* **X-Forwarded-Proto**: The original protocol (`https`) |
|||
* **X-Forwarded-Host**: The original host (`mysuperapp.com`) |
|||
|
|||
When **FastAPI CLI** is configured with `--forwarded-allow-ips`, it trusts these headers and uses them, for example to generate the correct URLs in redirects. |
|||
|
|||
## Proxy with a stripped path prefix { #proxy-with-a-stripped-path-prefix } |
|||
|
|||
You could have a proxy that adds a path prefix to your application. |
|||
|
|||
In these cases you can use `root_path` to configure your application. |
|||
|
|||
The `root_path` is a mechanism provided by the ASGI specification (that FastAPI is built on, through Starlette). |
|||
|
|||
The `root_path` is used to handle these specific cases. |
|||
|
|||
And it's also used internally when mounting sub-applications. |
|||
|
|||
Having a proxy with a stripped path prefix, in this case, means that you could declare a path at `/app` in your code, but then, you 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` would actually be served at `/api/v1/app`. |
|||
|
|||
Even though all your code is written assuming there's just `/app`. |
|||
|
|||
{* ../../docs_src/behind_a_proxy/tutorial001.py hl[6] *} |
|||
|
|||
And the proxy would be **"stripping"** the **path prefix** on the fly before transmitting the request to the app server (probably Uvicorn via FastAPI CLI), keeping your application convinced that it is being served at `/app`, so that you don't have to update all your code to include the prefix `/api/v1`. |
|||
|
|||
Up to here, everything would work as normally. |
|||
|
|||
But then, when you open the integrated docs UI (the frontend), it 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. |
|||
|
|||
Because we have a proxy with a path prefix of `/api/v1` for our app, the frontend needs to fetch the OpenAPI schema at `/api/v1/openapi.json`. |
|||
|
|||
```mermaid |
|||
graph LR |
|||
|
|||
browser("Browser") |
|||
proxy["Proxy on http://0.0.0.0:9999/api/v1/app"] |
|||
server["Server on http://127.0.0.1:8000/app"] |
|||
|
|||
browser --> proxy |
|||
proxy --> server |
|||
``` |
|||
|
|||
/// tip |
|||
|
|||
The IP `0.0.0.0` is commonly used to mean that the program listens on all the IPs available in that machine/server. |
|||
|
|||
/// |
|||
|
|||
The docs UI would also need the OpenAPI schema to declare that this API `server` is located at `/api/v1` (behind the proxy). For example: |
|||
|
|||
```JSON hl_lines="4-8" |
|||
{ |
|||
"openapi": "3.1.0", |
|||
// More stuff here |
|||
"servers": [ |
|||
{ |
|||
"url": "/api/v1" |
|||
} |
|||
], |
|||
"paths": { |
|||
// More stuff here |
|||
} |
|||
} |
|||
``` |
|||
|
|||
In this example, the "Proxy" could be something like **Traefik**. And the server would be something like FastAPI CLI with **Uvicorn**, running your FastAPI application. |
|||
|
|||
### Providing the `root_path` { #providing-the-root-path } |
|||
|
|||
To achieve this, you can use the command line option `--root-path` like: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1 |
|||
|
|||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
If you use Hypercorn, it also has the option `--root-path`. |
|||
|
|||
/// note | Technical Details |
|||
|
|||
The ASGI specification defines a `root_path` for this use case. |
|||
|
|||
And the `--root-path` command line option provides that `root_path`. |
|||
|
|||
/// |
|||
|
|||
### Checking the current `root_path` { #checking-the-current-root-path } |
|||
|
|||
You can get the current `root_path` used by your application for each request, it is part of the `scope` dictionary (that's part of the ASGI spec). |
|||
|
|||
Here we are including it in the message just for demonstration purposes. |
|||
|
|||
{* ../../docs_src/behind_a_proxy/tutorial001.py hl[8] *} |
|||
|
|||
Then, if you start Uvicorn with: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1 |
|||
|
|||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
The response would be something like: |
|||
|
|||
```JSON |
|||
{ |
|||
"message": "Hello World", |
|||
"root_path": "/api/v1" |
|||
} |
|||
``` |
|||
|
|||
### Setting the `root_path` in the FastAPI app { #setting-the-root-path-in-the-fastapi-app } |
|||
|
|||
Alternatively, if you don't have a way to provide a command line option like `--root-path` or equivalent, you can set the `root_path` parameter when creating your FastAPI app: |
|||
|
|||
{* ../../docs_src/behind_a_proxy/tutorial002.py hl[3] *} |
|||
|
|||
Passing the `root_path` to `FastAPI` would be the equivalent of passing the `--root-path` command line option to Uvicorn or Hypercorn. |
|||
|
|||
### About `root_path` { #about-root-path } |
|||
|
|||
Keep in mind that the server (Uvicorn) won't use that `root_path` for anything else than passing it to the app. |
|||
|
|||
But if you go with your browser to <a href="http://127.0.0.1:8000" class="external-link" target="_blank">http://127.0.0.1:8000/app</a> you will see the normal response: |
|||
|
|||
```JSON |
|||
{ |
|||
"message": "Hello World", |
|||
"root_path": "/api/v1" |
|||
} |
|||
``` |
|||
|
|||
So, it won't expect to be accessed at `http://127.0.0.1:8000/api/v1/app`. |
|||
|
|||
Uvicorn will expect the proxy to access Uvicorn at `http://127.0.0.1:8000/app`, and then it would be the proxy's responsibility to add the extra `/api/v1` prefix on top. |
|||
|
|||
## About proxies with a stripped path prefix { #about-proxies-with-a-stripped-path-prefix } |
|||
|
|||
Keep in mind that a proxy with stripped path prefix is only one of the ways to configure it. |
|||
|
|||
Probably in many cases the default will be that the proxy doesn't have a stripped path prefix. |
|||
|
|||
In a case like that (without a stripped path prefix), the proxy would listen on something like `https://myawesomeapp.com`, and then if the browser goes to `https://myawesomeapp.com/api/v1/app` and your server (e.g. Uvicorn) listens on `http://127.0.0.1:8000` the proxy (without a stripped path prefix) would access Uvicorn at the same path: `http://127.0.0.1:8000/api/v1/app`. |
|||
|
|||
## Testing locally with Traefik { #testing-locally-with-traefik } |
|||
|
|||
You can easily run the experiment locally with a stripped path prefix using <a href="https://docs.traefik.io/" class="external-link" target="_blank">Traefik</a>. |
|||
|
|||
<a href="https://github.com/containous/traefik/releases" class="external-link" target="_blank">Download Traefik</a>, it's a single binary, you can extract the compressed file and run it directly from the terminal. |
|||
|
|||
Then create a file `traefik.toml` with: |
|||
|
|||
```TOML hl_lines="3" |
|||
[entryPoints] |
|||
[entryPoints.http] |
|||
address = ":9999" |
|||
|
|||
[providers] |
|||
[providers.file] |
|||
filename = "routes.toml" |
|||
``` |
|||
|
|||
This tells Traefik to listen on port 9999 and to use another file `routes.toml`. |
|||
|
|||
/// tip |
|||
|
|||
We are using port 9999 instead of the standard HTTP port 80 so that you don't have to run it with admin (`sudo`) privileges. |
|||
|
|||
/// |
|||
|
|||
Now create that other file `routes.toml`: |
|||
|
|||
```TOML hl_lines="5 12 20" |
|||
[http] |
|||
[http.middlewares] |
|||
|
|||
[http.middlewares.api-stripprefix.stripPrefix] |
|||
prefixes = ["/api/v1"] |
|||
|
|||
[http.routers] |
|||
|
|||
[http.routers.app-http] |
|||
entryPoints = ["http"] |
|||
service = "app" |
|||
rule = "PathPrefix(`/api/v1`)" |
|||
middlewares = ["api-stripprefix"] |
|||
|
|||
[http.services] |
|||
|
|||
[http.services.app] |
|||
[http.services.app.loadBalancer] |
|||
[[http.services.app.loadBalancer.servers]] |
|||
url = "http://127.0.0.1:8000" |
|||
``` |
|||
|
|||
This file configures Traefik to use the path prefix `/api/v1`. |
|||
|
|||
And then Traefik will redirect its requests to your Uvicorn running on `http://127.0.0.1:8000`. |
|||
|
|||
Now start Traefik: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ ./traefik --configFile=traefik.toml |
|||
|
|||
INFO[0000] Configuration loaded from file: /home/user/awesomeapi/traefik.toml |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
And now start your app, using the `--root-path` option: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1 |
|||
|
|||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
### Check the responses { #check-the-responses } |
|||
|
|||
Now, if you go to the URL with the port for Uvicorn: <a href="http://127.0.0.1:8000/app" class="external-link" target="_blank">http://127.0.0.1:8000/app</a>, you will see the normal response: |
|||
|
|||
```JSON |
|||
{ |
|||
"message": "Hello World", |
|||
"root_path": "/api/v1" |
|||
} |
|||
``` |
|||
|
|||
/// tip |
|||
|
|||
Notice that even though you are accessing it at `http://127.0.0.1:8000/app` it shows the `root_path` of `/api/v1`, taken from the option `--root-path`. |
|||
|
|||
/// |
|||
|
|||
And now open the URL with the port for Traefik, including the path prefix: <a href="http://127.0.0.1:9999/api/v1/app" class="external-link" target="_blank">http://127.0.0.1:9999/api/v1/app</a>. |
|||
|
|||
We get the same response: |
|||
|
|||
```JSON |
|||
{ |
|||
"message": "Hello World", |
|||
"root_path": "/api/v1" |
|||
} |
|||
``` |
|||
|
|||
but this time at the URL with the prefix path provided by the proxy: `/api/v1`. |
|||
|
|||
Of course, the idea here is that everyone would access the app through the proxy, so the version with the path prefix `/api/v1` is the "correct" one. |
|||
|
|||
And the version without the path prefix (`http://127.0.0.1:8000/app`), provided by Uvicorn directly, would be exclusively for the _proxy_ (Traefik) to access it. |
|||
|
|||
That demonstrates how the Proxy (Traefik) uses the path prefix and how the server (Uvicorn) uses the `root_path` from the option `--root-path`. |
|||
|
|||
### Check the docs UI { #check-the-docs-ui } |
|||
|
|||
But here's the fun part. ✨ |
|||
|
|||
The "official" way to access the app would be through the proxy with the path prefix that we defined. So, as we would expect, if you try the docs UI served by Uvicorn directly, without the path prefix in the URL, it won't work, because it expects to be accessed through the proxy. |
|||
|
|||
You can check it at <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>: |
|||
|
|||
<img src="/img/tutorial/behind-a-proxy/image01.png"> |
|||
|
|||
But if we access the docs UI at the "official" URL using the proxy with port `9999`, at `/api/v1/docs`, it works correctly! 🎉 |
|||
|
|||
You can check it at <a href="http://127.0.0.1:9999/api/v1/docs" class="external-link" target="_blank">http://127.0.0.1:9999/api/v1/docs</a>: |
|||
|
|||
<img src="/img/tutorial/behind-a-proxy/image02.png"> |
|||
|
|||
Right as we wanted it. ✔️ |
|||
|
|||
This is because FastAPI uses this `root_path` to create the default `server` in OpenAPI with the URL provided by `root_path`. |
|||
|
|||
## Additional servers { #additional-servers } |
|||
|
|||
/// warning |
|||
|
|||
This is a more advanced use case. Feel free to skip it. |
|||
|
|||
/// |
|||
|
|||
By default, **FastAPI** will create a `server` in the OpenAPI schema with the URL for the `root_path`. |
|||
|
|||
But you can also provide other alternative `servers`, for example if you want *the same* docs UI to interact with both a staging and a production environment. |
|||
|
|||
If you pass a custom list of `servers` and there's a `root_path` (because your API lives behind a proxy), **FastAPI** will insert a "server" with this `root_path` at the beginning of the list. |
|||
|
|||
For example: |
|||
|
|||
{* ../../docs_src/behind_a_proxy/tutorial003.py hl[4:7] *} |
|||
|
|||
Will generate an OpenAPI schema like: |
|||
|
|||
```JSON hl_lines="5-7" |
|||
{ |
|||
"openapi": "3.1.0", |
|||
// More stuff here |
|||
"servers": [ |
|||
{ |
|||
"url": "/api/v1" |
|||
}, |
|||
{ |
|||
"url": "https://stag.example.com", |
|||
"description": "Staging environment" |
|||
}, |
|||
{ |
|||
"url": "https://prod.example.com", |
|||
"description": "Production environment" |
|||
} |
|||
], |
|||
"paths": { |
|||
// More stuff here |
|||
} |
|||
} |
|||
``` |
|||
|
|||
/// tip |
|||
|
|||
Notice the auto-generated server with a `url` value of `/api/v1`, taken from the `root_path`. |
|||
|
|||
/// |
|||
|
|||
In the docs UI at <a href="http://127.0.0.1:9999/api/v1/docs" class="external-link" target="_blank">http://127.0.0.1:9999/api/v1/docs</a> it would look like: |
|||
|
|||
<img src="/img/tutorial/behind-a-proxy/image03.png"> |
|||
|
|||
/// tip |
|||
|
|||
The docs UI will interact with the server that you select. |
|||
|
|||
/// |
|||
|
|||
### Disable automatic server from `root_path` { #disable-automatic-server-from-root-path } |
|||
|
|||
If you don't want **FastAPI** to include an automatic server using the `root_path`, you can use the parameter `root_path_in_servers=False`: |
|||
|
|||
{* ../../docs_src/behind_a_proxy/tutorial004.py hl[9] *} |
|||
|
|||
and then it won't include it in the OpenAPI schema. |
|||
|
|||
## Mounting a sub-application { #mounting-a-sub-application } |
|||
|
|||
If you need to mount a sub-application (as described in [Sub Applications - Mounts](sub-applications.md){.internal-link target=_blank}) while also using a proxy with `root_path`, you can do it normally, as you would expect. |
|||
|
|||
FastAPI will internally use the `root_path` smartly, so it will just work. ✨ |
|||
@ -0,0 +1,313 @@ |
|||
# Custom Response - HTML, Stream, File, others { #custom-response-html-stream-file-others } |
|||
|
|||
By default, **FastAPI** will return the responses using `JSONResponse`. |
|||
|
|||
You can override it by returning a `Response` directly as seen in [Return a Response directly](response-directly.md){.internal-link target=_blank}. |
|||
|
|||
But if you return a `Response` directly (or any subclass, like `JSONResponse`), the data won't be automatically converted (even if you declare a `response_model`), and the documentation won't be automatically generated (for example, including the specific "media type", in the HTTP header `Content-Type` as part of the generated OpenAPI). |
|||
|
|||
But you can also declare the `Response` that you want to be used (e.g. any `Response` subclass), in the *path operation decorator* using the `response_class` parameter. |
|||
|
|||
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 `ORJSONResponse` { #use-orjsonresponse } |
|||
|
|||
For example, if you are squeezing performance, you can install and use <a href="https://github.com/ijl/orjson" class="external-link" target="_blank">`orjson`</a> and set the response to be `ORJSONResponse`. |
|||
|
|||
Import the `Response` class (sub-class) you want to use and declare it in the *path operation decorator*. |
|||
|
|||
For large responses, returning a `Response` directly is much faster than returning a dictionary. |
|||
|
|||
This is because by default, FastAPI will inspect every item inside and make sure it is serializable as JSON, using the same [JSON Compatible Encoder](../tutorial/encoder.md){.internal-link target=_blank} explained in the tutorial. This is what allows you to return **arbitrary objects**, for example database models. |
|||
|
|||
But if you are certain that the content that you are returning is **serializable with JSON**, you can pass it directly to the response class and avoid the extra overhead that FastAPI would have by passing your return content through the `jsonable_encoder` before passing it to the response class. |
|||
|
|||
{* ../../docs_src/custom_response/tutorial001b.py hl[2,7] *} |
|||
|
|||
/// 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. |
|||
|
|||
/// |
|||
|
|||
/// tip |
|||
|
|||
The `ORJSONResponse` is only available in FastAPI, not in Starlette. |
|||
|
|||
/// |
|||
|
|||
## HTML Response { #html-response } |
|||
|
|||
To return a response with HTML directly from **FastAPI**, use `HTMLResponse`. |
|||
|
|||
* Import `HTMLResponse`. |
|||
* Pass `HTMLResponse` as the parameter `response_class` of your *path operation decorator*. |
|||
|
|||
{* ../../docs_src/custom_response/tutorial002.py hl[2,7] *} |
|||
|
|||
/// 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 `Response` { #return-a-response } |
|||
|
|||
As seen in [Return a Response directly](response-directly.md){.internal-link target=_blank}, 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: |
|||
|
|||
{* ../../docs_src/custom_response/tutorial003.py hl[2,7,19] *} |
|||
|
|||
/// 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 you returned. |
|||
|
|||
/// |
|||
|
|||
### Document in OpenAPI and override `Response` { #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 { #return-an-htmlresponse-directly } |
|||
|
|||
For example, it could be something like: |
|||
|
|||
{* ../../docs_src/custom_response/tutorial004.py hl[7,21,23] *} |
|||
|
|||
In this example, the function `generate_html_response()` already generates and returns a `Response` instead of returning 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` too, **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"> |
|||
|
|||
## Available responses { #available-responses } |
|||
|
|||
Here are some of the available responses. |
|||
|
|||
Keep in mind that you can use `Response` to return anything else, or even create a custom sub-class. |
|||
|
|||
/// note | Technical Details |
|||
|
|||
You could also use `from starlette.responses import HTMLResponse`. |
|||
|
|||
**FastAPI** provides the same `starlette.responses` as `fastapi.responses` just as a convenience for you, the developer. But most of the available responses come directly from Starlette. |
|||
|
|||
/// |
|||
|
|||
### `Response` { #response } |
|||
|
|||
The main `Response` class, all the other responses inherit from it. |
|||
|
|||
You can return it directly. |
|||
|
|||
It accepts the following parameters: |
|||
|
|||
* `content` - A `str` or `bytes`. |
|||
* `status_code` - An `int` HTTP status code. |
|||
* `headers` - A `dict` of strings. |
|||
* `media_type` - A `str` giving the media type. E.g. `"text/html"`. |
|||
|
|||
FastAPI (actually Starlette) will automatically include a Content-Length header. It will also include a Content-Type header, based on the `media_type` and appending a charset for text types. |
|||
|
|||
{* ../../docs_src/response_directly/tutorial002.py hl[1,18] *} |
|||
|
|||
### `HTMLResponse` { #htmlresponse } |
|||
|
|||
Takes some text or bytes and returns an HTML response, as you read above. |
|||
|
|||
### `PlainTextResponse` { #plaintextresponse } |
|||
|
|||
Takes some text or bytes and returns a plain text response. |
|||
|
|||
{* ../../docs_src/custom_response/tutorial005.py hl[2,7,9] *} |
|||
|
|||
### `JSONResponse` { #jsonresponse } |
|||
|
|||
Takes some data and returns an `application/json` encoded response. |
|||
|
|||
This is the default response used in **FastAPI**, as you read above. |
|||
|
|||
### `ORJSONResponse` { #orjsonresponse } |
|||
|
|||
A fast alternative JSON response using <a href="https://github.com/ijl/orjson" class="external-link" target="_blank">`orjson`</a>, as you read above. |
|||
|
|||
/// info |
|||
|
|||
This requires installing `orjson` for example with `pip install orjson`. |
|||
|
|||
/// |
|||
|
|||
### `UJSONResponse` { #ujsonresponse } |
|||
|
|||
An alternative JSON response using <a href="https://github.com/ultrajson/ultrajson" class="external-link" target="_blank">`ujson`</a>. |
|||
|
|||
/// info |
|||
|
|||
This requires installing `ujson` for example with `pip install ujson`. |
|||
|
|||
/// |
|||
|
|||
/// warning |
|||
|
|||
`ujson` is less careful than Python's built-in implementation in how it handles some edge-cases. |
|||
|
|||
/// |
|||
|
|||
{* ../../docs_src/custom_response/tutorial001.py hl[2,7] *} |
|||
|
|||
/// tip |
|||
|
|||
It's possible that `ORJSONResponse` might be a faster alternative. |
|||
|
|||
/// |
|||
|
|||
### `RedirectResponse` { #redirectresponse } |
|||
|
|||
Returns an HTTP redirect. Uses a 307 status code (Temporary Redirect) by default. |
|||
|
|||
You can return a `RedirectResponse` directly: |
|||
|
|||
{* ../../docs_src/custom_response/tutorial006.py hl[2,9] *} |
|||
|
|||
--- |
|||
|
|||
Or you can use it in the `response_class` parameter: |
|||
|
|||
|
|||
{* ../../docs_src/custom_response/tutorial006b.py hl[2,7,9] *} |
|||
|
|||
If you do that, then you can return the URL directly from your *path operation* function. |
|||
|
|||
In this case, the `status_code` used will be the default one for the `RedirectResponse`, which is `307`. |
|||
|
|||
--- |
|||
|
|||
You can also use the `status_code` parameter combined with the `response_class` parameter: |
|||
|
|||
{* ../../docs_src/custom_response/tutorial006c.py hl[2,7,9] *} |
|||
|
|||
### `StreamingResponse` { #streamingresponse } |
|||
|
|||
Takes an async generator or a normal generator/iterator and streams the response body. |
|||
|
|||
{* ../../docs_src/custom_response/tutorial007.py hl[2,14] *} |
|||
|
|||
#### Using `StreamingResponse` with file-like objects { #using-streamingresponse-with-file-like-objects } |
|||
|
|||
If you have a <a href="https://docs.python.org/3/glossary.html#term-file-like-object" class="external-link" target="_blank">file-like</a> object (e.g. the object returned by `open()`), you can create a generator function to iterate over that file-like object. |
|||
|
|||
That way, you don't have to read it all first in memory, and you can pass that generator function to the `StreamingResponse`, and return it. |
|||
|
|||
This includes many libraries to interact with cloud storage, video processing, and others. |
|||
|
|||
{* ../../docs_src/custom_response/tutorial008.py hl[2,10:12,14] *} |
|||
|
|||
1. This is the generator function. It's a "generator function" because it contains `yield` statements inside. |
|||
2. By using a `with` block, we make sure that the file-like object is closed after the generator function is done. So, after it finishes sending the response. |
|||
3. This `yield from` tells the function to iterate over that thing named `file_like`. And then, for each part iterated, yield that part as coming from this generator function (`iterfile`). |
|||
|
|||
So, it is a generator function that transfers the "generating" work to something else internally. |
|||
|
|||
By doing it this way, we can put it in a `with` block, and that way, ensure that the file-like object is closed after finishing. |
|||
|
|||
/// tip |
|||
|
|||
Notice that here as we are using standard `open()` that doesn't support `async` and `await`, we declare the path operation with normal `def`. |
|||
|
|||
/// |
|||
|
|||
### `FileResponse` { #fileresponse } |
|||
|
|||
Asynchronously streams a file as the response. |
|||
|
|||
Takes a different set of arguments to instantiate than the other response types: |
|||
|
|||
* `path` - The file path to the file to stream. |
|||
* `headers` - Any custom headers to include, as a dictionary. |
|||
* `media_type` - A string giving the media type. If unset, the filename or path will be used to infer a media type. |
|||
* `filename` - If set, this will be included in the response `Content-Disposition`. |
|||
|
|||
File responses will include appropriate `Content-Length`, `Last-Modified` and `ETag` headers. |
|||
|
|||
{* ../../docs_src/custom_response/tutorial009.py hl[2,10] *} |
|||
|
|||
You can also use the `response_class` parameter: |
|||
|
|||
{* ../../docs_src/custom_response/tutorial009b.py hl[2,8,10] *} |
|||
|
|||
In this case, you can return the file path directly from your *path operation* function. |
|||
|
|||
## Custom response class { #custom-response-class } |
|||
|
|||
You can create your own custom response class, inheriting from `Response` and using it. |
|||
|
|||
For example, let's say that you want to use <a href="https://github.com/ijl/orjson" class="external-link" target="_blank">`orjson`</a>, but with some custom settings not used in the included `ORJSONResponse` class. |
|||
|
|||
Let's say you want it to return indented and formatted JSON, so you want to use the orjson option `orjson.OPT_INDENT_2`. |
|||
|
|||
You could create a `CustomORJSONResponse`. The main thing you have to do is create a `Response.render(content)` method that returns the content as `bytes`: |
|||
|
|||
{* ../../docs_src/custom_response/tutorial009c.py hl[9:14,17] *} |
|||
|
|||
Now instead of returning: |
|||
|
|||
```json |
|||
{"message": "Hello World"} |
|||
``` |
|||
|
|||
...this response will return: |
|||
|
|||
```json |
|||
{ |
|||
"message": "Hello World" |
|||
} |
|||
``` |
|||
|
|||
Of course, you will probably find much better ways to take advantage of this than formatting JSON. 😉 |
|||
|
|||
## Default response class { #default-response-class } |
|||
|
|||
When creating a **FastAPI** class instance or an `APIRouter` you can specify which response class to use by default. |
|||
|
|||
The parameter that defines this is `default_response_class`. |
|||
|
|||
In the example below, **FastAPI** will use `ORJSONResponse` by default, in all *path operations*, instead of `JSONResponse`. |
|||
|
|||
{* ../../docs_src/custom_response/tutorial010.py hl[2,4] *} |
|||
|
|||
/// tip |
|||
|
|||
You can still override `response_class` in *path operations* as before. |
|||
|
|||
/// |
|||
|
|||
## Additional documentation { #additional-documentation } |
|||
|
|||
You can also declare the media type and many other details in OpenAPI using `responses`: [Additional Responses in OpenAPI](additional-responses.md){.internal-link target=_blank}. |
|||
@ -0,0 +1,95 @@ |
|||
# Using Dataclasses { #using-dataclasses } |
|||
|
|||
FastAPI is built on top of **Pydantic**, and I have been showing you how to use Pydantic models to declare requests and responses. |
|||
|
|||
But FastAPI also supports using <a href="https://docs.python.org/3/library/dataclasses.html" class="external-link" target="_blank">`dataclasses`</a> the same way: |
|||
|
|||
{* ../../docs_src/dataclasses/tutorial001.py hl[1,7:12,19:20] *} |
|||
|
|||
This is still supported thanks to **Pydantic**, as it has <a href="https://docs.pydantic.dev/latest/concepts/dataclasses/#use-of-stdlib-dataclasses-with-basemodel" class="external-link" target="_blank">internal support for `dataclasses`</a>. |
|||
|
|||
So, even with the code above that doesn't use Pydantic explicitly, FastAPI is using Pydantic to convert those standard dataclasses to Pydantic's own flavor of dataclasses. |
|||
|
|||
And of course, it supports the same: |
|||
|
|||
* data validation |
|||
* data serialization |
|||
* data documentation, etc. |
|||
|
|||
This works the same way as with Pydantic models. And it is actually achieved in the same way underneath, using Pydantic. |
|||
|
|||
/// info |
|||
|
|||
Keep in mind that dataclasses can't do everything Pydantic models can do. |
|||
|
|||
So, you might still need to use Pydantic models. |
|||
|
|||
But if you have a bunch of dataclasses laying around, this is a nice trick to use them to power a web API using FastAPI. 🤓 |
|||
|
|||
/// |
|||
|
|||
## Dataclasses in `response_model` { #dataclasses-in-response-model } |
|||
|
|||
You can also use `dataclasses` in the `response_model` parameter: |
|||
|
|||
{* ../../docs_src/dataclasses/tutorial002.py hl[1,7:13,19] *} |
|||
|
|||
The dataclass will be automatically converted to a Pydantic dataclass. |
|||
|
|||
This way, its schema will show up in the API docs user interface: |
|||
|
|||
<img src="/img/tutorial/dataclasses/image01.png"> |
|||
|
|||
## Dataclasses in Nested Data Structures { #dataclasses-in-nested-data-structures } |
|||
|
|||
You can also combine `dataclasses` with other type annotations to make nested data structures. |
|||
|
|||
In some cases, you might still have to use Pydantic's version of `dataclasses`. For example, if you have errors with the automatically generated API documentation. |
|||
|
|||
In that case, you can simply swap the standard `dataclasses` with `pydantic.dataclasses`, which is a drop-in replacement: |
|||
|
|||
{* ../../docs_src/dataclasses/tutorial003.py hl[1,5,8:11,14:17,23:25,28] *} |
|||
|
|||
1. We still import `field` from standard `dataclasses`. |
|||
|
|||
2. `pydantic.dataclasses` is a drop-in replacement for `dataclasses`. |
|||
|
|||
3. The `Author` dataclass includes a list of `Item` dataclasses. |
|||
|
|||
4. The `Author` dataclass is used as the `response_model` parameter. |
|||
|
|||
5. You can use other standard type annotations with dataclasses as the request body. |
|||
|
|||
In this case, it's a list of `Item` dataclasses. |
|||
|
|||
6. Here we are returning a dictionary that contains `items` which is a list of dataclasses. |
|||
|
|||
FastAPI is still capable of <abbr title="converting the data to a format that can be transmitted">serializing</abbr> the data to JSON. |
|||
|
|||
7. Here the `response_model` is using a type annotation of a list of `Author` dataclasses. |
|||
|
|||
Again, you can combine `dataclasses` with standard type annotations. |
|||
|
|||
8. Notice that this *path operation function* uses regular `def` instead of `async def`. |
|||
|
|||
As always, in FastAPI you can combine `def` and `async def` as needed. |
|||
|
|||
If you need a refresher about when to use which, check out the section _"In a hurry?"_ in the docs about [`async` and `await`](../async.md#in-a-hurry){.internal-link target=_blank}. |
|||
|
|||
9. This *path operation function* is not returning dataclasses (although it could), but a list of dictionaries with internal data. |
|||
|
|||
FastAPI will use the `response_model` parameter (that includes dataclasses) to convert the response. |
|||
|
|||
You can combine `dataclasses` with other type annotations in many different combinations to form complex data structures. |
|||
|
|||
Check the in-code annotation tips above to see more specific details. |
|||
|
|||
## Learn More { #learn-more } |
|||
|
|||
You can also combine `dataclasses` with other Pydantic models, inherit from them, include them in your own models, etc. |
|||
|
|||
To learn more, check the <a href="https://docs.pydantic.dev/latest/concepts/dataclasses/" class="external-link" target="_blank">Pydantic docs about dataclasses</a>. |
|||
|
|||
## Version { #version } |
|||
|
|||
This is available since FastAPI version `0.67.0`. 🔖 |
|||
@ -0,0 +1,165 @@ |
|||
# Lifespan Events { #lifespan-events } |
|||
|
|||
You can define logic (code) that should be executed before the application **starts up**. This means that this code will be executed **once**, **before** the application **starts receiving requests**. |
|||
|
|||
The same way, you can define logic (code) that should be executed when the application is **shutting down**. In this case, this code will be executed **once**, **after** having handled possibly **many requests**. |
|||
|
|||
Because this code is executed before the application **starts** taking requests, and right after it **finishes** handling requests, it covers the whole application **lifespan** (the word "lifespan" will be important in a second 😉). |
|||
|
|||
This can be very useful for setting up **resources** that you need to use for the whole app, and that are **shared** among requests, and/or that you need to **clean up** afterwards. For example, a database connection pool, or loading a shared machine learning model. |
|||
|
|||
## Use Case { #use-case } |
|||
|
|||
Let's start with an example **use case** and then see how to solve it with this. |
|||
|
|||
Let's imagine that you have some **machine learning models** that you want to use to handle requests. 🤖 |
|||
|
|||
The same models are shared among requests, so, it's not one model per request, or one per user or something similar. |
|||
|
|||
Let's imagine that loading the model can **take quite some time**, because it has to read a lot of **data from disk**. So you don't want to do it for every request. |
|||
|
|||
You could load it at the top level of the module/file, but that would also mean that it would **load the model** even if you are just running a simple automated test, then that test would be **slow** because it would have to wait for the model to load before being able to run an independent part of the code. |
|||
|
|||
That's what we'll solve, let's load the model before the requests are handled, but only right before the application starts receiving requests, not while the code is being loaded. |
|||
|
|||
## Lifespan { #lifespan } |
|||
|
|||
You can define this *startup* and *shutdown* logic using the `lifespan` parameter of the `FastAPI` app, and a "context manager" (I'll show you what that is in a second). |
|||
|
|||
Let's start with an example and then see it in detail. |
|||
|
|||
We create an async function `lifespan()` with `yield` like this: |
|||
|
|||
{* ../../docs_src/events/tutorial003.py hl[16,19] *} |
|||
|
|||
Here we are simulating the expensive *startup* operation of loading the model by putting the (fake) model function in the dictionary with machine learning models before the `yield`. This code will be executed **before** the application **starts taking requests**, during the *startup*. |
|||
|
|||
And then, right after the `yield`, we unload the model. This code will be executed **after** the application **finishes handling requests**, right before the *shutdown*. This could, for example, release resources like memory or a GPU. |
|||
|
|||
/// tip |
|||
|
|||
The `shutdown` would happen when you are **stopping** the application. |
|||
|
|||
Maybe you need to start a new version, or you just got tired of running it. 🤷 |
|||
|
|||
/// |
|||
|
|||
### Lifespan function { #lifespan-function } |
|||
|
|||
The first thing to notice, is that we are defining an async function with `yield`. This is very similar to Dependencies with `yield`. |
|||
|
|||
{* ../../docs_src/events/tutorial003.py hl[14:19] *} |
|||
|
|||
The first part of the function, before the `yield`, will be executed **before** the application starts. |
|||
|
|||
And the part after the `yield` will be executed **after** the application has finished. |
|||
|
|||
### Async Context Manager { #async-context-manager } |
|||
|
|||
If you check, the function is decorated with an `@asynccontextmanager`. |
|||
|
|||
That converts the function into something called an "**async context manager**". |
|||
|
|||
{* ../../docs_src/events/tutorial003.py hl[1,13] *} |
|||
|
|||
A **context manager** in Python is something that you can use in a `with` statement, for example, `open()` can be used as a context manager: |
|||
|
|||
```Python |
|||
with open("file.txt") as file: |
|||
file.read() |
|||
``` |
|||
|
|||
In recent versions of Python, there's also an **async context manager**. You would use it with `async with`: |
|||
|
|||
```Python |
|||
async with lifespan(app): |
|||
await do_stuff() |
|||
``` |
|||
|
|||
When you create a context manager or an async context manager like above, what it does is that, before entering the `with` block, it will execute the code before the `yield`, and after exiting the `with` block, it will execute the code after the `yield`. |
|||
|
|||
In our code example above, we don't use it directly, but we pass it to FastAPI for it to use it. |
|||
|
|||
The `lifespan` parameter of the `FastAPI` app takes an **async context manager**, so we can pass our new `lifespan` async context manager to it. |
|||
|
|||
{* ../../docs_src/events/tutorial003.py hl[22] *} |
|||
|
|||
## Alternative Events (deprecated) { #alternative-events-deprecated } |
|||
|
|||
/// warning |
|||
|
|||
The recommended way to handle the *startup* and *shutdown* is using the `lifespan` parameter of the `FastAPI` app as described above. If you provide a `lifespan` parameter, `startup` and `shutdown` event handlers will no longer be called. It's all `lifespan` or all events, not both. |
|||
|
|||
You can probably skip this part. |
|||
|
|||
/// |
|||
|
|||
There's an alternative way to define this logic to be executed during *startup* and during *shutdown*. |
|||
|
|||
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 { #startup-event } |
|||
|
|||
To add a function that should be run before the application starts, declare it with the event `"startup"`: |
|||
|
|||
{* ../../docs_src/events/tutorial001.py hl[8] *} |
|||
|
|||
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 { #shutdown-event } |
|||
|
|||
To add a function that should be run when the application is shutting down, declare it with the event `"shutdown"`: |
|||
|
|||
{* ../../docs_src/events/tutorial002.py hl[6] *} |
|||
|
|||
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`. |
|||
|
|||
/// |
|||
|
|||
### `startup` and `shutdown` together { #startup-and-shutdown-together } |
|||
|
|||
There's a high chance that the logic for your *startup* and *shutdown* is connected, you might want to start something and then finish it, acquire a resource and then release it, etc. |
|||
|
|||
Doing that in separated functions that don't share logic or variables together is more difficult as you would need to store values in global variables or similar tricks. |
|||
|
|||
Because of that, it's now recommended to instead use the `lifespan` as explained above. |
|||
|
|||
## Technical Details { #technical-details } |
|||
|
|||
Just a technical detail for the curious nerds. 🤓 |
|||
|
|||
Underneath, in the ASGI technical specification, this is part of the <a href="https://asgi.readthedocs.io/en/latest/specs/lifespan.html" class="external-link" target="_blank">Lifespan Protocol</a>, and it defines events called `startup` and `shutdown`. |
|||
|
|||
/// info |
|||
|
|||
You can read more about the Starlette `lifespan` handlers in <a href="https://www.starlette.io/lifespan/" class="external-link" target="_blank">Starlette's Lifespan' docs</a>. |
|||
|
|||
Including how to handle lifespan state that can be used in other areas of your code. |
|||
|
|||
/// |
|||
|
|||
## Sub Applications { #sub-applications } |
|||
|
|||
🚨 Keep in mind that these lifespan events (startup and shutdown) will only be executed for the main application, not for [Sub Applications - Mounts](sub-applications.md){.internal-link target=_blank}. |
|||
@ -0,0 +1,208 @@ |
|||
# Generating SDKs { #generating-sdks } |
|||
|
|||
Because **FastAPI** is based on the **OpenAPI** specification, its APIs can be described in a standard format that many tools understand. |
|||
|
|||
This makes it easy to generate up-to-date **documentation**, client libraries (<abbr title="Software Development Kits">**SDKs**</abbr>) in multiple languages, and **testing** or **automation workflows** that stay in sync with your code. |
|||
|
|||
In this guide, you'll learn how to generate a **TypeScript SDK** for your FastAPI backend. |
|||
|
|||
## Open Source SDK Generators { #open-source-sdk-generators } |
|||
|
|||
A versatile option is the <a href="https://openapi-generator.tech/" class="external-link" target="_blank">OpenAPI Generator</a>, which supports **many programming languages** and can generate SDKs from your OpenAPI specification. |
|||
|
|||
For **TypeScript clients**, <a href="https://heyapi.dev/" class="external-link" target="_blank">Hey API</a> is a purpose-built solution, providing an optimized experience for the TypeScript ecosystem. |
|||
|
|||
You can discover more SDK generators on <a href="https://openapi.tools/#sdk" class="external-link" target="_blank">OpenAPI.Tools</a>. |
|||
|
|||
/// tip |
|||
|
|||
FastAPI automatically generates **OpenAPI 3.1** specifications, so any tool you use must support this version. |
|||
|
|||
/// |
|||
|
|||
## SDK Generators from FastAPI Sponsors { #sdk-generators-from-fastapi-sponsors } |
|||
|
|||
This section highlights **venture-backed** and **company-supported** solutions from companies that sponsor FastAPI. These products provide **additional features** and **integrations** on top of high-quality generated SDKs. |
|||
|
|||
By ✨ [**sponsoring FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨, these companies help ensure the framework and its **ecosystem** remain healthy and **sustainable**. |
|||
|
|||
Their sponsorship also demonstrates a strong commitment to the FastAPI **community** (you), showing that they care not only about offering a **great service** but also about supporting a **robust and thriving framework**, FastAPI. 🙇 |
|||
|
|||
For example, you might want to try: |
|||
|
|||
* <a href="https://speakeasy.com/editor?utm_source=fastapi+repo&utm_medium=github+sponsorship" class="external-link" target="_blank">Speakeasy</a> |
|||
* <a href="https://www.stainless.com/?utm_source=fastapi&utm_medium=referral" class="external-link" target="_blank">Stainless</a> |
|||
* <a href="https://developers.liblab.com/tutorials/sdk-for-fastapi?utm_source=fastapi" class="external-link" target="_blank">liblab</a> |
|||
|
|||
Some of these solutions may also be open source or offer free tiers, so you can try them without a financial commitment. Other commercial SDK generators are available and can be found online. 🤓 |
|||
|
|||
## Create a TypeScript SDK { #create-a-typescript-sdk } |
|||
|
|||
Let's start with a simple FastAPI application: |
|||
|
|||
{* ../../docs_src/generate_clients/tutorial001_py39.py hl[7:9,12:13,16:17,21] *} |
|||
|
|||
Notice that the *path operations* define the models they use for request payload and response payload, using the models `Item` and `ResponseMessage`. |
|||
|
|||
### API Docs { #api-docs } |
|||
|
|||
If you go to `/docs`, you will see that it has the **schemas** for the data to be sent in requests and received in responses: |
|||
|
|||
<img src="/img/tutorial/generate-clients/image01.png"> |
|||
|
|||
You can see those schemas because they were declared with the models in the app. |
|||
|
|||
That information is available in the app's **OpenAPI schema**, and then shown in the API docs. |
|||
|
|||
That same information from the models that is included in OpenAPI is what can be used to **generate the client code**. |
|||
|
|||
### Hey API { #hey-api } |
|||
|
|||
Once we have a FastAPI app with the models, we can use Hey API to generate a TypeScript client. The fastest way to do that is via npx. |
|||
|
|||
```sh |
|||
npx @hey-api/openapi-ts -i http://localhost:8000/openapi.json -o src/client |
|||
``` |
|||
|
|||
This will generate a TypeScript SDK in `./src/client`. |
|||
|
|||
You can learn how to <a href="https://heyapi.dev/openapi-ts/get-started" class="external-link" target="_blank">install `@hey-api/openapi-ts`</a> and read about the <a href="https://heyapi.dev/openapi-ts/output" class="external-link" target="_blank">generated output</a> on their website. |
|||
|
|||
### Using the SDK { #using-the-sdk } |
|||
|
|||
Now you can import and use the client code. It could look like this, notice that you get autocompletion for the methods: |
|||
|
|||
<img src="/img/tutorial/generate-clients/image02.png"> |
|||
|
|||
You will also get autocompletion for the payload to send: |
|||
|
|||
<img src="/img/tutorial/generate-clients/image03.png"> |
|||
|
|||
/// tip |
|||
|
|||
Notice the autocompletion for `name` and `price`, that was defined in the FastAPI application, in the `Item` model. |
|||
|
|||
/// |
|||
|
|||
You will have inline errors for the data that you send: |
|||
|
|||
<img src="/img/tutorial/generate-clients/image04.png"> |
|||
|
|||
The response object will also have autocompletion: |
|||
|
|||
<img src="/img/tutorial/generate-clients/image05.png"> |
|||
|
|||
## FastAPI App with Tags { #fastapi-app-with-tags } |
|||
|
|||
In many cases, your FastAPI app will be bigger, and you will probably use tags to separate different groups of *path operations*. |
|||
|
|||
For example, you could have a section for **items** and another section for **users**, and they could be separated by tags: |
|||
|
|||
{* ../../docs_src/generate_clients/tutorial002_py39.py hl[21,26,34] *} |
|||
|
|||
### Generate a TypeScript Client with Tags { #generate-a-typescript-client-with-tags } |
|||
|
|||
If you generate a client for a FastAPI app using tags, it will normally also separate the client code based on the tags. |
|||
|
|||
This way, you will be able to have things ordered and grouped correctly for the client code: |
|||
|
|||
<img src="/img/tutorial/generate-clients/image06.png"> |
|||
|
|||
In this case, you have: |
|||
|
|||
* `ItemsService` |
|||
* `UsersService` |
|||
|
|||
### Client Method Names { #client-method-names } |
|||
|
|||
Right now, the generated method names like `createItemItemsPost` don't look very clean: |
|||
|
|||
```TypeScript |
|||
ItemsService.createItemItemsPost({name: "Plumbus", price: 5}) |
|||
``` |
|||
|
|||
...that's because the client generator uses the OpenAPI internal **operation ID** for each *path operation*. |
|||
|
|||
OpenAPI requires that each operation ID is unique across all the *path operations*, so FastAPI uses the **function name**, the **path**, and the **HTTP method/operation** to generate that operation ID, because that way it can make sure that the operation IDs are unique. |
|||
|
|||
But I'll show you how to improve that next. 🤓 |
|||
|
|||
## Custom Operation IDs and Better Method Names { #custom-operation-ids-and-better-method-names } |
|||
|
|||
You can **modify** the way these operation IDs are **generated** to make them simpler and have **simpler method names** in the clients. |
|||
|
|||
In this case, you will have to ensure that each operation ID is **unique** in some other way. |
|||
|
|||
For example, you could make sure that each *path operation* has a tag, and then generate the operation ID based on the **tag** and the *path operation* **name** (the function name). |
|||
|
|||
### Custom Generate Unique ID Function { #custom-generate-unique-id-function } |
|||
|
|||
FastAPI uses a **unique ID** for each *path operation*, which is used for the **operation ID** and also for the names of any needed custom models, for requests or responses. |
|||
|
|||
You can customize that function. It takes an `APIRoute` and outputs a string. |
|||
|
|||
For example, here it is using the first tag (you will probably have only one tag) and the *path operation* name (the function name). |
|||
|
|||
You can then pass that custom function to **FastAPI** as the `generate_unique_id_function` parameter: |
|||
|
|||
{* ../../docs_src/generate_clients/tutorial003_py39.py hl[6:7,10] *} |
|||
|
|||
### Generate a TypeScript Client with Custom Operation IDs { #generate-a-typescript-client-with-custom-operation-ids } |
|||
|
|||
Now, if you generate the client again, you will see that it has the improved method names: |
|||
|
|||
<img src="/img/tutorial/generate-clients/image07.png"> |
|||
|
|||
As you see, the method names now have the tag and then the function name, now they don't include information from the URL path and the HTTP operation. |
|||
|
|||
### Preprocess the OpenAPI Specification for the Client Generator { #preprocess-the-openapi-specification-for-the-client-generator } |
|||
|
|||
The generated code still has some **duplicated information**. |
|||
|
|||
We already know that this method is related to the **items** because that word is in the `ItemsService` (taken from the tag), but we still have the tag name prefixed in the method name too. 😕 |
|||
|
|||
We will probably still want to keep it for OpenAPI in general, as that will ensure that the operation IDs are **unique**. |
|||
|
|||
But for the generated client, we could **modify** the OpenAPI operation IDs right before generating the clients, just to make those method names nicer and **cleaner**. |
|||
|
|||
We could download the OpenAPI JSON to a file `openapi.json` and then we could **remove that prefixed tag** with a script like this: |
|||
|
|||
{* ../../docs_src/generate_clients/tutorial004.py *} |
|||
|
|||
//// tab | Node.js |
|||
|
|||
```Javascript |
|||
{!> ../../docs_src/generate_clients/tutorial004.js!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
With that, the operation IDs would be renamed from things like `items-get_items` to just `get_items`, that way the client generator can generate simpler method names. |
|||
|
|||
### Generate a TypeScript Client with the Preprocessed OpenAPI { #generate-a-typescript-client-with-the-preprocessed-openapi } |
|||
|
|||
Since the end result is now in an `openapi.json` file, you need to update your input location: |
|||
|
|||
```sh |
|||
npx @hey-api/openapi-ts -i ./openapi.json -o src/client |
|||
``` |
|||
|
|||
After generating the new client, you would now have **clean method names**, with all the **autocompletion**, **inline errors**, etc: |
|||
|
|||
<img src="/img/tutorial/generate-clients/image08.png"> |
|||
|
|||
## Benefits { #benefits } |
|||
|
|||
When using the automatically generated clients, you would get **autocompletion** for: |
|||
|
|||
* Methods. |
|||
* Request payloads in the body, query parameters, etc. |
|||
* Response payloads. |
|||
|
|||
You would also have **inline errors** for everything. |
|||
|
|||
And whenever you update the backend code, and **regenerate** the frontend, it would have any new *path operations* available as methods, the old ones removed, and any other change would be reflected on the generated code. 🤓 |
|||
|
|||
This also means that if something changed, it will be **reflected** on the client code automatically. And if you **build** the client, it will error out if you have any **mismatch** in the data used. |
|||
|
|||
So, you would **detect many errors** very early in the development cycle instead of having to wait for the errors to show up to your final users in production and then trying to debug where the problem is. ✨ |
|||
@ -0,0 +1,97 @@ |
|||
# Advanced Middleware { #advanced-middleware } |
|||
|
|||
In the main tutorial you read how to add [Custom Middleware](../tutorial/middleware.md){.internal-link target=_blank} to your application. |
|||
|
|||
And then you also read how to handle [CORS with the `CORSMiddleware`](../tutorial/cors.md){.internal-link target=_blank}. |
|||
|
|||
In this section we'll see how to use other middlewares. |
|||
|
|||
## Adding ASGI middlewares { #adding-asgi-middlewares } |
|||
|
|||
As **FastAPI** is based on Starlette and implements the <abbr title="Asynchronous Server Gateway Interface">ASGI</abbr> specification, you can use any ASGI middleware. |
|||
|
|||
A middleware doesn't have to be made for FastAPI or Starlette to work, as long as it follows the ASGI spec. |
|||
|
|||
In general, ASGI middlewares are classes that expect to receive an ASGI app as the first argument. |
|||
|
|||
So, in the documentation for third-party ASGI middlewares they will probably tell you to do something like: |
|||
|
|||
```Python |
|||
from unicorn import UnicornMiddleware |
|||
|
|||
app = SomeASGIApp() |
|||
|
|||
new_app = UnicornMiddleware(app, some_config="rainbow") |
|||
``` |
|||
|
|||
But FastAPI (actually Starlette) provides a simpler way to do it that makes sure that the internal middlewares handle server errors and custom exception handlers work properly. |
|||
|
|||
For that, you use `app.add_middleware()` (as in the example for CORS). |
|||
|
|||
```Python |
|||
from fastapi import FastAPI |
|||
from unicorn import UnicornMiddleware |
|||
|
|||
app = FastAPI() |
|||
|
|||
app.add_middleware(UnicornMiddleware, some_config="rainbow") |
|||
``` |
|||
|
|||
`app.add_middleware()` receives a middleware class as the first argument and any additional arguments to be passed to the middleware. |
|||
|
|||
## Integrated middlewares { #integrated-middlewares } |
|||
|
|||
**FastAPI** includes several middlewares for common use cases, we'll see next how to use them. |
|||
|
|||
/// note | Technical Details |
|||
|
|||
For the next examples, you could also use `from starlette.middleware.something import SomethingMiddleware`. |
|||
|
|||
**FastAPI** provides several middlewares in `fastapi.middleware` just as a convenience for you, the developer. But most of the available middlewares come directly from Starlette. |
|||
|
|||
/// |
|||
|
|||
## `HTTPSRedirectMiddleware` { #httpsredirectmiddleware } |
|||
|
|||
Enforces that all incoming requests must either be `https` or `wss`. |
|||
|
|||
Any incoming request to `http` or `ws` will be redirected to the secure scheme instead. |
|||
|
|||
{* ../../docs_src/advanced_middleware/tutorial001.py hl[2,6] *} |
|||
|
|||
## `TrustedHostMiddleware` { #trustedhostmiddleware } |
|||
|
|||
Enforces that all incoming requests have a correctly set `Host` header, in order to guard against HTTP Host Header attacks. |
|||
|
|||
{* ../../docs_src/advanced_middleware/tutorial002.py hl[2,6:8] *} |
|||
|
|||
The following arguments are supported: |
|||
|
|||
* `allowed_hosts` - A list of domain names that should be allowed as hostnames. Wildcard domains such as `*.example.com` are supported for matching subdomains. To allow any hostname either use `allowed_hosts=["*"]` or omit the middleware. |
|||
* `www_redirect` - If set to True, requests to non-www versions of the allowed hosts will be redirected to their www counterparts. Defaults to `True`. |
|||
|
|||
If an incoming request does not validate correctly then a `400` response will be sent. |
|||
|
|||
## `GZipMiddleware` { #gzipmiddleware } |
|||
|
|||
Handles GZip responses for any request that includes `"gzip"` in the `Accept-Encoding` header. |
|||
|
|||
The middleware will handle both standard and streaming responses. |
|||
|
|||
{* ../../docs_src/advanced_middleware/tutorial003.py hl[2,6] *} |
|||
|
|||
The following arguments are supported: |
|||
|
|||
* `minimum_size` - Do not GZip responses that are smaller than this minimum size in bytes. Defaults to `500`. |
|||
* `compresslevel` - Used during GZip compression. It is an integer ranging from 1 to 9. Defaults to `9`. Lower value results in faster compression but larger file sizes, while higher value results in slower compression but smaller file sizes. |
|||
|
|||
## Other middlewares { #other-middlewares } |
|||
|
|||
There are many other ASGI middlewares. |
|||
|
|||
For example: |
|||
|
|||
* <a href="https://github.com/encode/uvicorn/blob/master/uvicorn/middleware/proxy_headers.py" class="external-link" target="_blank">Uvicorn's `ProxyHeadersMiddleware`</a> |
|||
* <a href="https://github.com/florimondmanca/msgpack-asgi" class="external-link" target="_blank">MessagePack</a> |
|||
|
|||
To see other available middlewares check <a href="https://www.starlette.io/middleware/" class="external-link" target="_blank">Starlette's Middleware docs</a> and the <a href="https://github.com/florimondmanca/awesome-asgi" class="external-link" target="_blank">ASGI Awesome List</a>. |
|||
@ -0,0 +1,186 @@ |
|||
# OpenAPI Callbacks { #openapi-callbacks } |
|||
|
|||
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 { #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 { #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: |
|||
|
|||
{* ../../docs_src/openapi_callbacks/tutorial001.py hl[9:13,36:53] *} |
|||
|
|||
/// tip |
|||
|
|||
The `callback_url` query parameter uses a Pydantic <a href="https://docs.pydantic.dev/latest/api/networks/" class="external-link" target="_blank">Url</a> type. |
|||
|
|||
/// |
|||
|
|||
The only new thing is the `callbacks=invoices_callback_router.routes` as an argument to the *path operation decorator*. We'll see what that is next. |
|||
|
|||
## Documenting the callback { #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/" |
|||
httpx.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.python-httpx.org" class="external-link" target="_blank">HTTPX</a> or <a href="https://requests.readthedocs.io/" class="external-link" target="_blank">Requests</a>. |
|||
|
|||
/// |
|||
|
|||
## Write the callback documentation code { #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` { #create-a-callback-apirouter } |
|||
|
|||
First create a new `APIRouter` that will contain one or more callbacks. |
|||
|
|||
{* ../../docs_src/openapi_callbacks/tutorial001.py hl[3,25] *} |
|||
|
|||
### Create the callback *path operation* { #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`. |
|||
|
|||
{* ../../docs_src/openapi_callbacks/tutorial001.py hl[16:18,21:22,28:32] *} |
|||
|
|||
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.1.0.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-expression } |
|||
|
|||
The callback *path* can have an <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.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 { #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: |
|||
|
|||
{* ../../docs_src/openapi_callbacks/tutorial001.py hl[35] *} |
|||
|
|||
/// 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 { #check-the-docs } |
|||
|
|||
Now you can start your app 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 "Callbacks" section for your *path operation* that shows how the *external API* should look like: |
|||
|
|||
<img src="/img/tutorial/openapi-callbacks/image01.png"> |
|||
@ -0,0 +1,55 @@ |
|||
# OpenAPI Webhooks { #openapi-webhooks } |
|||
|
|||
There are cases where you want to tell your API **users** that your app could call *their* app (sending a request) with some data, normally to **notify** of some type of **event**. |
|||
|
|||
This means that instead of the normal process of your users sending requests to your API, it's **your API** (or your app) that could **send requests to their system** (to their API, their app). |
|||
|
|||
This is normally called a **webhook**. |
|||
|
|||
## Webhooks steps { #webhooks-steps } |
|||
|
|||
The process normally is that **you define** in your code what is the message that you will send, the **body of the request**. |
|||
|
|||
You also define in some way at which **moments** your app will send those requests or events. |
|||
|
|||
And **your users** define in some way (for example in a web dashboard somewhere) the **URL** where your app should send those requests. |
|||
|
|||
All the **logic** about how to register the URLs for webhooks and the code to actually send those requests is up to you. You write it however you want to in **your own code**. |
|||
|
|||
## Documenting webhooks with **FastAPI** and OpenAPI { #documenting-webhooks-with-fastapi-and-openapi } |
|||
|
|||
With **FastAPI**, using OpenAPI, you can define the names of these webhooks, the types of HTTP operations that your app can send (e.g. `POST`, `PUT`, etc.) and the request **bodies** that your app would send. |
|||
|
|||
This can make it a lot easier for your users to **implement their APIs** to receive your **webhook** requests, they might even be able to autogenerate some of their own API code. |
|||
|
|||
/// info |
|||
|
|||
Webhooks are available in OpenAPI 3.1.0 and above, supported by FastAPI `0.99.0` and above. |
|||
|
|||
/// |
|||
|
|||
## An app with webhooks { #an-app-with-webhooks } |
|||
|
|||
When you create a **FastAPI** application, there is a `webhooks` attribute that you can use to define *webhooks*, the same way you would define *path operations*, for example with `@app.webhooks.post()`. |
|||
|
|||
{* ../../docs_src/openapi_webhooks/tutorial001.py hl[9:13,36:53] *} |
|||
|
|||
The webhooks that you define will end up in the **OpenAPI** schema and the automatic **docs UI**. |
|||
|
|||
/// info |
|||
|
|||
The `app.webhooks` object is actually just an `APIRouter`, the same type you would use when structuring your app with multiple files. |
|||
|
|||
/// |
|||
|
|||
Notice that with webhooks you are actually not declaring a *path* (like `/items/`), the text you pass there is just an **identifier** of the webhook (the name of the event), for example in `@app.webhooks.post("new-subscription")`, the webhook name is `new-subscription`. |
|||
|
|||
This is because it is expected that **your users** would define the actual **URL path** where they want to receive the webhook request in some other way (e.g. a web dashboard). |
|||
|
|||
### Check the docs { #check-the-docs } |
|||
|
|||
Now you can start your app 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 have the normal *path operations* and now also some **webhooks**: |
|||
|
|||
<img src="/img/tutorial/openapi-webhooks/image01.png"> |
|||
@ -0,0 +1,204 @@ |
|||
# Path Operation Advanced Configuration { #path-operation-advanced-configuration } |
|||
|
|||
## OpenAPI operationId { #openapi-operationid } |
|||
|
|||
/// warning |
|||
|
|||
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. |
|||
|
|||
{* ../../docs_src/path_operation_advanced_configuration/tutorial001.py hl[6] *} |
|||
|
|||
### Using the *path operation function* name as the operationId { #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*. |
|||
|
|||
{* ../../docs_src/path_operation_advanced_configuration/tutorial002.py hl[2, 12:21, 24] *} |
|||
|
|||
/// 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 { #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`: |
|||
|
|||
{* ../../docs_src/path_operation_advanced_configuration/tutorial003.py hl[6] *} |
|||
|
|||
## Advanced description from docstring { #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. |
|||
|
|||
{* ../../docs_src/path_operation_advanced_configuration/tutorial004.py hl[19:29] *} |
|||
|
|||
## Additional Responses { #additional-responses } |
|||
|
|||
You probably have seen how to declare the `response_model` and `status_code` for a *path operation*. |
|||
|
|||
That defines the metadata about the main response of a *path operation*. |
|||
|
|||
You can also declare additional responses with their models, status codes, etc. |
|||
|
|||
There's a whole chapter here in the documentation about it, you can read it at [Additional Responses in OpenAPI](additional-responses.md){.internal-link target=_blank}. |
|||
|
|||
## OpenAPI Extra { #openapi-extra } |
|||
|
|||
When you declare a *path operation* in your application, **FastAPI** automatically generates the relevant metadata about that *path operation* to be included in the OpenAPI schema. |
|||
|
|||
/// note | Technical details |
|||
|
|||
In the OpenAPI specification it is called the <a href="https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#operation-object" class="external-link" target="_blank">Operation Object</a>. |
|||
|
|||
/// |
|||
|
|||
It has all the information about the *path operation* and is used to generate the automatic documentation. |
|||
|
|||
It includes the `tags`, `parameters`, `requestBody`, `responses`, etc. |
|||
|
|||
This *path operation*-specific OpenAPI schema is normally generated automatically by **FastAPI**, but you can also extend it. |
|||
|
|||
/// tip |
|||
|
|||
This is a low level extension point. |
|||
|
|||
If you only need to declare additional responses, a more convenient way to do it is with [Additional Responses in OpenAPI](additional-responses.md){.internal-link target=_blank}. |
|||
|
|||
/// |
|||
|
|||
You can extend the OpenAPI schema for a *path operation* using the parameter `openapi_extra`. |
|||
|
|||
### OpenAPI Extensions { #openapi-extensions } |
|||
|
|||
This `openapi_extra` can be helpful, for example, to declare [OpenAPI Extensions](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#specificationExtensions): |
|||
|
|||
{* ../../docs_src/path_operation_advanced_configuration/tutorial005.py hl[6] *} |
|||
|
|||
If you open the automatic API docs, your extension will show up at the bottom of the specific *path operation*. |
|||
|
|||
<img src="/img/tutorial/path-operation-advanced-configuration/image01.png"> |
|||
|
|||
And if you see the resulting OpenAPI (at `/openapi.json` in your API), you will see your extension as part of the specific *path operation* too: |
|||
|
|||
```JSON hl_lines="22" |
|||
{ |
|||
"openapi": "3.1.0", |
|||
"info": { |
|||
"title": "FastAPI", |
|||
"version": "0.1.0" |
|||
}, |
|||
"paths": { |
|||
"/items/": { |
|||
"get": { |
|||
"summary": "Read Items", |
|||
"operationId": "read_items_items__get", |
|||
"responses": { |
|||
"200": { |
|||
"description": "Successful Response", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": {} |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
"x-aperture-labs-portal": "blue" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
|
|||
### Custom OpenAPI *path operation* schema { #custom-openapi-path-operation-schema } |
|||
|
|||
The dictionary in `openapi_extra` will be deeply merged with the automatically generated OpenAPI schema for the *path operation*. |
|||
|
|||
So, you could add additional data to the automatically generated schema. |
|||
|
|||
For example, you could decide to read and validate the request with your own code, without using the automatic features of FastAPI with Pydantic, but you could still want to define the request in the OpenAPI schema. |
|||
|
|||
You could do that with `openapi_extra`: |
|||
|
|||
{* ../../docs_src/path_operation_advanced_configuration/tutorial006.py hl[19:36, 39:40] *} |
|||
|
|||
In this example, we didn't declare any Pydantic model. In fact, the request body is not even <abbr title="converted from some plain format, like bytes, into Python objects">parsed</abbr> as JSON, it is read directly as `bytes`, and the function `magic_data_reader()` would be in charge of parsing it in some way. |
|||
|
|||
Nevertheless, we can declare the expected schema for the request body. |
|||
|
|||
### Custom OpenAPI content type { #custom-openapi-content-type } |
|||
|
|||
Using this same trick, you could use a Pydantic model to define the JSON Schema that is then included in the custom OpenAPI schema section for the *path operation*. |
|||
|
|||
And you could do this even if the data type in the request is not JSON. |
|||
|
|||
For example, in this application we don't use FastAPI's integrated functionality to extract the JSON Schema from Pydantic models nor the automatic validation for JSON. In fact, we are declaring the request content type as YAML, not JSON: |
|||
|
|||
//// tab | Pydantic v2 |
|||
|
|||
{* ../../docs_src/path_operation_advanced_configuration/tutorial007.py hl[17:22, 24] *} |
|||
|
|||
//// |
|||
|
|||
//// tab | Pydantic v1 |
|||
|
|||
{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1.py hl[17:22, 24] *} |
|||
|
|||
//// |
|||
|
|||
/// info |
|||
|
|||
In Pydantic version 1 the method to get the JSON Schema for a model was called `Item.schema()`, in Pydantic version 2, the method is called `Item.model_json_schema()`. |
|||
|
|||
/// |
|||
|
|||
Nevertheless, although we are not using the default integrated functionality, we are still using a Pydantic model to manually generate the JSON Schema for the data that we want to receive in YAML. |
|||
|
|||
Then we use the request directly, and extract the body as `bytes`. This means that FastAPI won't even try to parse the request payload as JSON. |
|||
|
|||
And then in our code, we parse that YAML content directly, and then we are again using the same Pydantic model to validate the YAML content: |
|||
|
|||
//// tab | Pydantic v2 |
|||
|
|||
{* ../../docs_src/path_operation_advanced_configuration/tutorial007.py hl[26:33] *} |
|||
|
|||
//// |
|||
|
|||
//// tab | Pydantic v1 |
|||
|
|||
{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1.py hl[26:33] *} |
|||
|
|||
//// |
|||
|
|||
/// info |
|||
|
|||
In Pydantic version 1 the method to parse and validate an object was `Item.parse_obj()`, in Pydantic version 2, the method is called `Item.model_validate()`. |
|||
|
|||
/// |
|||
|
|||
/// tip |
|||
|
|||
Here we reuse the same Pydantic model. |
|||
|
|||
But the same way, we could have validated it in some other way. |
|||
|
|||
/// |
|||
@ -0,0 +1,41 @@ |
|||
# Response Headers { #response-headers } |
|||
|
|||
## 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 then you can set headers in that *temporal* response object. |
|||
|
|||
{* ../../docs_src/response_headers/tutorial002.py hl[1, 7:8] *} |
|||
|
|||
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 { #return-a-response-directly } |
|||
|
|||
You can also add headers when you return a `Response` directly. |
|||
|
|||
Create a response as described in [Return a Response Directly](response-directly.md){.internal-link target=_blank} and pass the headers as an additional parameter: |
|||
|
|||
{* ../../docs_src/response_headers/tutorial001.py hl[10:12] *} |
|||
|
|||
/// note | Technical Details |
|||
|
|||
You could also use `from starlette.responses import Response` or `from starlette.responses import JSONResponse`. |
|||
|
|||
**FastAPI** provides the same `starlette.responses` as `fastapi.responses` just as a convenience for you, the developer. But most of the available responses come directly from Starlette. |
|||
|
|||
And as the `Response` can be used frequently to set headers and cookies, **FastAPI** also provides it at `fastapi.Response`. |
|||
|
|||
/// |
|||
|
|||
## Custom Headers { #custom-headers } |
|||
|
|||
Keep 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 CORS configurations (read more in [CORS (Cross-Origin Resource Sharing)](../tutorial/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>. |
|||
@ -0,0 +1,107 @@ |
|||
# HTTP Basic Auth { #http-basic-auth } |
|||
|
|||
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 { #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. |
|||
|
|||
{* ../../docs_src/security/tutorial006_an_py39.py hl[4,8,12] *} |
|||
|
|||
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 { #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. |
|||
|
|||
`secrets.compare_digest()` needs to take `bytes` or a `str` that only contains ASCII characters (the ones in English), this means it wouldn't work with characters like `á`, as in `Sebastián`. |
|||
|
|||
To handle that, we first convert the `username` and `password` to `bytes` encoding them with UTF-8. |
|||
|
|||
Then we can use `secrets.compare_digest()` to ensure that `credentials.username` is `"stanleyjobson"`, and that `credentials.password` is `"swordfish"`. |
|||
|
|||
{* ../../docs_src/security/tutorial007_an_py39.py hl[1,12:24] *} |
|||
|
|||
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 { #timing-attacks } |
|||
|
|||
But what's a "timing attack"? |
|||
|
|||
Let's imagine some attackers are trying to guess the username and password. |
|||
|
|||
And they send 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 username or password". |
|||
|
|||
But then the attackers try 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 username or password". |
|||
|
|||
#### The time to answer helps the attackers { #the-time-to-answer-helps-the-attackers } |
|||
|
|||
At that point, by noticing that the server took some microseconds longer to send the "Incorrect username or password" response, the attackers will know that they got _something_ right, some of the initial letters were right. |
|||
|
|||
And then they can try again knowing that it's probably something more similar to `stanleyjobsox` than to `johndoe`. |
|||
|
|||
#### A "professional" attack { #a-professional-attack } |
|||
|
|||
Of course, the attackers would not try all this by hand, they would write a program to do it, possibly with thousands or millions of tests per second. And they would get just one extra correct letter at a time. |
|||
|
|||
But doing that, in some minutes or hours the attackers 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()` { #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 { #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: |
|||
|
|||
{* ../../docs_src/security/tutorial007_an_py39.py hl[26:30] *} |
|||
@ -0,0 +1,19 @@ |
|||
# Advanced Security { #advanced-security } |
|||
|
|||
## Additional Features { #additional-features } |
|||
|
|||
There are some extra features to handle security apart from the ones covered in the [Tutorial - User Guide: Security](../../tutorial/security/index.md){.internal-link target=_blank}. |
|||
|
|||
/// tip |
|||
|
|||
The next sections are **not necessarily "advanced"**. |
|||
|
|||
And it's possible that for your use case, the solution is in one of them. |
|||
|
|||
/// |
|||
|
|||
## Read the Tutorial first { #read-the-tutorial-first } |
|||
|
|||
The next sections assume you already read the main [Tutorial - User Guide: Security](../../tutorial/security/index.md){.internal-link target=_blank}. |
|||
|
|||
They are all based on the same concepts, but allow some extra functionalities. |
|||
@ -0,0 +1,274 @@ |
|||
# OAuth2 scopes { #oauth2-scopes } |
|||
|
|||
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, X (Twitter), etc. They use it to provide specific permissions to users and applications. |
|||
|
|||
Every time you "log in with" Facebook, Google, GitHub, Microsoft, X (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 { #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". |
|||
|
|||
When one of these security schemes uses OAuth2, you can also declare and use scopes. |
|||
|
|||
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. |
|||
* `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. |
|||
|
|||
Those details are implementation specific. |
|||
|
|||
For OAuth2 they are just strings. |
|||
|
|||
/// |
|||
|
|||
## Global view { #global-view } |
|||
|
|||
First, let's quickly see the parts that change from the examples in the main **Tutorial - User Guide** for [OAuth2 with Password (and hashing), Bearer with JWT tokens](../../tutorial/security/oauth2-jwt.md){.internal-link target=_blank}. Now using OAuth2 scopes: |
|||
|
|||
{* ../../docs_src/security/tutorial005_an_py310.py hl[5,9,13,47,65,106,108:116,122:126,130:136,141,157] *} |
|||
|
|||
Now let's review those changes step by step. |
|||
|
|||
## OAuth2 Security scheme { #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: |
|||
|
|||
{* ../../docs_src/security/tutorial005_an_py310.py hl[63:66] *} |
|||
|
|||
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 { #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. |
|||
|
|||
/// |
|||
|
|||
{* ../../docs_src/security/tutorial005_an_py310.py hl[157] *} |
|||
|
|||
## Declare scopes in *path operations* and dependencies { #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. |
|||
|
|||
/// |
|||
|
|||
{* ../../docs_src/security/tutorial005_an_py310.py hl[5,141,172] *} |
|||
|
|||
/// 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`, those are actually functions that return special classes. |
|||
|
|||
/// |
|||
|
|||
## Use `SecurityScopes` { #use-securityscopes } |
|||
|
|||
Now update the dependency `get_current_user`. |
|||
|
|||
This is the one used by the dependencies above. |
|||
|
|||
Here's where 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). |
|||
|
|||
{* ../../docs_src/security/tutorial005_an_py310.py hl[9,106] *} |
|||
|
|||
## Use the `scopes` { #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 reuse (`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 the `WWW-Authenticate` header (this is part of the spec). |
|||
|
|||
{* ../../docs_src/security/tutorial005_an_py310.py hl[106,108:116] *} |
|||
|
|||
## Verify the `username` and data shape { #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. |
|||
|
|||
{* ../../docs_src/security/tutorial005_an_py310.py hl[47,117:129] *} |
|||
|
|||
## Verify the `scopes` { #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`. |
|||
|
|||
{* ../../docs_src/security/tutorial005_an_py310.py hl[130:136] *} |
|||
|
|||
## Dependency tree and scopes { #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 `scopes` 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` { #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 { #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 { #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 it's 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` { #security-in-decorator-dependencies } |
|||
|
|||
The same way you can define a `list` of `Depends` in the decorator's `dependencies` parameter (as explained in [Dependencies in path operation decorators](../../tutorial/dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}), you could also use `Security` with `scopes` there. |
|||
@ -0,0 +1,346 @@ |
|||
# Settings and Environment Variables { #settings-and-environment-variables } |
|||
|
|||
In many cases your application could need some external settings or configurations, for example secret keys, database credentials, credentials for email services, etc. |
|||
|
|||
Most of these settings are variable (can change), like database URLs. And many could be sensitive, like secrets. |
|||
|
|||
For this reason it's common to provide them in environment variables that are read by the application. |
|||
|
|||
/// tip |
|||
|
|||
To understand environment variables you can read [Environment Variables](../environment-variables.md){.internal-link target=_blank}. |
|||
|
|||
/// |
|||
|
|||
## Types and validation { #types-and-validation } |
|||
|
|||
These environment variables can only handle text strings, as they are external to Python and have to be compatible with other programs and the rest of the system (and even with different operating systems, as Linux, Windows, macOS). |
|||
|
|||
That means that any value read in Python from an environment variable will be a `str`, and any conversion to a different type or any validation has to be done in code. |
|||
|
|||
## Pydantic `Settings` { #pydantic-settings } |
|||
|
|||
Fortunately, Pydantic provides a great utility to handle these settings coming from environment variables with <a href="https://docs.pydantic.dev/latest/concepts/pydantic_settings/" class="external-link" target="_blank">Pydantic: Settings management</a>. |
|||
|
|||
### Install `pydantic-settings` { #install-pydantic-settings } |
|||
|
|||
First, make sure you create your [virtual environment](../virtual-environments.md){.internal-link target=_blank}, activate it, and then install the `pydantic-settings` package: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ pip install pydantic-settings |
|||
---> 100% |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
It also comes included when you install the `all` extras with: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ pip install "fastapi[all]" |
|||
---> 100% |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
/// info |
|||
|
|||
In Pydantic v1 it came included with the main package. Now it is distributed as this independent package so that you can choose to install it or not if you don't need that functionality. |
|||
|
|||
/// |
|||
|
|||
### Create the `Settings` object { #create-the-settings-object } |
|||
|
|||
Import `BaseSettings` from Pydantic and create a sub-class, very much like with a Pydantic model. |
|||
|
|||
The same way as with Pydantic models, you declare class attributes with type annotations, and possibly default values. |
|||
|
|||
You can use all the same validation features and tools you use for Pydantic models, like different data types and additional validations with `Field()`. |
|||
|
|||
//// tab | Pydantic v2 |
|||
|
|||
{* ../../docs_src/settings/tutorial001.py hl[2,5:8,11] *} |
|||
|
|||
//// |
|||
|
|||
//// tab | Pydantic v1 |
|||
|
|||
/// info |
|||
|
|||
In Pydantic v1 you would import `BaseSettings` directly from `pydantic` instead of from `pydantic_settings`. |
|||
|
|||
/// |
|||
|
|||
{* ../../docs_src/settings/tutorial001_pv1.py hl[2,5:8,11] *} |
|||
|
|||
//// |
|||
|
|||
/// tip |
|||
|
|||
If you want something quick to copy and paste, don't use this example, use the last one below. |
|||
|
|||
/// |
|||
|
|||
Then, when you create an instance of that `Settings` class (in this case, in the `settings` object), Pydantic will read the environment variables in a case-insensitive way, so, an upper-case variable `APP_NAME` will still be read for the attribute `app_name`. |
|||
|
|||
Next it will convert and validate the data. So, when you use that `settings` object, you will have data of the types you declared (e.g. `items_per_user` will be an `int`). |
|||
|
|||
### Use the `settings` { #use-the-settings } |
|||
|
|||
Then you can use the new `settings` object in your application: |
|||
|
|||
{* ../../docs_src/settings/tutorial001.py hl[18:20] *} |
|||
|
|||
### Run the server { #run-the-server } |
|||
|
|||
Next, you would run the server passing the configurations as environment variables, for example you could set an `ADMIN_EMAIL` and `APP_NAME` with: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ ADMIN_EMAIL="[email protected]" APP_NAME="ChimichangApp" fastapi run main.py |
|||
|
|||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
/// tip |
|||
|
|||
To set multiple env vars for a single command just separate them with a space, and put them all before the command. |
|||
|
|||
/// |
|||
|
|||
And then the `admin_email` setting would be set to `"[email protected]"`. |
|||
|
|||
The `app_name` would be `"ChimichangApp"`. |
|||
|
|||
And the `items_per_user` would keep its default value of `50`. |
|||
|
|||
## Settings in another module { #settings-in-another-module } |
|||
|
|||
You could put those settings in another module file as you saw in [Bigger Applications - Multiple Files](../tutorial/bigger-applications.md){.internal-link target=_blank}. |
|||
|
|||
For example, you could have a file `config.py` with: |
|||
|
|||
{* ../../docs_src/settings/app01/config.py *} |
|||
|
|||
And then use it in a file `main.py`: |
|||
|
|||
{* ../../docs_src/settings/app01/main.py hl[3,11:13] *} |
|||
|
|||
/// tip |
|||
|
|||
You would also need a file `__init__.py` as you saw in [Bigger Applications - Multiple Files](../tutorial/bigger-applications.md){.internal-link target=_blank}. |
|||
|
|||
/// |
|||
|
|||
## Settings in a dependency { #settings-in-a-dependency } |
|||
|
|||
In some occasions it might be useful to provide the settings from a dependency, instead of having a global object with `settings` that is used everywhere. |
|||
|
|||
This could be especially useful during testing, as it's very easy to override a dependency with your own custom settings. |
|||
|
|||
### The config file { #the-config-file } |
|||
|
|||
Coming from the previous example, your `config.py` file could look like: |
|||
|
|||
{* ../../docs_src/settings/app02/config.py hl[10] *} |
|||
|
|||
Notice that now we don't create a default instance `settings = Settings()`. |
|||
|
|||
### The main app file { #the-main-app-file } |
|||
|
|||
Now we create a dependency that returns a new `config.Settings()`. |
|||
|
|||
{* ../../docs_src/settings/app02_an_py39/main.py hl[6,12:13] *} |
|||
|
|||
/// tip |
|||
|
|||
We'll discuss the `@lru_cache` in a bit. |
|||
|
|||
For now you can assume `get_settings()` is a normal function. |
|||
|
|||
/// |
|||
|
|||
And then we can require it from the *path operation function* as a dependency and use it anywhere we need it. |
|||
|
|||
{* ../../docs_src/settings/app02_an_py39/main.py hl[17,19:21] *} |
|||
|
|||
### Settings and testing { #settings-and-testing } |
|||
|
|||
Then it would be very easy to provide a different settings object during testing by creating a dependency override for `get_settings`: |
|||
|
|||
{* ../../docs_src/settings/app02/test_main.py hl[9:10,13,21] *} |
|||
|
|||
In the dependency override we set a new value for the `admin_email` when creating the new `Settings` object, and then we return that new object. |
|||
|
|||
Then we can test that it is used. |
|||
|
|||
## Reading a `.env` file { #reading-a-env-file } |
|||
|
|||
If you have many settings that possibly change a lot, maybe in different environments, it might be useful to put them on a file and then read them from it as if they were environment variables. |
|||
|
|||
This practice is common enough that it has a name, these environment variables are commonly placed in a file `.env`, and the file is called a "dotenv". |
|||
|
|||
/// tip |
|||
|
|||
A file starting with a dot (`.`) is a hidden file in Unix-like systems, like Linux and macOS. |
|||
|
|||
But a dotenv file doesn't really have to have that exact filename. |
|||
|
|||
/// |
|||
|
|||
Pydantic has support for reading from these types of files using an external library. You can read more at <a href="https://docs.pydantic.dev/latest/concepts/pydantic_settings/#dotenv-env-support" class="external-link" target="_blank">Pydantic Settings: Dotenv (.env) support</a>. |
|||
|
|||
/// tip |
|||
|
|||
For this to work, you need to `pip install python-dotenv`. |
|||
|
|||
/// |
|||
|
|||
### The `.env` file { #the-env-file } |
|||
|
|||
You could have a `.env` file with: |
|||
|
|||
```bash |
|||
ADMIN_EMAIL="[email protected]" |
|||
APP_NAME="ChimichangApp" |
|||
``` |
|||
|
|||
### Read settings from `.env` { #read-settings-from-env } |
|||
|
|||
And then update your `config.py` with: |
|||
|
|||
//// tab | Pydantic v2 |
|||
|
|||
{* ../../docs_src/settings/app03_an/config.py hl[9] *} |
|||
|
|||
/// tip |
|||
|
|||
The `model_config` attribute is used just for Pydantic configuration. You can read more at <a href="https://docs.pydantic.dev/latest/concepts/config/" class="external-link" target="_blank">Pydantic: Concepts: Configuration</a>. |
|||
|
|||
/// |
|||
|
|||
//// |
|||
|
|||
//// tab | Pydantic v1 |
|||
|
|||
{* ../../docs_src/settings/app03_an/config_pv1.py hl[9:10] *} |
|||
|
|||
/// tip |
|||
|
|||
The `Config` class is used just for Pydantic configuration. You can read more at <a href="https://docs.pydantic.dev/1.10/usage/model_config/" class="external-link" target="_blank">Pydantic Model Config</a>. |
|||
|
|||
/// |
|||
|
|||
//// |
|||
|
|||
/// info |
|||
|
|||
In Pydantic version 1 the configuration was done in an internal class `Config`, in Pydantic version 2 it's done in an attribute `model_config`. This attribute takes a `dict`, and to get autocompletion and inline errors you can import and use `SettingsConfigDict` to define that `dict`. |
|||
|
|||
/// |
|||
|
|||
Here we define the config `env_file` inside of your Pydantic `Settings` class, and set the value to the filename with the dotenv file we want to use. |
|||
|
|||
### Creating the `Settings` only once with `lru_cache` { #creating-the-settings-only-once-with-lru-cache } |
|||
|
|||
Reading a file from disk is normally a costly (slow) operation, so you probably want to do it only once and then reuse the same settings object, instead of reading it for each request. |
|||
|
|||
But every time we do: |
|||
|
|||
```Python |
|||
Settings() |
|||
``` |
|||
|
|||
a new `Settings` object would be created, and at creation it would read the `.env` file again. |
|||
|
|||
If the dependency function was just like: |
|||
|
|||
```Python |
|||
def get_settings(): |
|||
return Settings() |
|||
``` |
|||
|
|||
we would create that object for each request, and we would be reading the `.env` file for each request. ⚠️ |
|||
|
|||
But as we are using the `@lru_cache` decorator on top, the `Settings` object will be created only once, the first time it's called. ✔️ |
|||
|
|||
{* ../../docs_src/settings/app03_an_py39/main.py hl[1,11] *} |
|||
|
|||
Then for any subsequent call of `get_settings()` in the dependencies for the next requests, instead of executing the internal code of `get_settings()` and creating a new `Settings` object, it will return the same object that was returned on the first call, again and again. |
|||
|
|||
#### `lru_cache` Technical Details { #lru-cache-technical-details } |
|||
|
|||
`@lru_cache` modifies the function it decorates to return the same value that was returned the first time, instead of computing it again, executing the code of the function every time. |
|||
|
|||
So, the function below it will be executed once for each combination of arguments. And then the values returned by each of those combinations of arguments will be used again and again whenever the function is called with exactly the same combination of arguments. |
|||
|
|||
For example, if you have a function: |
|||
|
|||
```Python |
|||
@lru_cache |
|||
def say_hi(name: str, salutation: str = "Ms."): |
|||
return f"Hello {salutation} {name}" |
|||
``` |
|||
|
|||
your program could execute like this: |
|||
|
|||
```mermaid |
|||
sequenceDiagram |
|||
|
|||
participant code as Code |
|||
participant function as say_hi() |
|||
participant execute as Execute function |
|||
|
|||
rect rgba(0, 255, 0, .1) |
|||
code ->> function: say_hi(name="Camila") |
|||
function ->> execute: execute function code |
|||
execute ->> code: return the result |
|||
end |
|||
|
|||
rect rgba(0, 255, 255, .1) |
|||
code ->> function: say_hi(name="Camila") |
|||
function ->> code: return stored result |
|||
end |
|||
|
|||
rect rgba(0, 255, 0, .1) |
|||
code ->> function: say_hi(name="Rick") |
|||
function ->> execute: execute function code |
|||
execute ->> code: return the result |
|||
end |
|||
|
|||
rect rgba(0, 255, 0, .1) |
|||
code ->> function: say_hi(name="Rick", salutation="Mr.") |
|||
function ->> execute: execute function code |
|||
execute ->> code: return the result |
|||
end |
|||
|
|||
rect rgba(0, 255, 255, .1) |
|||
code ->> function: say_hi(name="Rick") |
|||
function ->> code: return stored result |
|||
end |
|||
|
|||
rect rgba(0, 255, 255, .1) |
|||
code ->> function: say_hi(name="Camila") |
|||
function ->> code: return stored result |
|||
end |
|||
``` |
|||
|
|||
In the case of our dependency `get_settings()`, the function doesn't even take any arguments, so it always returns the same value. |
|||
|
|||
That way, it behaves almost as if it was just a global variable. But as it uses a dependency function, then we can override it easily for testing. |
|||
|
|||
`@lru_cache` is part of `functools` which is part of Python's standard library, you can read more about it in the <a href="https://docs.python.org/3/library/functools.html#functools.lru_cache" class="external-link" target="_blank">Python docs for `@lru_cache`</a>. |
|||
|
|||
## Recap { #recap } |
|||
|
|||
You can use Pydantic Settings to handle the settings or configurations for your application, with all the power of Pydantic models. |
|||
|
|||
* By using a dependency you can simplify testing. |
|||
* You can use `.env` files with it. |
|||
* Using `@lru_cache` lets you avoid reading the dotenv file again and again for each request, while allowing you to override it during testing. |
|||
@ -0,0 +1,67 @@ |
|||
# Sub Applications - Mounts { #sub-applications-mounts } |
|||
|
|||
If you need to have two independent FastAPI applications, with their own independent OpenAPI and their own docs UIs, you can have a main app and "mount" one (or more) sub-application(s). |
|||
|
|||
## Mounting a **FastAPI** application { #mounting-a-fastapi-application } |
|||
|
|||
"Mounting" means adding a completely "independent" application in a specific path, that then takes care of handling everything under that path, with the _path operations_ declared in that sub-application. |
|||
|
|||
### Top-level application { #top-level-application } |
|||
|
|||
First, create the main, top-level, **FastAPI** application, and its *path operations*: |
|||
|
|||
{* ../../docs_src/sub_applications/tutorial001.py hl[3, 6:8] *} |
|||
|
|||
### Sub-application { #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": |
|||
|
|||
{* ../../docs_src/sub_applications/tutorial001.py hl[11, 14:16] *} |
|||
|
|||
### Mount the sub-application { #mount-the-sub-application } |
|||
|
|||
In your top-level application, `app`, mount the sub-application, `subapi`. |
|||
|
|||
In this case, it will be mounted at the path `/subapi`: |
|||
|
|||
{* ../../docs_src/sub_applications/tutorial001.py hl[11, 19] *} |
|||
|
|||
### Check the automatic API docs { #check-the-automatic-api-docs } |
|||
|
|||
Now, run the `fastapi` command with your file: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ fastapi dev main.py |
|||
|
|||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
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 _path operations_: |
|||
|
|||
<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 _path operations_, all under the correct sub-path prefix `/subapi`: |
|||
|
|||
<img src="/img/tutorial/sub-applications/image02.png"> |
|||
|
|||
If you try interacting with any of the two user interfaces, they will work correctly, because the browser will be able to talk to each specific app or sub-app. |
|||
|
|||
### Technical Details: `root_path` { #technical-details-root-path } |
|||
|
|||
When you mount a sub-application as described above, FastAPI will take care of communicating the mount path for the sub-application using a mechanism from the ASGI specification called a `root_path`. |
|||
|
|||
That way, the sub-application will know to use that path prefix for the docs UI. |
|||
|
|||
And the sub-application could also have its own mounted sub-applications and everything would work correctly, because FastAPI handles all these `root_path`s automatically. |
|||
|
|||
You will learn more about the `root_path` and how to use it explicitly in the section about [Behind a Proxy](behind-a-proxy.md){.internal-link target=_blank}. |
|||
@ -0,0 +1,126 @@ |
|||
# Templates { #templates } |
|||
|
|||
You can use any template engine you want with **FastAPI**. |
|||
|
|||
A common choice is Jinja2, the same one used by Flask and other tools. |
|||
|
|||
There are utilities to configure it easily that you can use directly in your **FastAPI** application (provided by Starlette). |
|||
|
|||
## Install dependencies { #install-dependencies } |
|||
|
|||
Make sure you create a [virtual environment](../virtual-environments.md){.internal-link target=_blank}, activate it, and install `jinja2`: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ pip install jinja2 |
|||
|
|||
---> 100% |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
## Using `Jinja2Templates` { #using-jinja2templates } |
|||
|
|||
* Import `Jinja2Templates`. |
|||
* Create a `templates` object that you can reuse 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`, pass the name of the template, the request object, and a "context" dictionary with key-value pairs to be used inside of the Jinja2 template. |
|||
|
|||
{* ../../docs_src/templates/tutorial001.py hl[4,11,15:18] *} |
|||
|
|||
/// note |
|||
|
|||
Before FastAPI 0.108.0, Starlette 0.29.0, the `name` was the first parameter. |
|||
|
|||
Also, before that, in previous versions, the `request` object was passed as part of the key-value pairs in the context for Jinja2. |
|||
|
|||
/// |
|||
|
|||
/// tip |
|||
|
|||
By declaring `response_class=HTMLResponse` the docs UI will be able to know that the response will be HTML. |
|||
|
|||
/// |
|||
|
|||
/// note | Technical Details |
|||
|
|||
You could also use `from starlette.templating import Jinja2Templates`. |
|||
|
|||
**FastAPI** provides the same `starlette.templating` as `fastapi.templating` just as a convenience for you, the developer. But most of the available responses come directly from Starlette. The same with `Request` and `StaticFiles`. |
|||
|
|||
/// |
|||
|
|||
## Writing templates { #writing-templates } |
|||
|
|||
Then you can write a template at `templates/item.html` with, for example: |
|||
|
|||
```jinja hl_lines="7" |
|||
{!../../docs_src/templates/templates/item.html!} |
|||
``` |
|||
|
|||
### Template Context Values { #template-context-values } |
|||
|
|||
In the HTML that contains: |
|||
|
|||
{% raw %} |
|||
|
|||
```jinja |
|||
Item ID: {{ id }} |
|||
``` |
|||
|
|||
{% endraw %} |
|||
|
|||
...it will show the `id` taken from the "context" `dict` you passed: |
|||
|
|||
```Python |
|||
{"id": id} |
|||
``` |
|||
|
|||
For example, with an ID of `42`, this would render: |
|||
|
|||
```html |
|||
Item ID: 42 |
|||
``` |
|||
|
|||
### Template `url_for` Arguments { #template-url-for-arguments } |
|||
|
|||
You can also use `url_for()` inside of the template, it takes as arguments the same arguments that would be used by your *path operation function*. |
|||
|
|||
So, the section with: |
|||
|
|||
{% raw %} |
|||
|
|||
```jinja |
|||
<a href="{{ url_for('read_item', id=id) }}"> |
|||
``` |
|||
|
|||
{% endraw %} |
|||
|
|||
...will generate a link to the same URL that would be handled by the *path operation function* `read_item(id=id)`. |
|||
|
|||
For example, with an ID of `42`, this would render: |
|||
|
|||
```html |
|||
<a href="/items/42"> |
|||
``` |
|||
|
|||
## Templates and static files { #templates-and-static-files } |
|||
|
|||
You can also use `url_for()` inside of the template, and use it, for example, with the `StaticFiles` you mounted with the `name="static"`. |
|||
|
|||
```jinja hl_lines="4" |
|||
{!../../docs_src/templates/templates/item.html!} |
|||
``` |
|||
|
|||
In this example, it would link to a CSS file at `static/styles.css` with: |
|||
|
|||
```CSS hl_lines="4" |
|||
{!../../docs_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 { #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>. |
|||
@ -0,0 +1,53 @@ |
|||
# Testing Dependencies with Overrides { #testing-dependencies-with-overrides } |
|||
|
|||
## Overriding dependencies during testing { #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 { #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 the `app.dependency_overrides` attribute { #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. |
|||
|
|||
{* ../../docs_src/dependency_testing/tutorial001_an_py310.py hl[26:27,30] *} |
|||
|
|||
/// 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). |
|||
|
|||
/// |
|||
@ -0,0 +1,12 @@ |
|||
# Testing Events: lifespan and startup - shutdown { #testing-events-lifespan-and-startup-shutdown } |
|||
|
|||
When you need `lifespan` to run in your tests, you can use the `TestClient` with a `with` statement: |
|||
|
|||
{* ../../docs_src/app_testing/tutorial004.py hl[9:15,18,27:28,30:32,41:43] *} |
|||
|
|||
|
|||
You can read more details about the ["Running lifespan in tests in the official Starlette documentation site."](https://www.starlette.io/lifespan/#running-lifespan-in-tests) |
|||
|
|||
For the deprecated `startup` and `shutdown` events, you can use the `TestClient` as follows: |
|||
|
|||
{* ../../docs_src/app_testing/tutorial003.py hl[9:12,20:24] *} |
|||
@ -0,0 +1,13 @@ |
|||
# Testing WebSockets { #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: |
|||
|
|||
{* ../../docs_src/app_testing/tutorial002.py hl[27:31] *} |
|||
|
|||
/// note |
|||
|
|||
For more details, check Starlette's documentation for <a href="https://www.starlette.io/testclient/#testing-websocket-sessions" class="external-link" target="_blank">testing WebSockets</a>. |
|||
|
|||
/// |
|||
@ -0,0 +1,56 @@ |
|||
# Using the Request Directly { #using-the-request-directly } |
|||
|
|||
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 { #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 documented (with OpenAPI, for the automatic API user interface) 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 { #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. |
|||
|
|||
{* ../../docs_src/using_request_directly/tutorial001.py hl[1,7:8] *} |
|||
|
|||
By declaring a *path operation function* parameter with the type being the `Request` **FastAPI** will know to pass the `Request` in that parameter. |
|||
|
|||
/// tip |
|||
|
|||
Note that in this case, we are declaring a path parameter beside 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 { #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>. |
|||
|
|||
/// note | Technical Details |
|||
|
|||
You could also use `from starlette.requests import Request`. |
|||
|
|||
**FastAPI** provides it directly just as a convenience for you, the developer. But it comes directly from Starlette. |
|||
|
|||
/// |
|||
@ -0,0 +1,35 @@ |
|||
# Including WSGI - Flask, Django, others { #including-wsgi-flask-django-others } |
|||
|
|||
You can mount WSGI applications as you saw with [Sub Applications - Mounts](sub-applications.md){.internal-link target=_blank}, [Behind a Proxy](behind-a-proxy.md){.internal-link target=_blank}. |
|||
|
|||
For that, you can use the `WSGIMiddleware` and use it to wrap your WSGI application, for example, Flask, Django, etc. |
|||
|
|||
## Using `WSGIMiddleware` { #using-wsgimiddleware } |
|||
|
|||
You need to import `WSGIMiddleware`. |
|||
|
|||
Then wrap the WSGI (e.g. Flask) app with the middleware. |
|||
|
|||
And then mount that under a path. |
|||
|
|||
{* ../../docs_src/wsgi/tutorial001.py hl[2:3,3] *} |
|||
|
|||
## Check it { #check-it } |
|||
|
|||
Now, every request under the path `/v1/` will be handled by the Flask application. |
|||
|
|||
And the rest will be handled by **FastAPI**. |
|||
|
|||
If you run it and go to <a href="http://localhost:8000/v1/" class="external-link" target="_blank">http://localhost:8000/v1/</a> you will see the response from Flask: |
|||
|
|||
```txt |
|||
Hello, World from Flask! |
|||
``` |
|||
|
|||
And if you go to <a href="http://localhost:8000/v2" class="external-link" target="_blank">http://localhost:8000/v2</a> you will see the response from FastAPI: |
|||
|
|||
```JSON |
|||
{ |
|||
"message": "Hello World" |
|||
} |
|||
``` |
|||
@ -0,0 +1,16 @@ |
|||
# Deploy FastAPI on Cloud Providers { #deploy-fastapi-on-cloud-providers } |
|||
|
|||
You can use virtually **any cloud provider** to deploy your FastAPI application. |
|||
|
|||
In most of the cases, the main cloud providers have guides to deploy FastAPI with them. |
|||
|
|||
## Cloud Providers - Sponsors { #cloud-providers-sponsors } |
|||
|
|||
Some cloud providers ✨ [**sponsor FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨, this ensures the continued and healthy **development** of FastAPI and its **ecosystem**. |
|||
|
|||
And it shows their true commitment to FastAPI and its **community** (you), as they not only want to provide you a **good service** but also want to make sure you have a **good and healthy framework**, FastAPI. 🙇 |
|||
|
|||
You might want to try their services and follow their guides: |
|||
|
|||
* <a href="https://docs.render.com/deploy-fastapi?utm_source=deploydoc&utm_medium=referral&utm_campaign=fastapi" class="external-link" target="_blank">Render</a> |
|||
* <a href="https://docs.railway.com/guides/fastapi?utm_medium=integration&utm_source=docs&utm_campaign=fastapi" class="external-link" target="_blank">Railway</a> |
|||
@ -0,0 +1,139 @@ |
|||
# Server Workers - Uvicorn with Workers { #server-workers-uvicorn-with-workers } |
|||
|
|||
Let's check back those deployment concepts from before: |
|||
|
|||
* Security - HTTPS |
|||
* Running on startup |
|||
* Restarts |
|||
* **Replication (the number of processes running)** |
|||
* Memory |
|||
* Previous steps before starting |
|||
|
|||
Up to this point, with all the tutorials in the docs, you have probably been running a **server program**, for example, using the `fastapi` command, that runs Uvicorn, running a **single process**. |
|||
|
|||
When deploying applications you will probably want to have some **replication of processes** to take advantage of **multiple cores** and to be able to handle more requests. |
|||
|
|||
As you saw in the previous chapter about [Deployment Concepts](concepts.md){.internal-link target=_blank}, there are multiple strategies you can use. |
|||
|
|||
Here I'll show you how to use **Uvicorn** with **worker processes** using the `fastapi` command or the `uvicorn` command directly. |
|||
|
|||
/// info |
|||
|
|||
If you are using containers, for example with Docker or Kubernetes, I'll tell you more about that in the next chapter: [FastAPI in Containers - Docker](docker.md){.internal-link target=_blank}. |
|||
|
|||
In particular, when running on **Kubernetes** you will probably **not** want to use workers and instead run **a single Uvicorn process per container**, but I'll tell you about it later in that chapter. |
|||
|
|||
/// |
|||
|
|||
## Multiple Workers { #multiple-workers } |
|||
|
|||
You can start multiple workers with the `--workers` command line option: |
|||
|
|||
//// tab | `fastapi` |
|||
|
|||
If you use the `fastapi` command: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ <font color="#4E9A06">fastapi</font> run --workers 4 <u style="text-decoration-style:solid">main.py</u> |
|||
|
|||
<span style="background-color:#009485"><font color="#D3D7CF"> FastAPI </font></span> Starting production server 🚀 |
|||
|
|||
Searching for package file structure from directories with |
|||
<font color="#3465A4">__init__.py</font> files |
|||
Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font> |
|||
|
|||
<span style="background-color:#007166"><font color="#D3D7CF"> module </font></span> 🐍 main.py |
|||
|
|||
<span style="background-color:#007166"><font color="#D3D7CF"> code </font></span> Importing the FastAPI app object from the module with the |
|||
following code: |
|||
|
|||
<u style="text-decoration-style:solid">from </u><u style="text-decoration-style:solid"><b>main</b></u><u style="text-decoration-style:solid"> import </u><u style="text-decoration-style:solid"><b>app</b></u> |
|||
|
|||
<span style="background-color:#007166"><font color="#D3D7CF"> app </font></span> Using import string: <font color="#3465A4">main:app</font> |
|||
|
|||
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Server started at <font color="#729FCF"><u style="text-decoration-style:solid">http://0.0.0.0:8000</u></font> |
|||
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Documentation at <font color="#729FCF"><u style="text-decoration-style:solid">http://0.0.0.0:8000/docs</u></font> |
|||
|
|||
Logs: |
|||
|
|||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Uvicorn running on <font color="#729FCF"><u style="text-decoration-style:solid">http://0.0.0.0:8000</u></font> <b>(</b>Press CTRL+C to |
|||
quit<b>)</b> |
|||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started parent process <b>[</b><font color="#34E2E2"><b>27365</b></font><b>]</b> |
|||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>27368</b></font><b>]</b> |
|||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>27369</b></font><b>]</b> |
|||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>27370</b></font><b>]</b> |
|||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>27367</b></font><b>]</b> |
|||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup. |
|||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup. |
|||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup. |
|||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup. |
|||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete. |
|||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete. |
|||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete. |
|||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete. |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
//// |
|||
|
|||
//// tab | `uvicorn` |
|||
|
|||
If you prefer to use the `uvicorn` command directly: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ uvicorn main:app --host 0.0.0.0 --port 8080 --workers 4 |
|||
<font color="#A6E22E">INFO</font>: Uvicorn running on <b>http://0.0.0.0:8080</b> (Press CTRL+C to quit) |
|||
<font color="#A6E22E">INFO</font>: Started parent process [<font color="#A1EFE4"><b>27365</b></font>] |
|||
<font color="#A6E22E">INFO</font>: Started server process [<font color="#A1EFE4">27368</font>] |
|||
<font color="#A6E22E">INFO</font>: Waiting for application startup. |
|||
<font color="#A6E22E">INFO</font>: Application startup complete. |
|||
<font color="#A6E22E">INFO</font>: Started server process [<font color="#A1EFE4">27369</font>] |
|||
<font color="#A6E22E">INFO</font>: Waiting for application startup. |
|||
<font color="#A6E22E">INFO</font>: Application startup complete. |
|||
<font color="#A6E22E">INFO</font>: Started server process [<font color="#A1EFE4">27370</font>] |
|||
<font color="#A6E22E">INFO</font>: Waiting for application startup. |
|||
<font color="#A6E22E">INFO</font>: Application startup complete. |
|||
<font color="#A6E22E">INFO</font>: Started server process [<font color="#A1EFE4">27367</font>] |
|||
<font color="#A6E22E">INFO</font>: Waiting for application startup. |
|||
<font color="#A6E22E">INFO</font>: Application startup complete. |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
//// |
|||
|
|||
The only new option here is `--workers` telling Uvicorn to start 4 worker processes. |
|||
|
|||
You can also see that it shows the **PID** of each process, `27365` for the parent process (this is the **process manager**) and one for each worker process: `27368`, `27369`, `27370`, and `27367`. |
|||
|
|||
## Deployment Concepts { #deployment-concepts } |
|||
|
|||
Here you saw how to use multiple **workers** to **parallelize** the execution of the application, take advantage of **multiple cores** in the CPU, and be able to serve **more requests**. |
|||
|
|||
From the list of deployment concepts from above, using workers would mainly help with the **replication** part, and a little bit with the **restarts**, but you still need to take care of the others: |
|||
|
|||
* **Security - HTTPS** |
|||
* **Running on startup** |
|||
* ***Restarts*** |
|||
* Replication (the number of processes running) |
|||
* **Memory** |
|||
* **Previous steps before starting** |
|||
|
|||
## Containers and Docker { #containers-and-docker } |
|||
|
|||
In the next chapter about [FastAPI in Containers - Docker](docker.md){.internal-link target=_blank} I'll explain some strategies you could use to handle the other **deployment concepts**. |
|||
|
|||
I'll show you how to **build your own image from scratch** to run a single Uvicorn process. It is a simple process and is probably what you would want to do when using a distributed container management system like **Kubernetes**. |
|||
|
|||
## Recap { #recap } |
|||
|
|||
You can use multiple worker processes with the `--workers` CLI option with the `fastapi` or `uvicorn` commands to take advantage of **multi-core CPUs**, to run **multiple processes in parallel**. |
|||
|
|||
You could use these tools and ideas if you are setting up **your own deployment system** while taking care of the other deployment concepts yourself. |
|||
|
|||
Check out the next chapter to learn about **FastAPI** with containers (e.g. Docker and Kubernetes). You will see that those tools have simple ways to solve the other **deployment concepts** as well. ✨ |
|||
@ -0,0 +1,56 @@ |
|||
# Conditional OpenAPI { #conditional-openapi } |
|||
|
|||
If you needed to, you could use settings and environment variables to configure OpenAPI conditionally depending on the environment, and even disable it entirely. |
|||
|
|||
## About security, APIs, and docs { #about-security-apis-and-docs } |
|||
|
|||
Hiding your documentation user interfaces in production *shouldn't* be the way to protect your API. |
|||
|
|||
That doesn't add any extra security to your API, the *path operations* will still be available where they are. |
|||
|
|||
If there's a security flaw in your code, it will still exist. |
|||
|
|||
Hiding the documentation just makes it more difficult to understand how to interact with your API, and could make it more difficult for you to debug it in production. It could be considered simply a form of <a href="https://en.wikipedia.org/wiki/Security_through_obscurity" class="external-link" target="_blank">Security through obscurity</a>. |
|||
|
|||
If you want to secure your API, there are several better things you can do, for example: |
|||
|
|||
* Make sure you have well defined Pydantic models for your request bodies and responses. |
|||
* Configure any required permissions and roles using dependencies. |
|||
* Never store plaintext passwords, only password hashes. |
|||
* Implement and use well-known cryptographic tools, like Passlib and JWT tokens, etc. |
|||
* Add more granular permission controls with OAuth2 scopes where needed. |
|||
* ...etc. |
|||
|
|||
Nevertheless, you might have a very specific use case where you really need to disable the API docs for some environment (e.g. for production) or depending on configurations from environment variables. |
|||
|
|||
## Conditional OpenAPI from settings and env vars { #conditional-openapi-from-settings-and-env-vars } |
|||
|
|||
You can easily use the same Pydantic settings to configure your generated OpenAPI and the docs UIs. |
|||
|
|||
For example: |
|||
|
|||
{* ../../docs_src/conditional_openapi/tutorial001.py hl[6,11] *} |
|||
|
|||
Here we declare the setting `openapi_url` with the same default of `"/openapi.json"`. |
|||
|
|||
And then we use it when creating the `FastAPI` app. |
|||
|
|||
Then you could disable OpenAPI (including the UI docs) by setting the environment variable `OPENAPI_URL` to the empty string, like: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ OPENAPI_URL= uvicorn main:app |
|||
|
|||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
Then if you go to the URLs at `/openapi.json`, `/docs`, or `/redoc` you will just get a `404 Not Found` error like: |
|||
|
|||
```JSON |
|||
{ |
|||
"detail": "Not Found" |
|||
} |
|||
``` |
|||
@ -0,0 +1,70 @@ |
|||
# Configure Swagger UI { #configure-swagger-ui } |
|||
|
|||
You can configure some extra <a href="https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/" class="external-link" target="_blank">Swagger UI parameters</a>. |
|||
|
|||
To configure them, pass the `swagger_ui_parameters` argument when creating the `FastAPI()` app object or to the `get_swagger_ui_html()` function. |
|||
|
|||
`swagger_ui_parameters` receives a dictionary with the configurations passed to Swagger UI directly. |
|||
|
|||
FastAPI converts the configurations to **JSON** to make them compatible with JavaScript, as that's what Swagger UI needs. |
|||
|
|||
## Disable Syntax Highlighting { #disable-syntax-highlighting } |
|||
|
|||
For example, you could disable syntax highlighting in Swagger UI. |
|||
|
|||
Without changing the settings, syntax highlighting is enabled by default: |
|||
|
|||
<img src="/img/tutorial/extending-openapi/image02.png"> |
|||
|
|||
But you can disable it by setting `syntaxHighlight` to `False`: |
|||
|
|||
{* ../../docs_src/configure_swagger_ui/tutorial001.py hl[3] *} |
|||
|
|||
...and then Swagger UI won't show the syntax highlighting anymore: |
|||
|
|||
<img src="/img/tutorial/extending-openapi/image03.png"> |
|||
|
|||
## Change the Theme { #change-the-theme } |
|||
|
|||
The same way you could set the syntax highlighting theme with the key `"syntaxHighlight.theme"` (notice that it has a dot in the middle): |
|||
|
|||
{* ../../docs_src/configure_swagger_ui/tutorial002.py hl[3] *} |
|||
|
|||
That configuration would change the syntax highlighting color theme: |
|||
|
|||
<img src="/img/tutorial/extending-openapi/image04.png"> |
|||
|
|||
## Change Default Swagger UI Parameters { #change-default-swagger-ui-parameters } |
|||
|
|||
FastAPI includes some default configuration parameters appropriate for most of the use cases. |
|||
|
|||
It includes these default configurations: |
|||
|
|||
{* ../../fastapi/openapi/docs.py ln[8:23] hl[17:23] *} |
|||
|
|||
You can override any of them by setting a different value in the argument `swagger_ui_parameters`. |
|||
|
|||
For example, to disable `deepLinking` you could pass these settings to `swagger_ui_parameters`: |
|||
|
|||
{* ../../docs_src/configure_swagger_ui/tutorial003.py hl[3] *} |
|||
|
|||
## Other Swagger UI Parameters { #other-swagger-ui-parameters } |
|||
|
|||
To see all the other possible configurations you can use, read the official <a href="https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/" class="external-link" target="_blank">docs for Swagger UI parameters</a>. |
|||
|
|||
## JavaScript-only settings { #javascript-only-settings } |
|||
|
|||
Swagger UI also allows other configurations to be **JavaScript-only** objects (for example, JavaScript functions). |
|||
|
|||
FastAPI also includes these JavaScript-only `presets` settings: |
|||
|
|||
```JavaScript |
|||
presets: [ |
|||
SwaggerUIBundle.presets.apis, |
|||
SwaggerUIBundle.SwaggerUIStandalonePreset |
|||
] |
|||
``` |
|||
|
|||
These are **JavaScript** objects, not strings, so you can't pass them from Python code directly. |
|||
|
|||
If you need to use JavaScript-only configurations like those, you can use one of the methods above. Override all the Swagger UI *path operation* and manually write any JavaScript you need. |
|||
@ -0,0 +1,185 @@ |
|||
# Custom Docs UI Static Assets (Self-Hosting) { #custom-docs-ui-static-assets-self-hosting } |
|||
|
|||
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. |
|||
|
|||
## Custom CDN for JavaScript and CSS { #custom-cdn-for-javascript-and-css } |
|||
|
|||
Let's say that you want to use a different <abbr title="Content Delivery Network">CDN</abbr>, for example you want to use `https://unpkg.com/`. |
|||
|
|||
This could be useful if for example you live in a country that restricts some URLs. |
|||
|
|||
### Disable the automatic docs { #disable-the-automatic-docs } |
|||
|
|||
The first step is to disable the automatic docs, as by default, those use the default CDN. |
|||
|
|||
To disable them, set their URLs to `None` when creating your `FastAPI` app: |
|||
|
|||
{* ../../docs_src/custom_docs_ui/tutorial001.py hl[8] *} |
|||
|
|||
### Include the custom docs { #include-the-custom-docs } |
|||
|
|||
Now you can create the *path operations* for the custom docs. |
|||
|
|||
You can reuse 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 custom CDN URL. |
|||
* `swagger_css_url`: the URL where the HTML for your Swagger UI docs can get the **CSS** file. This is the custom CDN URL. |
|||
|
|||
And similarly for ReDoc... |
|||
|
|||
{* ../../docs_src/custom_docs_ui/tutorial001.py hl[2:6,11:19,22:24,27:33] *} |
|||
|
|||
/// 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 { #create-a-path-operation-to-test-it } |
|||
|
|||
Now, to be able to test that everything works, create a *path operation*: |
|||
|
|||
{* ../../docs_src/custom_docs_ui/tutorial001.py hl[36:38] *} |
|||
|
|||
### Test it { #test-it } |
|||
|
|||
Now, you should be able to 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, it will load those assets from the new CDN. |
|||
|
|||
## Self-hosting JavaScript and CSS for docs { #self-hosting-javascript-and-css-for-docs } |
|||
|
|||
Self-hosting the JavaScript and CSS could be useful if, for example, 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 { #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-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@5/swagger-ui-bundle.js" class="external-link" target="_blank">`swagger-ui-bundle.js`</a> |
|||
* <a href="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/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@2/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 |
|||
``` |
|||
|
|||
### Serve the static files { #serve-the-static-files } |
|||
|
|||
* Import `StaticFiles`. |
|||
* "Mount" a `StaticFiles()` instance in a specific path. |
|||
|
|||
{* ../../docs_src/custom_docs_ui/tutorial002.py hl[7,11] *} |
|||
|
|||
### Test the static files { #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 |
|||
/*! For license information please see redoc.standalone.js.LICENSE.txt */ |
|||
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("null")): |
|||
... |
|||
``` |
|||
|
|||
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 for static files { #disable-the-automatic-docs-for-static-files } |
|||
|
|||
The same as when using a custom CDN, 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: |
|||
|
|||
{* ../../docs_src/custom_docs_ui/tutorial002.py hl[9] *} |
|||
|
|||
### Include the custom docs for static files { #include-the-custom-docs-for-static-files } |
|||
|
|||
And the same way as with a custom CDN, now you can create the *path operations* for the custom docs. |
|||
|
|||
Again, you can reuse 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... |
|||
|
|||
{* ../../docs_src/custom_docs_ui/tutorial002.py hl[2:6,14:22,25:27,30:36] *} |
|||
|
|||
/// 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 static files { #create-a-path-operation-to-test-static-files } |
|||
|
|||
Now, to be able to test that everything works, create a *path operation*: |
|||
|
|||
{* ../../docs_src/custom_docs_ui/tutorial002.py hl[39:41] *} |
|||
|
|||
### Test Static Files UI { #test-static-files-ui } |
|||
|
|||
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. |
|||
@ -0,0 +1,109 @@ |
|||
# Custom Request and APIRoute class { #custom-request-and-apiroute-class } |
|||
|
|||
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 { #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 { #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 { #create-a-custom-gziprequest-class } |
|||
|
|||
/// tip |
|||
|
|||
This is a toy example to demonstrate how it works, if you need Gzip support, you can use the provided [`GzipMiddleware`](../advanced/middleware.md#gzipmiddleware){.internal-link target=_blank}. |
|||
|
|||
/// |
|||
|
|||
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. |
|||
|
|||
{* ../../docs_src/custom_request_and_route/tutorial001.py hl[8:15] *} |
|||
|
|||
### Create a custom `GzipRoute` class { #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. |
|||
|
|||
{* ../../docs_src/custom_request_and_route/tutorial001.py hl[18:26] *} |
|||
|
|||
/// 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 { #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` ([Handling Errors](../tutorial/handling-errors.md#use-the-requestvalidationerror-body){.internal-link target=_blank}). |
|||
|
|||
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: |
|||
|
|||
{* ../../docs_src/custom_request_and_route/tutorial002.py hl[13,15] *} |
|||
|
|||
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: |
|||
|
|||
{* ../../docs_src/custom_request_and_route/tutorial002.py hl[16:18] *} |
|||
|
|||
## Custom `APIRoute` class in a router { #custom-apiroute-class-in-a-router } |
|||
|
|||
You can also set the `route_class` parameter of an `APIRouter`: |
|||
|
|||
{* ../../docs_src/custom_request_and_route/tutorial003.py hl[26] *} |
|||
|
|||
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: |
|||
|
|||
{* ../../docs_src/custom_request_and_route/tutorial003.py hl[13:20] *} |
|||
@ -0,0 +1,80 @@ |
|||
# Extending OpenAPI { #extending-openapi } |
|||
|
|||
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-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.1.0`. |
|||
* `summary`: A short summary of the API. |
|||
* `description`: The description of your API, this can include markdown and will be shown in the docs. |
|||
* `routes`: A list of routes, these are each of the registered *path operations*. They are taken from `app.routes`. |
|||
|
|||
/// info |
|||
|
|||
The parameter `summary` is available in OpenAPI 3.1.0 and above, supported by FastAPI 0.99.0 and above. |
|||
|
|||
/// |
|||
|
|||
## Overriding the defaults { #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** { #normal-fastapi } |
|||
|
|||
First, write all your **FastAPI** application as normally: |
|||
|
|||
{* ../../docs_src/extending_openapi/tutorial001.py hl[1,4,7:9] *} |
|||
|
|||
### Generate the OpenAPI schema { #generate-the-openapi-schema } |
|||
|
|||
Then, use the same utility function to generate the OpenAPI schema, inside a `custom_openapi()` function: |
|||
|
|||
{* ../../docs_src/extending_openapi/tutorial001.py hl[2,15:21] *} |
|||
|
|||
### Modify the OpenAPI schema { #modify-the-openapi-schema } |
|||
|
|||
Now you can add the ReDoc extension, adding a custom `x-logo` to the `info` "object" in the OpenAPI schema: |
|||
|
|||
{* ../../docs_src/extending_openapi/tutorial001.py hl[22:24] *} |
|||
|
|||
### Cache the OpenAPI schema { #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. |
|||
|
|||
{* ../../docs_src/extending_openapi/tutorial001.py hl[13:14,25:26] *} |
|||
|
|||
### Override the method { #override-the-method } |
|||
|
|||
Now you can replace the `.openapi()` method with your new function. |
|||
|
|||
{* ../../docs_src/extending_openapi/tutorial001.py hl[29] *} |
|||
|
|||
### Check it { #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"> |
|||
@ -0,0 +1,39 @@ |
|||
# General - How To - Recipes { #general-how-to-recipes } |
|||
|
|||
Here are several pointers to other places in the docs, for general or frequent questions. |
|||
|
|||
## Filter Data - Security { #filter-data-security } |
|||
|
|||
To ensure that you don't return more data than you should, read the docs for [Tutorial - Response Model - Return Type](../tutorial/response-model.md){.internal-link target=_blank}. |
|||
|
|||
## Documentation Tags - OpenAPI { #documentation-tags-openapi } |
|||
|
|||
To add tags to your *path operations*, and group them in the docs UI, read the docs for [Tutorial - Path Operation Configurations - Tags](../tutorial/path-operation-configuration.md#tags){.internal-link target=_blank}. |
|||
|
|||
## Documentation Summary and Description - OpenAPI { #documentation-summary-and-description-openapi } |
|||
|
|||
To add a summary and description to your *path operations*, and show them in the docs UI, read the docs for [Tutorial - Path Operation Configurations - Summary and Description](../tutorial/path-operation-configuration.md#summary-and-description){.internal-link target=_blank}. |
|||
|
|||
## Documentation Response description - OpenAPI { #documentation-response-description-openapi } |
|||
|
|||
To define the description of the response, shown in the docs UI, read the docs for [Tutorial - Path Operation Configurations - Response description](../tutorial/path-operation-configuration.md#response-description){.internal-link target=_blank}. |
|||
|
|||
## Documentation Deprecate a *Path Operation* - OpenAPI { #documentation-deprecate-a-path-operation-openapi } |
|||
|
|||
To deprecate a *path operation*, and show it in the docs UI, read the docs for [Tutorial - Path Operation Configurations - Deprecation](../tutorial/path-operation-configuration.md#deprecate-a-path-operation){.internal-link target=_blank}. |
|||
|
|||
## Convert any Data to JSON-compatible { #convert-any-data-to-json-compatible } |
|||
|
|||
To convert any data to JSON-compatible, read the docs for [Tutorial - JSON Compatible Encoder](../tutorial/encoder.md){.internal-link target=_blank}. |
|||
|
|||
## OpenAPI Metadata - Docs { #openapi-metadata-docs } |
|||
|
|||
To add metadata to your OpenAPI schema, including a license, version, contact, etc, read the docs for [Tutorial - Metadata and Docs URLs](../tutorial/metadata.md){.internal-link target=_blank}. |
|||
|
|||
## OpenAPI Custom URL { #openapi-custom-url } |
|||
|
|||
To customize the OpenAPI URL (or remove it), read the docs for [Tutorial - Metadata and Docs URLs](../tutorial/metadata.md#openapi-url){.internal-link target=_blank}. |
|||
|
|||
## OpenAPI Docs URLs { #openapi-docs-urls } |
|||
|
|||
To update the URLs used for the automatically generated docs user interfaces, read the docs for [Tutorial - Metadata and Docs URLs](../tutorial/metadata.md#docs-urls){.internal-link target=_blank}. |
|||
@ -0,0 +1,60 @@ |
|||
# GraphQL { #graphql } |
|||
|
|||
As **FastAPI** is based on the **ASGI** standard, it's very easy to integrate any **GraphQL** library also compatible with ASGI. |
|||
|
|||
You can combine normal FastAPI *path operations* with GraphQL on the same application. |
|||
|
|||
/// tip |
|||
|
|||
**GraphQL** solves some very specific use cases. |
|||
|
|||
It has **advantages** and **disadvantages** when compared to common **web APIs**. |
|||
|
|||
Make sure you evaluate if the **benefits** for your use case compensate the **drawbacks**. 🤓 |
|||
|
|||
/// |
|||
|
|||
## GraphQL Libraries { #graphql-libraries } |
|||
|
|||
Here are some of the **GraphQL** libraries that have **ASGI** support. You could use them with **FastAPI**: |
|||
|
|||
* <a href="https://strawberry.rocks/" class="external-link" target="_blank">Strawberry</a> 🍓 |
|||
* With <a href="https://strawberry.rocks/docs/integrations/fastapi" class="external-link" target="_blank">docs for FastAPI</a> |
|||
* <a href="https://ariadnegraphql.org/" class="external-link" target="_blank">Ariadne</a> |
|||
* With <a href="https://ariadnegraphql.org/docs/fastapi-integration" class="external-link" target="_blank">docs for FastAPI</a> |
|||
* <a href="https://tartiflette.io/" class="external-link" target="_blank">Tartiflette</a> |
|||
* With <a href="https://tartiflette.github.io/tartiflette-asgi/" class="external-link" target="_blank">Tartiflette ASGI</a> to provide ASGI integration |
|||
* <a href="https://graphene-python.org/" class="external-link" target="_blank">Graphene</a> |
|||
* With <a href="https://github.com/ciscorn/starlette-graphene3" class="external-link" target="_blank">starlette-graphene3</a> |
|||
|
|||
## GraphQL with Strawberry { #graphql-with-strawberry } |
|||
|
|||
If you need or want to work with **GraphQL**, <a href="https://strawberry.rocks/" class="external-link" target="_blank">**Strawberry**</a> is the **recommended** library as it has the design closest to **FastAPI's** design, it's all based on **type annotations**. |
|||
|
|||
Depending on your use case, you might prefer to use a different library, but if you asked me, I would probably suggest you try **Strawberry**. |
|||
|
|||
Here's a small preview of how you could integrate Strawberry with FastAPI: |
|||
|
|||
{* ../../docs_src/graphql/tutorial001.py hl[3,22,25] *} |
|||
|
|||
You can learn more about Strawberry in the <a href="https://strawberry.rocks/" class="external-link" target="_blank">Strawberry documentation</a>. |
|||
|
|||
And also the docs about <a href="https://strawberry.rocks/docs/integrations/fastapi" class="external-link" target="_blank">Strawberry with FastAPI</a>. |
|||
|
|||
## Older `GraphQLApp` from Starlette { #older-graphqlapp-from-starlette } |
|||
|
|||
Previous versions of Starlette included a `GraphQLApp` class to integrate with <a href="https://graphene-python.org/" class="external-link" target="_blank">Graphene</a>. |
|||
|
|||
It was deprecated from Starlette, but if you have code that used it, you can easily **migrate** to <a href="https://github.com/ciscorn/starlette-graphene3" class="external-link" target="_blank">starlette-graphene3</a>, that covers the same use case and has an **almost identical interface**. |
|||
|
|||
/// tip |
|||
|
|||
If you need GraphQL, I still would recommend you check out <a href="https://strawberry.rocks/" class="external-link" target="_blank">Strawberry</a>, as it's based on type annotations instead of custom classes and types. |
|||
|
|||
/// |
|||
|
|||
## Learn More { #learn-more } |
|||
|
|||
You can learn more about **GraphQL** in the <a href="https://graphql.org/" class="external-link" target="_blank">official GraphQL documentation</a>. |
|||
|
|||
You can also read more about each those libraries described above in their links. |
|||
@ -0,0 +1,13 @@ |
|||
# How To - Recipes { #how-to-recipes } |
|||
|
|||
Here you will see different recipes or "how to" guides for **several topics**. |
|||
|
|||
Most of these ideas would be more or less **independent**, and in most cases you should only need to study them if they apply directly to **your project**. |
|||
|
|||
If something seems interesting and useful to your project, go ahead and check it, but otherwise, you might probably just skip them. |
|||
|
|||
/// tip |
|||
|
|||
If you want to **learn FastAPI** in a structured way (recommended), go and read the [Tutorial - User Guide](../tutorial/index.md){.internal-link target=_blank} chapter by chapter instead. |
|||
|
|||
/// |
|||
@ -0,0 +1,104 @@ |
|||
# Separate OpenAPI Schemas for Input and Output or Not { #separate-openapi-schemas-for-input-and-output-or-not } |
|||
|
|||
When using **Pydantic v2**, the generated OpenAPI is a bit more exact and **correct** than before. 😎 |
|||
|
|||
In fact, in some cases, it will even have **two JSON Schemas** in OpenAPI for the same Pydantic model, for input and output, depending on if they have **default values**. |
|||
|
|||
Let's see how that works and how to change it if you need to do that. |
|||
|
|||
## Pydantic Models for Input and Output { #pydantic-models-for-input-and-output } |
|||
|
|||
Let's say you have a Pydantic model with default values, like this one: |
|||
|
|||
{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py ln[1:7] hl[7] *} |
|||
|
|||
### Model for Input { #model-for-input } |
|||
|
|||
If you use this model as an input like here: |
|||
|
|||
{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py ln[1:15] hl[14] *} |
|||
|
|||
...then the `description` field will **not be required**. Because it has a default value of `None`. |
|||
|
|||
### Input Model in Docs { #input-model-in-docs } |
|||
|
|||
You can confirm that in the docs, the `description` field doesn't have a **red asterisk**, it's not marked as required: |
|||
|
|||
<div class="screenshot"> |
|||
<img src="/img/tutorial/separate-openapi-schemas/image01.png"> |
|||
</div> |
|||
|
|||
### Model for Output { #model-for-output } |
|||
|
|||
But if you use the same model as an output, like here: |
|||
|
|||
{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py hl[19] *} |
|||
|
|||
...then because `description` has a default value, if you **don't return anything** for that field, it will still have that **default value**. |
|||
|
|||
### Model for Output Response Data { #model-for-output-response-data } |
|||
|
|||
If you interact with the docs and check the response, even though the code didn't add anything in one of the `description` fields, the JSON response contains the default value (`null`): |
|||
|
|||
<div class="screenshot"> |
|||
<img src="/img/tutorial/separate-openapi-schemas/image02.png"> |
|||
</div> |
|||
|
|||
This means that it will **always have a value**, it's just that sometimes the value could be `None` (or `null` in JSON). |
|||
|
|||
That means that, clients using your API don't have to check if the value exists or not, they can **assume the field will always be there**, but just that in some cases it will have the default value of `None`. |
|||
|
|||
The way to describe this in OpenAPI, is to mark that field as **required**, because it will always be there. |
|||
|
|||
Because of that, the JSON Schema for a model can be different depending on if it's used for **input or output**: |
|||
|
|||
* for **input** the `description` will **not be required** |
|||
* for **output** it will be **required** (and possibly `None`, or in JSON terms, `null`) |
|||
|
|||
### Model for Output in Docs { #model-for-output-in-docs } |
|||
|
|||
You can check the output model in the docs too, **both** `name` and `description` are marked as **required** with a **red asterisk**: |
|||
|
|||
<div class="screenshot"> |
|||
<img src="/img/tutorial/separate-openapi-schemas/image03.png"> |
|||
</div> |
|||
|
|||
### Model for Input and Output in Docs { #model-for-input-and-output-in-docs } |
|||
|
|||
And if you check all the available Schemas (JSON Schemas) in OpenAPI, you will see that there are two, one `Item-Input` and one `Item-Output`. |
|||
|
|||
For `Item-Input`, `description` is **not required**, it doesn't have a red asterisk. |
|||
|
|||
But for `Item-Output`, `description` is **required**, it has a red asterisk. |
|||
|
|||
<div class="screenshot"> |
|||
<img src="/img/tutorial/separate-openapi-schemas/image04.png"> |
|||
</div> |
|||
|
|||
With this feature from **Pydantic v2**, your API documentation is more **precise**, and if you have autogenerated clients and SDKs, they will be more precise too, with a better **developer experience** and consistency. 🎉 |
|||
|
|||
## Do not Separate Schemas { #do-not-separate-schemas } |
|||
|
|||
Now, there are some cases where you might want to have the **same schema for input and output**. |
|||
|
|||
Probably the main use case for this is if you already have some autogenerated client code/SDKs and you don't want to update all the autogenerated client code/SDKs yet, you probably will want to do it at some point, but maybe not right now. |
|||
|
|||
In that case, you can disable this feature in **FastAPI**, with the parameter `separate_input_output_schemas=False`. |
|||
|
|||
/// info |
|||
|
|||
Support for `separate_input_output_schemas` was added in FastAPI `0.102.0`. 🤓 |
|||
|
|||
/// |
|||
|
|||
{* ../../docs_src/separate_openapi_schemas/tutorial002_py310.py hl[10] *} |
|||
|
|||
### Same Schema for Input and Output Models in Docs { #same-schema-for-input-and-output-models-in-docs } |
|||
|
|||
And now there will be one single schema for input and output for the model, only `Item`, and it will have `description` as **not required**: |
|||
|
|||
<div class="screenshot"> |
|||
<img src="/img/tutorial/separate-openapi-schemas/image05.png"> |
|||
</div> |
|||
|
|||
This is the same behavior as in Pydantic v1. 🤓 |
|||
@ -0,0 +1,7 @@ |
|||
# Testing a Database { #testing-a-database } |
|||
|
|||
You can study about databases, SQL, and SQLModel in the <a href="https://sqlmodel.tiangolo.com/" class="external-link" target="_blank">SQLModel docs</a>. 🤓 |
|||
|
|||
There's a mini <a href="https://sqlmodel.tiangolo.com/tutorial/fastapi/" class="external-link" target="_blank">tutorial on using SQLModel with FastAPI</a>. ✨ |
|||
|
|||
That tutorial includes a section about <a href="https://sqlmodel.tiangolo.com/tutorial/fastapi/tests/" class="external-link" target="_blank">testing SQL databases</a>. 😎 |
|||
@ -0,0 +1,3 @@ |
|||
# Resources { #resources } |
|||
|
|||
Additional resources, external links, articles and more. ✈️ |
|||
Loading…
Reference in new issue