mirror of https://github.com/tiangolo/fastapi.git
Merge 45f3af87cb into 4f3e749d02
This commit is contained in:
commit
c408fb1ecf
|
|
@ -26,7 +26,7 @@ Each of those response `dict`s can have a key `model`, containing a Pydantic mod
|
|||
|
||||
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] *}
|
||||
{* ../../docs_src/additional_responses/tutorial001_py39.py hl[18,22] *}
|
||||
|
||||
/// note
|
||||
|
||||
|
|
@ -203,7 +203,7 @@ For example, you can declare a response with a status code `404` that uses a Pyd
|
|||
|
||||
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] *}
|
||||
{* ../../docs_src/additional_responses/tutorial003_py39.py hl[20:31] *}
|
||||
|
||||
It will all be combined and included in your OpenAPI, and shown in the API docs:
|
||||
|
||||
|
|
|
|||
|
|
@ -32,11 +32,11 @@ For a simple example, let's consider a file structure similar to the one describ
|
|||
|
||||
The file `main.py` would have:
|
||||
|
||||
{* ../../docs_src/async_tests/main.py *}
|
||||
{* ../../docs_src/async_tests/app_a_py39/main.py *}
|
||||
|
||||
The file `test_main.py` would have the tests for `main.py`, it could look like this now:
|
||||
|
||||
{* ../../docs_src/async_tests/test_main.py *}
|
||||
{* ../../docs_src/async_tests/app_a_py39/test_main.py *}
|
||||
|
||||
## Run it { #run-it }
|
||||
|
||||
|
|
@ -56,7 +56,7 @@ $ pytest
|
|||
|
||||
The marker `@pytest.mark.anyio` tells pytest that this test function should be called asynchronously:
|
||||
|
||||
{* ../../docs_src/async_tests/test_main.py hl[7] *}
|
||||
{* ../../docs_src/async_tests/app_a_py39/test_main.py hl[7] *}
|
||||
|
||||
/// tip
|
||||
|
||||
|
|
@ -66,7 +66,7 @@ Note that the test function is now `async def` instead of just `def` as before w
|
|||
|
||||
Then we can create an `AsyncClient` with the app, and send async requests to it, using `await`.
|
||||
|
||||
{* ../../docs_src/async_tests/test_main.py hl[9:12] *}
|
||||
{* ../../docs_src/async_tests/app_a_py39/test_main.py hl[9:12] *}
|
||||
|
||||
This is the equivalent to:
|
||||
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ $ fastapi run --forwarded-allow-ips="*"
|
|||
|
||||
For example, let's say you define a *path operation* `/items/`:
|
||||
|
||||
{* ../../docs_src/behind_a_proxy/tutorial001_01.py hl[6] *}
|
||||
{* ../../docs_src/behind_a_proxy/tutorial001_01_py39.py hl[6] *}
|
||||
|
||||
If the client tries to go to `/items`, by default, it would be redirected to `/items/`.
|
||||
|
||||
|
|
@ -115,7 +115,7 @@ 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] *}
|
||||
{* ../../docs_src/behind_a_proxy/tutorial001_py39.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`.
|
||||
|
||||
|
|
@ -193,7 +193,7 @@ You can get the current `root_path` used by your application for each request, i
|
|||
|
||||
Here we are including it in the message just for demonstration purposes.
|
||||
|
||||
{* ../../docs_src/behind_a_proxy/tutorial001.py hl[8] *}
|
||||
{* ../../docs_src/behind_a_proxy/tutorial001_py39.py hl[8] *}
|
||||
|
||||
Then, if you start Uvicorn with:
|
||||
|
||||
|
|
@ -220,7 +220,7 @@ The response would be something like:
|
|||
|
||||
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] *}
|
||||
{* ../../docs_src/behind_a_proxy/tutorial002_py39.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.
|
||||
|
||||
|
|
@ -400,7 +400,7 @@ If you pass a custom list of `servers` and there's a `root_path` (because your A
|
|||
|
||||
For example:
|
||||
|
||||
{* ../../docs_src/behind_a_proxy/tutorial003.py hl[4:7] *}
|
||||
{* ../../docs_src/behind_a_proxy/tutorial003_py39.py hl[4:7] *}
|
||||
|
||||
Will generate an OpenAPI schema like:
|
||||
|
||||
|
|
@ -455,7 +455,7 @@ If you don't specify the `servers` parameter and `root_path` is equal to `/`, th
|
|||
|
||||
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] *}
|
||||
{* ../../docs_src/behind_a_proxy/tutorial004_py39.py hl[9] *}
|
||||
|
||||
and then it won't include it in the OpenAPI schema.
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ This is because by default, FastAPI will inspect every item inside and make sure
|
|||
|
||||
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] *}
|
||||
{* ../../docs_src/custom_response/tutorial001b_py39.py hl[2,7] *}
|
||||
|
||||
/// info
|
||||
|
||||
|
|
@ -55,7 +55,7 @@ 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] *}
|
||||
{* ../../docs_src/custom_response/tutorial002_py39.py hl[2,7] *}
|
||||
|
||||
/// info
|
||||
|
||||
|
|
@ -73,7 +73,7 @@ As seen in [Return a Response directly](response-directly.md){.internal-link tar
|
|||
|
||||
The same example from above, returning an `HTMLResponse`, could look like:
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial003.py hl[2,7,19] *}
|
||||
{* ../../docs_src/custom_response/tutorial003_py39.py hl[2,7,19] *}
|
||||
|
||||
/// warning
|
||||
|
||||
|
|
@ -97,7 +97,7 @@ The `response_class` will then be used only to document the OpenAPI *path operat
|
|||
|
||||
For example, it could be something like:
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial004.py hl[7,21,23] *}
|
||||
{* ../../docs_src/custom_response/tutorial004_py39.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`.
|
||||
|
||||
|
|
@ -136,7 +136,7 @@ It accepts the following parameters:
|
|||
|
||||
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] *}
|
||||
{* ../../docs_src/response_directly/tutorial002_py39.py hl[1,18] *}
|
||||
|
||||
### `HTMLResponse` { #htmlresponse }
|
||||
|
||||
|
|
@ -146,7 +146,7 @@ Takes some text or bytes and returns an HTML response, as you read above.
|
|||
|
||||
Takes some text or bytes and returns a plain text response.
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial005.py hl[2,7,9] *}
|
||||
{* ../../docs_src/custom_response/tutorial005_py39.py hl[2,7,9] *}
|
||||
|
||||
### `JSONResponse` { #jsonresponse }
|
||||
|
||||
|
|
@ -180,7 +180,7 @@ This requires installing `ujson` for example with `pip install ujson`.
|
|||
|
||||
///
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial001.py hl[2,7] *}
|
||||
{* ../../docs_src/custom_response/tutorial001_py39.py hl[2,7] *}
|
||||
|
||||
/// tip
|
||||
|
||||
|
|
@ -194,14 +194,14 @@ 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] *}
|
||||
{* ../../docs_src/custom_response/tutorial006_py39.py hl[2,9] *}
|
||||
|
||||
---
|
||||
|
||||
Or you can use it in the `response_class` parameter:
|
||||
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial006b.py hl[2,7,9] *}
|
||||
{* ../../docs_src/custom_response/tutorial006b_py39.py hl[2,7,9] *}
|
||||
|
||||
If you do that, then you can return the URL directly from your *path operation* function.
|
||||
|
||||
|
|
@ -211,13 +211,13 @@ In this case, the `status_code` used will be the default one for the `RedirectRe
|
|||
|
||||
You can also use the `status_code` parameter combined with the `response_class` parameter:
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial006c.py hl[2,7,9] *}
|
||||
{* ../../docs_src/custom_response/tutorial006c_py39.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] *}
|
||||
{* ../../docs_src/custom_response/tutorial007_py39.py hl[2,14] *}
|
||||
|
||||
#### Using `StreamingResponse` with file-like objects { #using-streamingresponse-with-file-like-objects }
|
||||
|
||||
|
|
@ -227,7 +227,7 @@ That way, you don't have to read it all first in memory, and you can pass that g
|
|||
|
||||
This includes many libraries to interact with cloud storage, video processing, and others.
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial008.py hl[2,10:12,14] *}
|
||||
{* ../../docs_src/custom_response/tutorial008_py39.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.
|
||||
|
|
@ -256,11 +256,11 @@ Takes a different set of arguments to instantiate than the other response types:
|
|||
|
||||
File responses will include appropriate `Content-Length`, `Last-Modified` and `ETag` headers.
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial009.py hl[2,10] *}
|
||||
{* ../../docs_src/custom_response/tutorial009_py39.py hl[2,10] *}
|
||||
|
||||
You can also use the `response_class` parameter:
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial009b.py hl[2,8,10] *}
|
||||
{* ../../docs_src/custom_response/tutorial009b_py39.py hl[2,8,10] *}
|
||||
|
||||
In this case, you can return the file path directly from your *path operation* function.
|
||||
|
||||
|
|
@ -274,7 +274,7 @@ Let's say you want it to return indented and formatted JSON, so you want to use
|
|||
|
||||
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] *}
|
||||
{* ../../docs_src/custom_response/tutorial009c_py39.py hl[9:14,17] *}
|
||||
|
||||
Now instead of returning:
|
||||
|
||||
|
|
@ -300,7 +300,7 @@ 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] *}
|
||||
{* ../../docs_src/custom_response/tutorial010_py39.py hl[2,4] *}
|
||||
|
||||
/// tip
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ 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] *}
|
||||
{* ../../docs_src/events/tutorial003_py39.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*.
|
||||
|
||||
|
|
@ -48,7 +48,7 @@ Maybe you need to start a new version, or you just got tired of running it. 🤷
|
|||
|
||||
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] *}
|
||||
{* ../../docs_src/events/tutorial003_py39.py hl[14:19] *}
|
||||
|
||||
The first part of the function, before the `yield`, will be executed **before** the application starts.
|
||||
|
||||
|
|
@ -60,7 +60,7 @@ 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] *}
|
||||
{* ../../docs_src/events/tutorial003_py39.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:
|
||||
|
||||
|
|
@ -82,7 +82,7 @@ In our code example above, we don't use it directly, but we pass it to FastAPI f
|
|||
|
||||
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] *}
|
||||
{* ../../docs_src/events/tutorial003_py39.py hl[22] *}
|
||||
|
||||
## Alternative Events (deprecated) { #alternative-events-deprecated }
|
||||
|
||||
|
|
@ -104,7 +104,7 @@ These functions can be declared with `async def` or normal `def`.
|
|||
|
||||
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] *}
|
||||
{* ../../docs_src/events/tutorial001_py39.py hl[8] *}
|
||||
|
||||
In this case, the `startup` event handler function will initialize the items "database" (just a `dict`) with some values.
|
||||
|
||||
|
|
@ -116,7 +116,7 @@ And your application won't start receiving requests until all the `startup` even
|
|||
|
||||
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] *}
|
||||
{* ../../docs_src/events/tutorial002_py39.py hl[6] *}
|
||||
|
||||
Here, the `shutdown` event handler function will write a text line `"Application shutdown"` to a file `log.txt`.
|
||||
|
||||
|
|
|
|||
|
|
@ -167,7 +167,7 @@ But for the generated client, we could **modify** the OpenAPI operation IDs righ
|
|||
|
||||
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 *}
|
||||
{* ../../docs_src/generate_clients/tutorial004_py39.py *}
|
||||
|
||||
//// tab | Node.js
|
||||
|
||||
|
|
|
|||
|
|
@ -57,13 +57,13 @@ 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] *}
|
||||
{* ../../docs_src/advanced_middleware/tutorial001_py39.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] *}
|
||||
{* ../../docs_src/advanced_middleware/tutorial002_py39.py hl[2,6:8] *}
|
||||
|
||||
The following arguments are supported:
|
||||
|
||||
|
|
@ -78,7 +78,7 @@ Handles GZip responses for any request that includes `"gzip"` in the `Accept-Enc
|
|||
|
||||
The middleware will handle both standard and streaming responses.
|
||||
|
||||
{* ../../docs_src/advanced_middleware/tutorial003.py hl[2,6] *}
|
||||
{* ../../docs_src/advanced_middleware/tutorial003_py39.py hl[2,6] *}
|
||||
|
||||
The following arguments are supported:
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ Webhooks are available in OpenAPI 3.1.0 and above, supported by FastAPI `0.99.0`
|
|||
|
||||
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] *}
|
||||
{* ../../docs_src/openapi_webhooks/tutorial001_py39.py hl[9:13,36:53] *}
|
||||
|
||||
The webhooks that you define will end up in the **OpenAPI** schema and the automatic **docs UI**.
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ You can set the OpenAPI `operationId` to be used in your *path operation* with t
|
|||
|
||||
You would have to make sure that it is unique for each operation.
|
||||
|
||||
{* ../../docs_src/path_operation_advanced_configuration/tutorial001.py hl[6] *}
|
||||
{* ../../docs_src/path_operation_advanced_configuration/tutorial001_py39.py hl[6] *}
|
||||
|
||||
### Using the *path operation function* name as the operationId { #using-the-path-operation-function-name-as-the-operationid }
|
||||
|
||||
|
|
@ -20,7 +20,7 @@ If you want to use your APIs' function names as `operationId`s, you can iterate
|
|||
|
||||
You should do it after adding all your *path operations*.
|
||||
|
||||
{* ../../docs_src/path_operation_advanced_configuration/tutorial002.py hl[2, 12:21, 24] *}
|
||||
{* ../../docs_src/path_operation_advanced_configuration/tutorial002_py39.py hl[2, 12:21, 24] *}
|
||||
|
||||
/// tip
|
||||
|
||||
|
|
@ -40,7 +40,7 @@ Even if they are in different modules (Python files).
|
|||
|
||||
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] *}
|
||||
{* ../../docs_src/path_operation_advanced_configuration/tutorial003_py39.py hl[6] *}
|
||||
|
||||
## Advanced description from docstring { #advanced-description-from-docstring }
|
||||
|
||||
|
|
@ -92,7 +92,7 @@ You can extend the OpenAPI schema for a *path operation* using the parameter `op
|
|||
|
||||
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] *}
|
||||
{* ../../docs_src/path_operation_advanced_configuration/tutorial005_py39.py hl[6] *}
|
||||
|
||||
If you open the automatic API docs, your extension will show up at the bottom of the specific *path operation*.
|
||||
|
||||
|
|
@ -139,7 +139,7 @@ For example, you could decide to read and validate the request with your own cod
|
|||
|
||||
You could do that with `openapi_extra`:
|
||||
|
||||
{* ../../docs_src/path_operation_advanced_configuration/tutorial006.py hl[19:36, 39:40] *}
|
||||
{* ../../docs_src/path_operation_advanced_configuration/tutorial006_py39.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.
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ You can declare a parameter of type `Response` in your *path operation function*
|
|||
|
||||
And then you can set the `status_code` in that *temporal* response object.
|
||||
|
||||
{* ../../docs_src/response_change_status_code/tutorial001.py hl[1,9,12] *}
|
||||
{* ../../docs_src/response_change_status_code/tutorial001_py39.py hl[1,9,12] *}
|
||||
|
||||
And then you can return any object you need, as you normally would (a `dict`, a database model, etc).
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ You can declare a parameter of type `Response` in your *path operation function*
|
|||
|
||||
And then you can set cookies in that *temporal* response object.
|
||||
|
||||
{* ../../docs_src/response_cookies/tutorial002.py hl[1, 8:9] *}
|
||||
{* ../../docs_src/response_cookies/tutorial002_py39.py hl[1, 8:9] *}
|
||||
|
||||
And then you can return any object you need, as you normally would (a `dict`, a database model, etc).
|
||||
|
||||
|
|
@ -24,7 +24,7 @@ To do that, you can create a response as described in [Return a Response Directl
|
|||
|
||||
Then set Cookies in it, and then return it:
|
||||
|
||||
{* ../../docs_src/response_cookies/tutorial001.py hl[10:12] *}
|
||||
{* ../../docs_src/response_cookies/tutorial001_py39.py hl[10:12] *}
|
||||
|
||||
/// tip
|
||||
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ Let's say that you want to return an <a href="https://en.wikipedia.org/wiki/XML"
|
|||
|
||||
You could put your XML content in a string, put that in a `Response`, and return it:
|
||||
|
||||
{* ../../docs_src/response_directly/tutorial002.py hl[1,18] *}
|
||||
{* ../../docs_src/response_directly/tutorial002_py39.py hl[1,18] *}
|
||||
|
||||
## Notes { #notes }
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ You can declare a parameter of type `Response` in your *path operation function*
|
|||
|
||||
And then you can set headers in that *temporal* response object.
|
||||
|
||||
{* ../../docs_src/response_headers/tutorial002.py hl[1, 7:8] *}
|
||||
{* ../../docs_src/response_headers/tutorial002_py39.py hl[1, 7:8] *}
|
||||
|
||||
And then you can return any object you need, as you normally would (a `dict`, a database model, etc).
|
||||
|
||||
|
|
@ -22,7 +22,7 @@ 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] *}
|
||||
{* ../../docs_src/response_headers/tutorial001_py39.py hl[10:12] *}
|
||||
|
||||
/// note | Technical Details
|
||||
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ You can use all the same validation features and tools you use for Pydantic mode
|
|||
|
||||
//// tab | Pydantic v2
|
||||
|
||||
{* ../../docs_src/settings/tutorial001.py hl[2,5:8,11] *}
|
||||
{* ../../docs_src/settings/tutorial001_py39.py hl[2,5:8,11] *}
|
||||
|
||||
////
|
||||
|
||||
|
|
@ -74,7 +74,7 @@ In Pydantic v1 you would import `BaseSettings` directly from `pydantic` instead
|
|||
|
||||
///
|
||||
|
||||
{* ../../docs_src/settings/tutorial001_pv1.py hl[2,5:8,11] *}
|
||||
{* ../../docs_src/settings/tutorial001_pv1_py39.py hl[2,5:8,11] *}
|
||||
|
||||
////
|
||||
|
||||
|
|
@ -92,7 +92,7 @@ Next it will convert and validate the data. So, when you use that `settings` obj
|
|||
|
||||
Then you can use the new `settings` object in your application:
|
||||
|
||||
{* ../../docs_src/settings/tutorial001.py hl[18:20] *}
|
||||
{* ../../docs_src/settings/tutorial001_py39.py hl[18:20] *}
|
||||
|
||||
### Run the server { #run-the-server }
|
||||
|
||||
|
|
@ -126,11 +126,11 @@ You could put those settings in another module file as you saw in [Bigger Applic
|
|||
|
||||
For example, you could have a file `config.py` with:
|
||||
|
||||
{* ../../docs_src/settings/app01/config.py *}
|
||||
{* ../../docs_src/settings/app01_py39/config.py *}
|
||||
|
||||
And then use it in a file `main.py`:
|
||||
|
||||
{* ../../docs_src/settings/app01/main.py hl[3,11:13] *}
|
||||
{* ../../docs_src/settings/app01_py39/main.py hl[3,11:13] *}
|
||||
|
||||
/// tip
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ If you need to have two independent FastAPI applications, with their own indepen
|
|||
|
||||
First, create the main, top-level, **FastAPI** application, and its *path operations*:
|
||||
|
||||
{* ../../docs_src/sub_applications/tutorial001.py hl[3, 6:8] *}
|
||||
{* ../../docs_src/sub_applications/tutorial001_py39.py hl[3, 6:8] *}
|
||||
|
||||
### Sub-application { #sub-application }
|
||||
|
||||
|
|
@ -18,7 +18,7 @@ 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] *}
|
||||
{* ../../docs_src/sub_applications/tutorial001_py39.py hl[11, 14:16] *}
|
||||
|
||||
### Mount the sub-application { #mount-the-sub-application }
|
||||
|
||||
|
|
@ -26,7 +26,7 @@ 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] *}
|
||||
{* ../../docs_src/sub_applications/tutorial001_py39.py hl[11, 19] *}
|
||||
|
||||
### Check the automatic API docs { #check-the-automatic-api-docs }
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ $ pip install jinja2
|
|||
* 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] *}
|
||||
{* ../../docs_src/templates/tutorial001_py39.py hl[4,11,15:18] *}
|
||||
|
||||
/// note
|
||||
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
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] *}
|
||||
{* ../../docs_src/app_testing/tutorial004_py39.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.dev/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] *}
|
||||
{* ../../docs_src/app_testing/tutorial003_py39.py hl[9:12,20:24] *}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ 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] *}
|
||||
{* ../../docs_src/app_testing/tutorial002_py39.py hl[27:31] *}
|
||||
|
||||
/// note
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ Let's imagine you want to get the client's IP address/host inside of your *path
|
|||
|
||||
For that you need to access the request directly.
|
||||
|
||||
{* ../../docs_src/using_request_directly/tutorial001.py hl[1,7:8] *}
|
||||
{* ../../docs_src/using_request_directly/tutorial001_py39.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.
|
||||
|
||||
|
|
|
|||
|
|
@ -38,13 +38,13 @@ In production you would have one of the options above.
|
|||
|
||||
But it's the simplest way to focus on the server-side of WebSockets and have a working example:
|
||||
|
||||
{* ../../docs_src/websockets/tutorial001.py hl[2,6:38,41:43] *}
|
||||
{* ../../docs_src/websockets/tutorial001_py39.py hl[2,6:38,41:43] *}
|
||||
|
||||
## Create a `websocket` { #create-a-websocket }
|
||||
|
||||
In your **FastAPI** application, create a `websocket`:
|
||||
|
||||
{* ../../docs_src/websockets/tutorial001.py hl[1,46:47] *}
|
||||
{* ../../docs_src/websockets/tutorial001_py39.py hl[1,46:47] *}
|
||||
|
||||
/// note | Technical Details
|
||||
|
||||
|
|
@ -58,7 +58,7 @@ You could also use `from starlette.websockets import WebSocket`.
|
|||
|
||||
In your WebSocket route you can `await` for messages and send messages.
|
||||
|
||||
{* ../../docs_src/websockets/tutorial001.py hl[48:52] *}
|
||||
{* ../../docs_src/websockets/tutorial001_py39.py hl[48:52] *}
|
||||
|
||||
You can receive and send binary, text, and JSON data.
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ 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] *}
|
||||
{* ../../docs_src/wsgi/tutorial001_py39.py hl[2:3,3] *}
|
||||
|
||||
## Check it { #check-it }
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ You can easily use the same Pydantic settings to configure your generated OpenAP
|
|||
|
||||
For example:
|
||||
|
||||
{* ../../docs_src/conditional_openapi/tutorial001.py hl[6,11] *}
|
||||
{* ../../docs_src/conditional_openapi/tutorial001_py39.py hl[6,11] *}
|
||||
|
||||
Here we declare the setting `openapi_url` with the same default of `"/openapi.json"`.
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ Without changing the settings, syntax highlighting is enabled by default:
|
|||
|
||||
But you can disable it by setting `syntaxHighlight` to `False`:
|
||||
|
||||
{* ../../docs_src/configure_swagger_ui/tutorial001.py hl[3] *}
|
||||
{* ../../docs_src/configure_swagger_ui/tutorial001_py39.py hl[3] *}
|
||||
|
||||
...and then Swagger UI won't show the syntax highlighting anymore:
|
||||
|
||||
|
|
@ -28,7 +28,7 @@ But you can disable it by setting `syntaxHighlight` to `False`:
|
|||
|
||||
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] *}
|
||||
{* ../../docs_src/configure_swagger_ui/tutorial002_py39.py hl[3] *}
|
||||
|
||||
That configuration would change the syntax highlighting color theme:
|
||||
|
||||
|
|
@ -46,7 +46,7 @@ You can override any of them by setting a different value in the argument `swagg
|
|||
|
||||
For example, to disable `deepLinking` you could pass these settings to `swagger_ui_parameters`:
|
||||
|
||||
{* ../../docs_src/configure_swagger_ui/tutorial003.py hl[3] *}
|
||||
{* ../../docs_src/configure_swagger_ui/tutorial003_py39.py hl[3] *}
|
||||
|
||||
## Other Swagger UI Parameters { #other-swagger-ui-parameters }
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ The first step is to disable the automatic docs, as by default, those use the de
|
|||
|
||||
To disable them, set their URLs to `None` when creating your `FastAPI` app:
|
||||
|
||||
{* ../../docs_src/custom_docs_ui/tutorial001.py hl[8] *}
|
||||
{* ../../docs_src/custom_docs_ui/tutorial001_py39.py hl[8] *}
|
||||
|
||||
### Include the custom docs { #include-the-custom-docs }
|
||||
|
||||
|
|
@ -34,7 +34,7 @@ You can reuse FastAPI's internal functions to create the HTML pages for the docs
|
|||
|
||||
And similarly for ReDoc...
|
||||
|
||||
{* ../../docs_src/custom_docs_ui/tutorial001.py hl[2:6,11:19,22:24,27:33] *}
|
||||
{* ../../docs_src/custom_docs_ui/tutorial001_py39.py hl[2:6,11:19,22:24,27:33] *}
|
||||
|
||||
/// tip
|
||||
|
||||
|
|
@ -50,7 +50,7 @@ Swagger UI will handle it behind the scenes for you, but it needs this "redirect
|
|||
|
||||
Now, to be able to test that everything works, create a *path operation*:
|
||||
|
||||
{* ../../docs_src/custom_docs_ui/tutorial001.py hl[36:38] *}
|
||||
{* ../../docs_src/custom_docs_ui/tutorial001_py39.py hl[36:38] *}
|
||||
|
||||
### Test it { #test-it }
|
||||
|
||||
|
|
@ -118,7 +118,7 @@ After that, your file structure could look like:
|
|||
* Import `StaticFiles`.
|
||||
* "Mount" a `StaticFiles()` instance in a specific path.
|
||||
|
||||
{* ../../docs_src/custom_docs_ui/tutorial002.py hl[7,11] *}
|
||||
{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[7,11] *}
|
||||
|
||||
### Test the static files { #test-the-static-files }
|
||||
|
||||
|
|
@ -144,7 +144,7 @@ The same as when using a custom CDN, the first step is to disable the automatic
|
|||
|
||||
To disable them, set their URLs to `None` when creating your `FastAPI` app:
|
||||
|
||||
{* ../../docs_src/custom_docs_ui/tutorial002.py hl[9] *}
|
||||
{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[9] *}
|
||||
|
||||
### Include the custom docs for static files { #include-the-custom-docs-for-static-files }
|
||||
|
||||
|
|
@ -160,7 +160,7 @@ Again, you can reuse FastAPI's internal functions to create the HTML pages for t
|
|||
|
||||
And similarly for ReDoc...
|
||||
|
||||
{* ../../docs_src/custom_docs_ui/tutorial002.py hl[2:6,14:22,25:27,30:36] *}
|
||||
{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[2:6,14:22,25:27,30:36] *}
|
||||
|
||||
/// tip
|
||||
|
||||
|
|
@ -176,7 +176,7 @@ Swagger UI will handle it behind the scenes for you, but it needs this "redirect
|
|||
|
||||
Now, to be able to test that everything works, create a *path operation*:
|
||||
|
||||
{* ../../docs_src/custom_docs_ui/tutorial002.py hl[39:41] *}
|
||||
{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[39:41] *}
|
||||
|
||||
### Test Static Files UI { #test-static-files-ui }
|
||||
|
||||
|
|
|
|||
|
|
@ -43,19 +43,19 @@ For example, let's add <a href="https://github.com/Rebilly/ReDoc/blob/master/doc
|
|||
|
||||
First, write all your **FastAPI** application as normally:
|
||||
|
||||
{* ../../docs_src/extending_openapi/tutorial001.py hl[1,4,7:9] *}
|
||||
{* ../../docs_src/extending_openapi/tutorial001_py39.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] *}
|
||||
{* ../../docs_src/extending_openapi/tutorial001_py39.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] *}
|
||||
{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[22:24] *}
|
||||
|
||||
### Cache the OpenAPI schema { #cache-the-openapi-schema }
|
||||
|
||||
|
|
@ -65,13 +65,13 @@ That way, your application won't have to generate the schema every time a user o
|
|||
|
||||
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] *}
|
||||
{* ../../docs_src/extending_openapi/tutorial001_py39.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] *}
|
||||
{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[29] *}
|
||||
|
||||
### Check it { #check-it }
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ Depending on your use case, you might prefer to use a different library, but if
|
|||
|
||||
Here's a small preview of how you could integrate Strawberry with FastAPI:
|
||||
|
||||
{* ../../docs_src/graphql/tutorial001.py hl[3,22,25] *}
|
||||
{* ../../docs_src/graphql/tutorial001_py39.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>.
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ If you are a Python expert, and you already know everything about type hints, sk
|
|||
|
||||
Let's start with a simple example:
|
||||
|
||||
{* ../../docs_src/python_types/tutorial001.py *}
|
||||
{* ../../docs_src/python_types/tutorial001_py39.py *}
|
||||
|
||||
Calling this program outputs:
|
||||
|
||||
|
|
@ -36,7 +36,7 @@ The function does the following:
|
|||
* Converts the first letter of each one to upper case with `title()`.
|
||||
* <abbr title="Puts them together, as one. With the contents of one after the other.">Concatenates</abbr> them with a space in the middle.
|
||||
|
||||
{* ../../docs_src/python_types/tutorial001.py hl[2] *}
|
||||
{* ../../docs_src/python_types/tutorial001_py39.py hl[2] *}
|
||||
|
||||
### Edit it { #edit-it }
|
||||
|
||||
|
|
@ -78,7 +78,7 @@ That's it.
|
|||
|
||||
Those are the "type hints":
|
||||
|
||||
{* ../../docs_src/python_types/tutorial002.py hl[1] *}
|
||||
{* ../../docs_src/python_types/tutorial002_py39.py hl[1] *}
|
||||
|
||||
That is not the same as declaring default values like would be with:
|
||||
|
||||
|
|
@ -106,7 +106,7 @@ With that, you can scroll, seeing the options, until you find the one that "ring
|
|||
|
||||
Check this function, it already has type hints:
|
||||
|
||||
{* ../../docs_src/python_types/tutorial003.py hl[1] *}
|
||||
{* ../../docs_src/python_types/tutorial003_py39.py hl[1] *}
|
||||
|
||||
Because the editor knows the types of the variables, you don't only get completion, you also get error checks:
|
||||
|
||||
|
|
@ -114,7 +114,7 @@ Because the editor knows the types of the variables, you don't only get completi
|
|||
|
||||
Now you know that you have to fix it, convert `age` to a string with `str(age)`:
|
||||
|
||||
{* ../../docs_src/python_types/tutorial004.py hl[2] *}
|
||||
{* ../../docs_src/python_types/tutorial004_py39.py hl[2] *}
|
||||
|
||||
## Declaring types { #declaring-types }
|
||||
|
||||
|
|
@ -133,7 +133,7 @@ You can use, for example:
|
|||
* `bool`
|
||||
* `bytes`
|
||||
|
||||
{* ../../docs_src/python_types/tutorial005.py hl[1] *}
|
||||
{* ../../docs_src/python_types/tutorial005_py39.py hl[1] *}
|
||||
|
||||
### Generic types with type parameters { #generic-types-with-type-parameters }
|
||||
|
||||
|
|
@ -161,56 +161,24 @@ If you can use the **latest versions of Python**, use the examples for the lates
|
|||
|
||||
For example, let's define a variable to be a `list` of `str`.
|
||||
|
||||
//// tab | Python 3.9+
|
||||
|
||||
Declare the variable, with the same colon (`:`) syntax.
|
||||
|
||||
As the type, put `list`.
|
||||
|
||||
As the list is a type that contains some internal types, you put them in square brackets:
|
||||
|
||||
```Python hl_lines="1"
|
||||
{!> ../../docs_src/python_types/tutorial006_py39.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.8+
|
||||
|
||||
From `typing`, import `List` (with a capital `L`):
|
||||
|
||||
```Python hl_lines="1"
|
||||
{!> ../../docs_src/python_types/tutorial006.py!}
|
||||
```
|
||||
|
||||
Declare the variable, with the same colon (`:`) syntax.
|
||||
|
||||
As the type, put the `List` that you imported from `typing`.
|
||||
|
||||
As the list is a type that contains some internal types, you put them in square brackets:
|
||||
|
||||
```Python hl_lines="4"
|
||||
{!> ../../docs_src/python_types/tutorial006.py!}
|
||||
```
|
||||
|
||||
////
|
||||
{* ../../docs_src/python_types/tutorial006_py39.py hl[1] *}
|
||||
|
||||
/// info
|
||||
|
||||
Those internal types in the square brackets are called "type parameters".
|
||||
|
||||
In this case, `str` is the type parameter passed to `List` (or `list` in Python 3.9 and above).
|
||||
In this case, `str` is the type parameter passed to `list`.
|
||||
|
||||
///
|
||||
|
||||
That means: "the variable `items` is a `list`, and each of the items in this list is a `str`".
|
||||
|
||||
/// tip
|
||||
|
||||
If you use Python 3.9 or above, you don't have to import `List` from `typing`, you can use the same regular `list` type instead.
|
||||
|
||||
///
|
||||
|
||||
By doing that, your editor can provide support even while processing items from the list:
|
||||
|
||||
<img src="/img/python-types/image05.png">
|
||||
|
|
@ -225,21 +193,7 @@ And still, the editor knows it is a `str`, and provides support for that.
|
|||
|
||||
You would do the same to declare `tuple`s and `set`s:
|
||||
|
||||
//// tab | Python 3.9+
|
||||
|
||||
```Python hl_lines="1"
|
||||
{!> ../../docs_src/python_types/tutorial007_py39.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.8+
|
||||
|
||||
```Python hl_lines="1 4"
|
||||
{!> ../../docs_src/python_types/tutorial007.py!}
|
||||
```
|
||||
|
||||
////
|
||||
{* ../../docs_src/python_types/tutorial007_py39.py hl[1] *}
|
||||
|
||||
This means:
|
||||
|
||||
|
|
@ -254,21 +208,7 @@ The first type parameter is for the keys of the `dict`.
|
|||
|
||||
The second type parameter is for the values of the `dict`:
|
||||
|
||||
//// tab | Python 3.9+
|
||||
|
||||
```Python hl_lines="1"
|
||||
{!> ../../docs_src/python_types/tutorial008_py39.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.8+
|
||||
|
||||
```Python hl_lines="1 4"
|
||||
{!> ../../docs_src/python_types/tutorial008.py!}
|
||||
```
|
||||
|
||||
////
|
||||
{* ../../docs_src/python_types/tutorial008_py39.py hl[1] *}
|
||||
|
||||
This means:
|
||||
|
||||
|
|
@ -292,10 +232,10 @@ In Python 3.10 there's also a **new syntax** where you can put the possible type
|
|||
|
||||
////
|
||||
|
||||
//// tab | Python 3.8+
|
||||
//// tab | Python 3.9+
|
||||
|
||||
```Python hl_lines="1 4"
|
||||
{!> ../../docs_src/python_types/tutorial008b.py!}
|
||||
{!> ../../docs_src/python_types/tutorial008b_py39.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
|
@ -309,7 +249,7 @@ You can declare that a value could have a type, like `str`, but that it could al
|
|||
In Python 3.6 and above (including Python 3.10) you can declare it by importing and using `Optional` from the `typing` module.
|
||||
|
||||
```Python hl_lines="1 4"
|
||||
{!../../docs_src/python_types/tutorial009.py!}
|
||||
{!../../docs_src/python_types/tutorial009_py39.py!}
|
||||
```
|
||||
|
||||
Using `Optional[str]` instead of just `str` will let the editor help you detect errors where you could be assuming that a value is always a `str`, when it could actually be `None` too.
|
||||
|
|
@ -326,18 +266,18 @@ This also means that in Python 3.10, you can use `Something | None`:
|
|||
|
||||
////
|
||||
|
||||
//// tab | Python 3.8+
|
||||
//// tab | Python 3.9+
|
||||
|
||||
```Python hl_lines="1 4"
|
||||
{!> ../../docs_src/python_types/tutorial009.py!}
|
||||
{!> ../../docs_src/python_types/tutorial009_py39.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.8+ alternative
|
||||
//// tab | Python 3.9+ alternative
|
||||
|
||||
```Python hl_lines="1 4"
|
||||
{!> ../../docs_src/python_types/tutorial009b.py!}
|
||||
{!> ../../docs_src/python_types/tutorial009b_py39.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
|
@ -357,7 +297,7 @@ It's just about the words and names. But those words can affect how you and your
|
|||
|
||||
As an example, let's take this function:
|
||||
|
||||
{* ../../docs_src/python_types/tutorial009c.py hl[1,4] *}
|
||||
{* ../../docs_src/python_types/tutorial009c_py39.py hl[1,4] *}
|
||||
|
||||
The parameter `name` is defined as `Optional[str]`, but it is **not optional**, you cannot call the function without the parameter:
|
||||
|
||||
|
|
@ -390,10 +330,10 @@ You can use the same builtin types as generics (with square brackets and types i
|
|||
* `set`
|
||||
* `dict`
|
||||
|
||||
And the same as with Python 3.8, from the `typing` module:
|
||||
And the same as with previous Python versions, from the `typing` module:
|
||||
|
||||
* `Union`
|
||||
* `Optional` (the same as with Python 3.8)
|
||||
* `Optional`
|
||||
* ...and others.
|
||||
|
||||
In Python 3.10, as an alternative to using the generics `Union` and `Optional`, you can use the <abbr title='also called "bitwise or operator", but that meaning is not relevant here'>vertical bar (`|`)</abbr> to declare unions of types, that's a lot better and simpler.
|
||||
|
|
@ -409,7 +349,7 @@ You can use the same builtin types as generics (with square brackets and types i
|
|||
* `set`
|
||||
* `dict`
|
||||
|
||||
And the same as with Python 3.8, from the `typing` module:
|
||||
And generics from the `typing` module:
|
||||
|
||||
* `Union`
|
||||
* `Optional`
|
||||
|
|
@ -417,29 +357,17 @@ And the same as with Python 3.8, from the `typing` module:
|
|||
|
||||
////
|
||||
|
||||
//// tab | Python 3.8+
|
||||
|
||||
* `List`
|
||||
* `Tuple`
|
||||
* `Set`
|
||||
* `Dict`
|
||||
* `Union`
|
||||
* `Optional`
|
||||
* ...and others.
|
||||
|
||||
////
|
||||
|
||||
### Classes as types { #classes-as-types }
|
||||
|
||||
You can also declare a class as the type of a variable.
|
||||
|
||||
Let's say you have a class `Person`, with a name:
|
||||
|
||||
{* ../../docs_src/python_types/tutorial010.py hl[1:3] *}
|
||||
{* ../../docs_src/python_types/tutorial010_py39.py hl[1:3] *}
|
||||
|
||||
Then you can declare a variable to be of type `Person`:
|
||||
|
||||
{* ../../docs_src/python_types/tutorial010.py hl[6] *}
|
||||
{* ../../docs_src/python_types/tutorial010_py39.py hl[6] *}
|
||||
|
||||
And then, again, you get all the editor support:
|
||||
|
||||
|
|
@ -463,29 +391,7 @@ And you get all the editor support with that resulting object.
|
|||
|
||||
An example from the official Pydantic docs:
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python
|
||||
{!> ../../docs_src/python_types/tutorial011_py310.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.9+
|
||||
|
||||
```Python
|
||||
{!> ../../docs_src/python_types/tutorial011_py39.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.8+
|
||||
|
||||
```Python
|
||||
{!> ../../docs_src/python_types/tutorial011.py!}
|
||||
```
|
||||
|
||||
////
|
||||
{* ../../docs_src/python_types/tutorial011_py310.py *}
|
||||
|
||||
/// info
|
||||
|
||||
|
|
@ -507,27 +413,9 @@ Pydantic has a special behavior when you use `Optional` or `Union[Something, Non
|
|||
|
||||
Python also has a feature that allows putting **additional <abbr title="Data about the data, in this case, information about the type, e.g. a description.">metadata</abbr>** in these type hints using `Annotated`.
|
||||
|
||||
//// tab | Python 3.9+
|
||||
Since Python 3.9, `Annotated` is a part of the standard library, so you can import it from `typing`.
|
||||
|
||||
In Python 3.9, `Annotated` is part of the standard library, so you can import it from `typing`.
|
||||
|
||||
```Python hl_lines="1 4"
|
||||
{!> ../../docs_src/python_types/tutorial013_py39.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.8+
|
||||
|
||||
In versions below Python 3.9, you import `Annotated` from `typing_extensions`.
|
||||
|
||||
It will already be installed with **FastAPI**.
|
||||
|
||||
```Python hl_lines="1 4"
|
||||
{!> ../../docs_src/python_types/tutorial013.py!}
|
||||
```
|
||||
|
||||
////
|
||||
{* ../../docs_src/python_types/tutorial013_py39.py hl[1,4] *}
|
||||
|
||||
Python itself doesn't do anything with this `Annotated`. And for editors and other tools, the type is still `str`.
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ This includes, for example:
|
|||
|
||||
First, import `BackgroundTasks` and define a parameter in your *path operation function* with a type declaration of `BackgroundTasks`:
|
||||
|
||||
{* ../../docs_src/background_tasks/tutorial001.py hl[1,13] *}
|
||||
{* ../../docs_src/background_tasks/tutorial001_py39.py hl[1,13] *}
|
||||
|
||||
**FastAPI** will create the object of type `BackgroundTasks` for you and pass it as that parameter.
|
||||
|
||||
|
|
@ -31,13 +31,13 @@ In this case, the task function will write to a file (simulating sending an emai
|
|||
|
||||
And as the write operation doesn't use `async` and `await`, we define the function with normal `def`:
|
||||
|
||||
{* ../../docs_src/background_tasks/tutorial001.py hl[6:9] *}
|
||||
{* ../../docs_src/background_tasks/tutorial001_py39.py hl[6:9] *}
|
||||
|
||||
## Add the background task { #add-the-background-task }
|
||||
|
||||
Inside of your *path operation function*, pass your task function to the *background tasks* object with the method `.add_task()`:
|
||||
|
||||
{* ../../docs_src/background_tasks/tutorial001.py hl[14] *}
|
||||
{* ../../docs_src/background_tasks/tutorial001_py39.py hl[14] *}
|
||||
|
||||
`.add_task()` receives as arguments:
|
||||
|
||||
|
|
|
|||
|
|
@ -14,35 +14,15 @@ This will make `tags` be a list, although it doesn't declare the type of the ele
|
|||
|
||||
But Python has a specific way to declare lists with internal types, or "type parameters":
|
||||
|
||||
### Import typing's `List` { #import-typings-list }
|
||||
|
||||
In Python 3.9 and above you can use the standard `list` to declare these type annotations as we'll see below. 💡
|
||||
|
||||
But in Python versions before 3.9 (3.6 and above), you first need to import `List` from standard Python's `typing` module:
|
||||
|
||||
{* ../../docs_src/body_nested_models/tutorial002.py hl[1] *}
|
||||
|
||||
### Declare a `list` with a type parameter { #declare-a-list-with-a-type-parameter }
|
||||
|
||||
To declare types that have type parameters (internal types), like `list`, `dict`, `tuple`:
|
||||
|
||||
* If you are in a Python version lower than 3.9, import their equivalent version from the `typing` module
|
||||
* Pass the internal type(s) as "type parameters" using square brackets: `[` and `]`
|
||||
|
||||
In Python 3.9 it would be:
|
||||
To declare types that have type parameters (internal types), like `list`, `dict`, `tuple`,
|
||||
pass the internal type(s) as "type parameters" using square brackets: `[` and `]`
|
||||
|
||||
```Python
|
||||
my_list: list[str]
|
||||
```
|
||||
|
||||
In versions of Python before 3.9, it would be:
|
||||
|
||||
```Python
|
||||
from typing import List
|
||||
|
||||
my_list: List[str]
|
||||
```
|
||||
|
||||
That's all standard Python syntax for type declarations.
|
||||
|
||||
Use that same standard syntax for model attributes with internal types.
|
||||
|
|
@ -178,12 +158,6 @@ Notice how `Offer` has a list of `Item`s, which in turn have an optional list of
|
|||
|
||||
If the top level value of the JSON body you expect is a JSON `array` (a Python `list`), you can declare the type in the parameter of the function, the same as in Pydantic models:
|
||||
|
||||
```Python
|
||||
images: List[Image]
|
||||
```
|
||||
|
||||
or in Python 3.9 and above:
|
||||
|
||||
```Python
|
||||
images: list[Image]
|
||||
```
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ You can also specify whether your backend allows:
|
|||
* Specific HTTP methods (`POST`, `PUT`) or all of them with the wildcard `"*"`.
|
||||
* Specific HTTP headers or all of them with the wildcard `"*"`.
|
||||
|
||||
{* ../../docs_src/cors/tutorial001.py hl[2,6:11,13:19] *}
|
||||
{* ../../docs_src/cors/tutorial001_py39.py hl[2,6:11,13:19] *}
|
||||
|
||||
|
||||
The default parameters used by the `CORSMiddleware` implementation are restrictive by default, so you'll need to explicitly enable particular origins, methods, or headers, in order for browsers to be permitted to use them in a Cross-Domain context.
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ You can connect the debugger in your editor, for example with Visual Studio Code
|
|||
|
||||
In your FastAPI application, import and run `uvicorn` directly:
|
||||
|
||||
{* ../../docs_src/debugging/tutorial001.py hl[1,15] *}
|
||||
{* ../../docs_src/debugging/tutorial001_py39.py hl[1,15] *}
|
||||
|
||||
### About `__name__ == "__main__"` { #about-name-main }
|
||||
|
||||
|
|
|
|||
|
|
@ -29,15 +29,15 @@ For example, you could use this to create a database session and close it after
|
|||
|
||||
Only the code prior to and including the `yield` statement is executed before creating a response:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial007.py hl[2:4] *}
|
||||
{* ../../docs_src/dependencies/tutorial007_py39.py hl[2:4] *}
|
||||
|
||||
The yielded value is what is injected into *path operations* and other dependencies:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial007.py hl[4] *}
|
||||
{* ../../docs_src/dependencies/tutorial007_py39.py hl[4] *}
|
||||
|
||||
The code following the `yield` statement is executed after the response:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial007.py hl[5:6] *}
|
||||
{* ../../docs_src/dependencies/tutorial007_py39.py hl[5:6] *}
|
||||
|
||||
/// tip
|
||||
|
||||
|
|
@ -57,7 +57,7 @@ So, you can look for that specific exception inside the dependency with `except
|
|||
|
||||
In the same way, you can use `finally` to make sure the exit steps are executed, no matter if there was an exception or not.
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial007.py hl[3,5] *}
|
||||
{* ../../docs_src/dependencies/tutorial007_py39.py hl[3,5] *}
|
||||
|
||||
## Sub-dependencies with `yield` { #sub-dependencies-with-yield }
|
||||
|
||||
|
|
@ -269,7 +269,7 @@ In Python, you can create Context Managers by <a href="https://docs.python.org/3
|
|||
You can also use them inside of **FastAPI** dependencies with `yield` by using
|
||||
`with` or `async with` statements inside of the dependency function:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial010.py hl[1:9,13] *}
|
||||
{* ../../docs_src/dependencies/tutorial010_py39.py hl[1:9,13] *}
|
||||
|
||||
/// tip
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ Similar to the way you can [add `dependencies` to the *path operation decorators
|
|||
|
||||
In that case, they will be applied to all the *path operations* in the application:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial012_an_py39.py hl[16] *}
|
||||
{* ../../docs_src/dependencies/tutorial012_an_py39.py hl[17] *}
|
||||
|
||||
|
||||
And all the ideas in the section about [adding `dependencies` to the *path operation decorators*](dependencies-in-path-operation-decorators.md){.internal-link target=_blank} still apply, but in this case, to all of the *path operations* in the app.
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
The simplest FastAPI file could look like this:
|
||||
|
||||
{* ../../docs_src/first_steps/tutorial001.py *}
|
||||
{* ../../docs_src/first_steps/tutorial001_py39.py *}
|
||||
|
||||
Copy that to a file `main.py`.
|
||||
|
||||
|
|
@ -183,7 +183,7 @@ That's it! Now you can access your app at that URL. ✨
|
|||
|
||||
### Step 1: import `FastAPI` { #step-1-import-fastapi }
|
||||
|
||||
{* ../../docs_src/first_steps/tutorial001.py hl[1] *}
|
||||
{* ../../docs_src/first_steps/tutorial001_py39.py hl[1] *}
|
||||
|
||||
`FastAPI` is a Python class that provides all the functionality for your API.
|
||||
|
||||
|
|
@ -197,7 +197,7 @@ You can use all the <a href="https://www.starlette.dev/" class="external-link" t
|
|||
|
||||
### Step 2: create a `FastAPI` "instance" { #step-2-create-a-fastapi-instance }
|
||||
|
||||
{* ../../docs_src/first_steps/tutorial001.py hl[3] *}
|
||||
{* ../../docs_src/first_steps/tutorial001_py39.py hl[3] *}
|
||||
|
||||
Here the `app` variable will be an "instance" of the class `FastAPI`.
|
||||
|
||||
|
|
@ -266,7 +266,7 @@ We are going to call them "**operations**" too.
|
|||
|
||||
#### Define a *path operation decorator* { #define-a-path-operation-decorator }
|
||||
|
||||
{* ../../docs_src/first_steps/tutorial001.py hl[6] *}
|
||||
{* ../../docs_src/first_steps/tutorial001_py39.py hl[6] *}
|
||||
|
||||
The `@app.get("/")` tells **FastAPI** that the function right below is in charge of handling requests that go to:
|
||||
|
||||
|
|
@ -320,7 +320,7 @@ This is our "**path operation function**":
|
|||
* **operation**: is `get`.
|
||||
* **function**: is the function below the "decorator" (below `@app.get("/")`).
|
||||
|
||||
{* ../../docs_src/first_steps/tutorial001.py hl[7] *}
|
||||
{* ../../docs_src/first_steps/tutorial001_py39.py hl[7] *}
|
||||
|
||||
This is a Python function.
|
||||
|
||||
|
|
@ -332,7 +332,7 @@ In this case, it is an `async` function.
|
|||
|
||||
You could also define it as a normal function instead of `async def`:
|
||||
|
||||
{* ../../docs_src/first_steps/tutorial003.py hl[7] *}
|
||||
{* ../../docs_src/first_steps/tutorial003_py39.py hl[7] *}
|
||||
|
||||
/// note
|
||||
|
||||
|
|
@ -342,7 +342,7 @@ If you don't know the difference, check the [Async: *"In a hurry?"*](../async.md
|
|||
|
||||
### Step 5: return the content { #step-5-return-the-content }
|
||||
|
||||
{* ../../docs_src/first_steps/tutorial001.py hl[8] *}
|
||||
{* ../../docs_src/first_steps/tutorial001_py39.py hl[8] *}
|
||||
|
||||
You can return a `dict`, `list`, singular values as `str`, `int`, etc.
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ To return HTTP responses with errors to the client you use `HTTPException`.
|
|||
|
||||
### Import `HTTPException` { #import-httpexception }
|
||||
|
||||
{* ../../docs_src/handling_errors/tutorial001.py hl[1] *}
|
||||
{* ../../docs_src/handling_errors/tutorial001_py39.py hl[1] *}
|
||||
|
||||
### Raise an `HTTPException` in your code { #raise-an-httpexception-in-your-code }
|
||||
|
||||
|
|
@ -39,7 +39,7 @@ The benefit of raising an exception over returning a value will be more evident
|
|||
|
||||
In this example, when the client requests an item by an ID that doesn't exist, raise an exception with a status code of `404`:
|
||||
|
||||
{* ../../docs_src/handling_errors/tutorial001.py hl[11] *}
|
||||
{* ../../docs_src/handling_errors/tutorial001_py39.py hl[11] *}
|
||||
|
||||
### The resulting response { #the-resulting-response }
|
||||
|
||||
|
|
@ -77,7 +77,7 @@ You probably won't need to use it directly in your code.
|
|||
|
||||
But in case you needed it for an advanced scenario, you can add custom headers:
|
||||
|
||||
{* ../../docs_src/handling_errors/tutorial002.py hl[14] *}
|
||||
{* ../../docs_src/handling_errors/tutorial002_py39.py hl[14] *}
|
||||
|
||||
## Install custom exception handlers { #install-custom-exception-handlers }
|
||||
|
||||
|
|
@ -89,7 +89,7 @@ And you want to handle this exception globally with FastAPI.
|
|||
|
||||
You could add a custom exception handler with `@app.exception_handler()`:
|
||||
|
||||
{* ../../docs_src/handling_errors/tutorial003.py hl[5:7,13:18,24] *}
|
||||
{* ../../docs_src/handling_errors/tutorial003_py39.py hl[5:7,13:18,24] *}
|
||||
|
||||
Here, if you request `/unicorns/yolo`, the *path operation* will `raise` a `UnicornException`.
|
||||
|
||||
|
|
@ -127,7 +127,7 @@ To override it, import the `RequestValidationError` and use it with `@app.except
|
|||
|
||||
The exception handler will receive a `Request` and the exception.
|
||||
|
||||
{* ../../docs_src/handling_errors/tutorial004.py hl[2,14:19] *}
|
||||
{* ../../docs_src/handling_errors/tutorial004_py39.py hl[2,14:19] *}
|
||||
|
||||
Now, if you go to `/items/foo`, instead of getting the default JSON error with:
|
||||
|
||||
|
|
@ -159,7 +159,7 @@ The same way, you can override the `HTTPException` handler.
|
|||
|
||||
For example, you could want to return a plain text response instead of JSON for these errors:
|
||||
|
||||
{* ../../docs_src/handling_errors/tutorial004.py hl[3:4,9:11,25] *}
|
||||
{* ../../docs_src/handling_errors/tutorial004_py39.py hl[3:4,9:11,25] *}
|
||||
|
||||
/// note | Technical Details
|
||||
|
||||
|
|
@ -183,7 +183,7 @@ The `RequestValidationError` contains the `body` it received with invalid data.
|
|||
|
||||
You could use it while developing your app to log the body and debug it, return it to the user, etc.
|
||||
|
||||
{* ../../docs_src/handling_errors/tutorial005.py hl[14] *}
|
||||
{* ../../docs_src/handling_errors/tutorial005_py39.py hl[14] *}
|
||||
|
||||
Now try sending an invalid item like:
|
||||
|
||||
|
|
@ -239,6 +239,6 @@ from starlette.exceptions import HTTPException as StarletteHTTPException
|
|||
|
||||
If you want to use the exception along with the same default exception handlers from **FastAPI**, you can import and reuse the default exception handlers from `fastapi.exception_handlers`:
|
||||
|
||||
{* ../../docs_src/handling_errors/tutorial006.py hl[2:5,15,21] *}
|
||||
{* ../../docs_src/handling_errors/tutorial006_py39.py hl[2:5,15,21] *}
|
||||
|
||||
In this example you are just printing the error with a very expressive message, but you get the idea. You can use the exception and then just reuse the default exception handlers.
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ You can set the following fields that are used in the OpenAPI specification and
|
|||
|
||||
You can set them as follows:
|
||||
|
||||
{* ../../docs_src/metadata/tutorial001.py hl[3:16, 19:32] *}
|
||||
{* ../../docs_src/metadata/tutorial001_py39.py hl[3:16, 19:32] *}
|
||||
|
||||
/// tip
|
||||
|
||||
|
|
@ -36,7 +36,7 @@ Since OpenAPI 3.1.0 and FastAPI 0.99.0, you can also set the `license_info` with
|
|||
|
||||
For example:
|
||||
|
||||
{* ../../docs_src/metadata/tutorial001_1.py hl[31] *}
|
||||
{* ../../docs_src/metadata/tutorial001_1_py39.py hl[31] *}
|
||||
|
||||
## Metadata for tags { #metadata-for-tags }
|
||||
|
||||
|
|
@ -58,7 +58,7 @@ Let's try that in an example with tags for `users` and `items`.
|
|||
|
||||
Create metadata for your tags and pass it to the `openapi_tags` parameter:
|
||||
|
||||
{* ../../docs_src/metadata/tutorial004.py hl[3:16,18] *}
|
||||
{* ../../docs_src/metadata/tutorial004_py39.py hl[3:16,18] *}
|
||||
|
||||
Notice that you can use Markdown inside of the descriptions, for example "login" will be shown in bold (**login**) and "fancy" will be shown in italics (_fancy_).
|
||||
|
||||
|
|
@ -72,7 +72,7 @@ You don't have to add metadata for all the tags that you use.
|
|||
|
||||
Use the `tags` parameter with your *path operations* (and `APIRouter`s) to assign them to different tags:
|
||||
|
||||
{* ../../docs_src/metadata/tutorial004.py hl[21,26] *}
|
||||
{* ../../docs_src/metadata/tutorial004_py39.py hl[21,26] *}
|
||||
|
||||
/// info
|
||||
|
||||
|
|
@ -100,7 +100,7 @@ But you can configure it with the parameter `openapi_url`.
|
|||
|
||||
For example, to set it to be served at `/api/v1/openapi.json`:
|
||||
|
||||
{* ../../docs_src/metadata/tutorial002.py hl[3] *}
|
||||
{* ../../docs_src/metadata/tutorial002_py39.py hl[3] *}
|
||||
|
||||
If you want to disable the OpenAPI schema completely you can set `openapi_url=None`, that will also disable the documentation user interfaces that use it.
|
||||
|
||||
|
|
@ -117,4 +117,4 @@ You can configure the two documentation user interfaces included:
|
|||
|
||||
For example, to set Swagger UI to be served at `/documentation` and disable ReDoc:
|
||||
|
||||
{* ../../docs_src/metadata/tutorial003.py hl[3] *}
|
||||
{* ../../docs_src/metadata/tutorial003_py39.py hl[3] *}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ The middleware function receives:
|
|||
* Then it returns the `response` generated by the corresponding *path operation*.
|
||||
* You can then further modify the `response` before returning it.
|
||||
|
||||
{* ../../docs_src/middleware/tutorial001.py hl[8:9,11,14] *}
|
||||
{* ../../docs_src/middleware/tutorial001_py39.py hl[8:9,11,14] *}
|
||||
|
||||
/// tip
|
||||
|
||||
|
|
@ -57,7 +57,7 @@ And also after the `response` is generated, before returning it.
|
|||
|
||||
For example, you could add a custom header `X-Process-Time` containing the time in seconds that it took to process the request and generate a response:
|
||||
|
||||
{* ../../docs_src/middleware/tutorial001.py hl[10,12:13] *}
|
||||
{* ../../docs_src/middleware/tutorial001_py39.py hl[10,12:13] *}
|
||||
|
||||
/// tip
|
||||
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ In these cases, it could make sense to store the tags in an `Enum`.
|
|||
|
||||
**FastAPI** supports that the same way as with plain strings:
|
||||
|
||||
{* ../../docs_src/path_operation_configuration/tutorial002b.py hl[1,8:10,13,18] *}
|
||||
{* ../../docs_src/path_operation_configuration/tutorial002b_py39.py hl[1,8:10,13,18] *}
|
||||
|
||||
## Summary and description { #summary-and-description }
|
||||
|
||||
|
|
@ -92,7 +92,7 @@ So, if you don't provide one, **FastAPI** will automatically generate one of "Su
|
|||
|
||||
If you need to mark a *path operation* as <abbr title="obsolete, recommended not to use it">deprecated</abbr>, but without removing it, pass the parameter `deprecated`:
|
||||
|
||||
{* ../../docs_src/path_operation_configuration/tutorial006.py hl[16] *}
|
||||
{* ../../docs_src/path_operation_configuration/tutorial006_py39.py hl[16] *}
|
||||
|
||||
It will be clearly marked as deprecated in the interactive docs:
|
||||
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ It doesn't matter for **FastAPI**. It will detect the parameters by their names,
|
|||
|
||||
So, you can declare your function as:
|
||||
|
||||
{* ../../docs_src/path_params_numeric_validations/tutorial002.py hl[7] *}
|
||||
{* ../../docs_src/path_params_numeric_validations/tutorial002_py39.py hl[7] *}
|
||||
|
||||
But keep in mind that if you use `Annotated`, you won't have this problem, it won't matter as you're not using the function parameter default values for `Query()` or `Path()`.
|
||||
|
||||
|
|
@ -83,7 +83,7 @@ Pass `*`, as the first parameter of the function.
|
|||
|
||||
Python won't do anything with that `*`, but it will know that all the following parameters should be called as keyword arguments (key-value pairs), also known as <abbr title="From: K-ey W-ord Arg-uments"><code>kwargs</code></abbr>. Even if they don't have a default value.
|
||||
|
||||
{* ../../docs_src/path_params_numeric_validations/tutorial003.py hl[7] *}
|
||||
{* ../../docs_src/path_params_numeric_validations/tutorial003_py39.py hl[7] *}
|
||||
|
||||
### Better with `Annotated` { #better-with-annotated }
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
You can declare path "parameters" or "variables" with the same syntax used by Python format strings:
|
||||
|
||||
{* ../../docs_src/path_params/tutorial001.py hl[6:7] *}
|
||||
{* ../../docs_src/path_params/tutorial001_py39.py hl[6:7] *}
|
||||
|
||||
The value of the path parameter `item_id` will be passed to your function as the argument `item_id`.
|
||||
|
||||
|
|
@ -16,7 +16,7 @@ So, if you run this example and go to <a href="http://127.0.0.1:8000/items/foo"
|
|||
|
||||
You can declare the type of a path parameter in the function, using standard Python type annotations:
|
||||
|
||||
{* ../../docs_src/path_params/tutorial002.py hl[7] *}
|
||||
{* ../../docs_src/path_params/tutorial002_py39.py hl[7] *}
|
||||
|
||||
In this case, `item_id` is declared to be an `int`.
|
||||
|
||||
|
|
@ -118,13 +118,13 @@ And then you can also have a path `/users/{user_id}` to get data about a specifi
|
|||
|
||||
Because *path operations* are evaluated in order, you need to make sure that the path for `/users/me` is declared before the one for `/users/{user_id}`:
|
||||
|
||||
{* ../../docs_src/path_params/tutorial003.py hl[6,11] *}
|
||||
{* ../../docs_src/path_params/tutorial003_py39.py hl[6,11] *}
|
||||
|
||||
Otherwise, the path for `/users/{user_id}` would match also for `/users/me`, "thinking" that it's receiving a parameter `user_id` with a value of `"me"`.
|
||||
|
||||
Similarly, you cannot redefine a path operation:
|
||||
|
||||
{* ../../docs_src/path_params/tutorial003b.py hl[6,11] *}
|
||||
{* ../../docs_src/path_params/tutorial003b_py39.py hl[6,11] *}
|
||||
|
||||
The first one will always be used since the path matches first.
|
||||
|
||||
|
|
@ -140,7 +140,7 @@ By inheriting from `str` the API docs will be able to know that the values must
|
|||
|
||||
Then create class attributes with fixed values, which will be the available valid values:
|
||||
|
||||
{* ../../docs_src/path_params/tutorial005.py hl[1,6:9] *}
|
||||
{* ../../docs_src/path_params/tutorial005_py39.py hl[1,6:9] *}
|
||||
|
||||
/// info
|
||||
|
||||
|
|
@ -158,7 +158,7 @@ If you are wondering, "AlexNet", "ResNet", and "LeNet" are just names of Machine
|
|||
|
||||
Then create a *path parameter* with a type annotation using the enum class you created (`ModelName`):
|
||||
|
||||
{* ../../docs_src/path_params/tutorial005.py hl[16] *}
|
||||
{* ../../docs_src/path_params/tutorial005_py39.py hl[16] *}
|
||||
|
||||
### Check the docs { #check-the-docs }
|
||||
|
||||
|
|
@ -174,13 +174,13 @@ The value of the *path parameter* will be an *enumeration member*.
|
|||
|
||||
You can compare it with the *enumeration member* in your created enum `ModelName`:
|
||||
|
||||
{* ../../docs_src/path_params/tutorial005.py hl[17] *}
|
||||
{* ../../docs_src/path_params/tutorial005_py39.py hl[17] *}
|
||||
|
||||
#### Get the *enumeration value* { #get-the-enumeration-value }
|
||||
|
||||
You can get the actual value (a `str` in this case) using `model_name.value`, or in general, `your_enum_member.value`:
|
||||
|
||||
{* ../../docs_src/path_params/tutorial005.py hl[20] *}
|
||||
{* ../../docs_src/path_params/tutorial005_py39.py hl[20] *}
|
||||
|
||||
/// tip
|
||||
|
||||
|
|
@ -194,7 +194,7 @@ You can return *enum members* from your *path operation*, even nested in a JSON
|
|||
|
||||
They will be converted to their corresponding values (strings in this case) before returning them to the client:
|
||||
|
||||
{* ../../docs_src/path_params/tutorial005.py hl[18,21,23] *}
|
||||
{* ../../docs_src/path_params/tutorial005_py39.py hl[18,21,23] *}
|
||||
|
||||
In your client you will get a JSON response like:
|
||||
|
||||
|
|
@ -233,7 +233,7 @@ In this case, the name of the parameter is `file_path`, and the last part, `:pat
|
|||
|
||||
So, you can use it with:
|
||||
|
||||
{* ../../docs_src/path_params/tutorial004.py hl[6] *}
|
||||
{* ../../docs_src/path_params/tutorial004_py39.py hl[6] *}
|
||||
|
||||
/// tip
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
When you declare other function parameters that are not part of the path parameters, they are automatically interpreted as "query" parameters.
|
||||
|
||||
{* ../../docs_src/query_params/tutorial001.py hl[9] *}
|
||||
{* ../../docs_src/query_params/tutorial001_py39.py hl[9] *}
|
||||
|
||||
The query is the set of key-value pairs that go after the `?` in a URL, separated by `&` characters.
|
||||
|
||||
|
|
@ -128,7 +128,7 @@ If you don't want to add a specific value but just make it optional, set the def
|
|||
|
||||
But when you want to make a query parameter required, you can just not declare any default value:
|
||||
|
||||
{* ../../docs_src/query_params/tutorial005.py hl[6:7] *}
|
||||
{* ../../docs_src/query_params/tutorial005_py39.py hl[6:7] *}
|
||||
|
||||
Here the query parameter `needy` is a required query parameter of type `str`.
|
||||
|
||||
|
|
|
|||
|
|
@ -183,7 +183,7 @@ There might be cases where you return something that is not a valid Pydantic fie
|
|||
|
||||
The most common case would be [returning a Response directly as explained later in the advanced docs](../advanced/response-directly.md){.internal-link target=_blank}.
|
||||
|
||||
{* ../../docs_src/response_model/tutorial003_02.py hl[8,10:11] *}
|
||||
{* ../../docs_src/response_model/tutorial003_02_py39.py hl[8,10:11] *}
|
||||
|
||||
This simple case is handled automatically by FastAPI because the return type annotation is the class (or a subclass of) `Response`.
|
||||
|
||||
|
|
@ -193,7 +193,7 @@ And tools will also be happy because both `RedirectResponse` and `JSONResponse`
|
|||
|
||||
You can also use a subclass of `Response` in the type annotation:
|
||||
|
||||
{* ../../docs_src/response_model/tutorial003_03.py hl[8:9] *}
|
||||
{* ../../docs_src/response_model/tutorial003_03_py39.py hl[8:9] *}
|
||||
|
||||
This will also work because `RedirectResponse` is a subclass of `Response`, and FastAPI will automatically handle this simple case.
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ The same way you can specify a response model, you can also declare the HTTP sta
|
|||
* `@app.delete()`
|
||||
* etc.
|
||||
|
||||
{* ../../docs_src/response_status_code/tutorial001.py hl[6] *}
|
||||
{* ../../docs_src/response_status_code/tutorial001_py39.py hl[6] *}
|
||||
|
||||
/// note
|
||||
|
||||
|
|
@ -74,7 +74,7 @@ To know more about each status code and which code is for what, check the <a hre
|
|||
|
||||
Let's see the previous example again:
|
||||
|
||||
{* ../../docs_src/response_status_code/tutorial001.py hl[6] *}
|
||||
{* ../../docs_src/response_status_code/tutorial001_py39.py hl[6] *}
|
||||
|
||||
`201` is the status code for "Created".
|
||||
|
||||
|
|
@ -82,7 +82,7 @@ But you don't have to memorize what each of these codes mean.
|
|||
|
||||
You can use the convenience variables from `fastapi.status`.
|
||||
|
||||
{* ../../docs_src/response_status_code/tutorial002.py hl[1,6] *}
|
||||
{* ../../docs_src/response_status_code/tutorial002_py39.py hl[1,6] *}
|
||||
|
||||
They are just a convenience, they hold the same number, but that way you can use the editor's autocomplete to find them:
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ You can serve static files automatically from a directory using `StaticFiles`.
|
|||
* Import `StaticFiles`.
|
||||
* "Mount" a `StaticFiles()` instance in a specific path.
|
||||
|
||||
{* ../../docs_src/static_files/tutorial001.py hl[2,6] *}
|
||||
{* ../../docs_src/static_files/tutorial001_py39.py hl[2,6] *}
|
||||
|
||||
/// note | Technical Details
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ Use the `TestClient` object the same way as you do with `httpx`.
|
|||
|
||||
Write simple `assert` statements with the standard Python expressions that you need to check (again, standard `pytest`).
|
||||
|
||||
{* ../../docs_src/app_testing/tutorial001.py hl[2,12,15:18] *}
|
||||
{* ../../docs_src/app_testing/tutorial001_py39.py hl[2,12,15:18] *}
|
||||
|
||||
/// tip
|
||||
|
||||
|
|
@ -76,7 +76,7 @@ Let's say you have a file structure as described in [Bigger Applications](bigger
|
|||
In the file `main.py` you have your **FastAPI** app:
|
||||
|
||||
|
||||
{* ../../docs_src/app_testing/main.py *}
|
||||
{* ../../docs_src/app_testing/app_a_py39/main.py *}
|
||||
|
||||
### Testing file { #testing-file }
|
||||
|
||||
|
|
@ -92,7 +92,7 @@ Then you could have a file `test_main.py` with your tests. It could live on the
|
|||
|
||||
Because this file is in the same package, you can use relative imports to import the object `app` from the `main` module (`main.py`):
|
||||
|
||||
{* ../../docs_src/app_testing/test_main.py hl[3] *}
|
||||
{* ../../docs_src/app_testing/app_a_py39/test_main.py hl[3] *}
|
||||
|
||||
|
||||
...and have the code for the tests just like before.
|
||||
|
|
|
|||
|
|
@ -1,26 +0,0 @@
|
|||
from typing import Union
|
||||
|
||||
from fastapi import Body, FastAPI, status
|
||||
from fastapi.responses import JSONResponse
|
||||
from typing_extensions import Annotated
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
items = {"foo": {"name": "Fighters", "size": 6}, "bar": {"name": "Tenders", "size": 3}}
|
||||
|
||||
|
||||
@app.put("/items/{item_id}")
|
||||
async def upsert_item(
|
||||
item_id: str,
|
||||
name: Annotated[Union[str, None], Body()] = None,
|
||||
size: Annotated[Union[int, None], Body()] = None,
|
||||
):
|
||||
if item_id in items:
|
||||
item = items[item_id]
|
||||
item["name"] = name
|
||||
item["size"] = size
|
||||
return item
|
||||
else:
|
||||
item = {"name": name, "size": size}
|
||||
items[item_id] = item
|
||||
return JSONResponse(status_code=status.HTTP_201_CREATED, content=item)
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
from typing import Union
|
||||
|
||||
from fastapi import FastAPI, Header, HTTPException
|
||||
from pydantic import BaseModel
|
||||
from typing_extensions import Annotated
|
||||
|
||||
fake_secret_token = "coneofsilence"
|
||||
|
||||
fake_db = {
|
||||
"foo": {"id": "foo", "title": "Foo", "description": "There goes my hero"},
|
||||
"bar": {"id": "bar", "title": "Bar", "description": "The bartenders"},
|
||||
}
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
class Item(BaseModel):
|
||||
id: str
|
||||
title: str
|
||||
description: Union[str, None] = None
|
||||
|
||||
|
||||
@app.get("/items/{item_id}", response_model=Item)
|
||||
async def read_main(item_id: str, x_token: Annotated[str, Header()]):
|
||||
if x_token != fake_secret_token:
|
||||
raise HTTPException(status_code=400, detail="Invalid X-Token header")
|
||||
if item_id not in fake_db:
|
||||
raise HTTPException(status_code=404, detail="Item not found")
|
||||
return fake_db[item_id]
|
||||
|
||||
|
||||
@app.post("/items/", response_model=Item)
|
||||
async def create_item(item: Item, x_token: Annotated[str, Header()]):
|
||||
if x_token != fake_secret_token:
|
||||
raise HTTPException(status_code=400, detail="Invalid X-Token header")
|
||||
if item.id in fake_db:
|
||||
raise HTTPException(status_code=409, detail="Item already exists")
|
||||
fake_db[item.id] = item
|
||||
return item
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
from fastapi.testclient import TestClient
|
||||
|
||||
from .main import app
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
def test_read_item():
|
||||
response = client.get("/items/foo", headers={"X-Token": "coneofsilence"})
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"id": "foo",
|
||||
"title": "Foo",
|
||||
"description": "There goes my hero",
|
||||
}
|
||||
|
||||
|
||||
def test_read_item_bad_token():
|
||||
response = client.get("/items/foo", headers={"X-Token": "hailhydra"})
|
||||
assert response.status_code == 400
|
||||
assert response.json() == {"detail": "Invalid X-Token header"}
|
||||
|
||||
|
||||
def test_read_nonexistent_item():
|
||||
response = client.get("/items/baz", headers={"X-Token": "coneofsilence"})
|
||||
assert response.status_code == 404
|
||||
assert response.json() == {"detail": "Item not found"}
|
||||
|
||||
|
||||
def test_create_item():
|
||||
response = client.post(
|
||||
"/items/",
|
||||
headers={"X-Token": "coneofsilence"},
|
||||
json={"id": "foobar", "title": "Foo Bar", "description": "The Foo Barters"},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"id": "foobar",
|
||||
"title": "Foo Bar",
|
||||
"description": "The Foo Barters",
|
||||
}
|
||||
|
||||
|
||||
def test_create_item_bad_token():
|
||||
response = client.post(
|
||||
"/items/",
|
||||
headers={"X-Token": "hailhydra"},
|
||||
json={"id": "bazz", "title": "Bazz", "description": "Drop the bazz"},
|
||||
)
|
||||
assert response.status_code == 400
|
||||
assert response.json() == {"detail": "Invalid X-Token header"}
|
||||
|
||||
|
||||
def test_create_existing_item():
|
||||
response = client.post(
|
||||
"/items/",
|
||||
headers={"X-Token": "coneofsilence"},
|
||||
json={
|
||||
"id": "foo",
|
||||
"title": "The Foo ID Stealers",
|
||||
"description": "There goes my stealer",
|
||||
},
|
||||
)
|
||||
assert response.status_code == 409
|
||||
assert response.json() == {"detail": "Item already exists"}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
from fastapi import Depends, FastAPI, HTTPException, status
|
||||
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
||||
from typing_extensions import Annotated
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
class HTTPBearer403(HTTPBearer):
|
||||
def make_not_authenticated_error(self) -> HTTPException:
|
||||
return HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN, detail="Not authenticated"
|
||||
)
|
||||
|
||||
|
||||
CredentialsDep = Annotated[HTTPAuthorizationCredentials, Depends(HTTPBearer403())]
|
||||
|
||||
|
||||
@app.get("/me")
|
||||
def read_me(credentials: CredentialsDep):
|
||||
return {"message": "You are authenticated", "token": credentials.credentials}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
from typing import Union
|
||||
|
||||
from fastapi import BackgroundTasks, Depends, FastAPI
|
||||
from typing_extensions import Annotated
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
def write_log(message: str):
|
||||
with open("log.txt", mode="a") as log:
|
||||
log.write(message)
|
||||
|
||||
|
||||
def get_query(background_tasks: BackgroundTasks, q: Union[str, None] = None):
|
||||
if q:
|
||||
message = f"found query: {q}\n"
|
||||
background_tasks.add_task(write_log, message)
|
||||
return q
|
||||
|
||||
|
||||
@app.post("/send-notification/{email}")
|
||||
async def send_notification(
|
||||
email: str, background_tasks: BackgroundTasks, q: Annotated[str, Depends(get_query)]
|
||||
):
|
||||
message = f"message to {email}\n"
|
||||
background_tasks.add_task(write_log, message)
|
||||
return {"message": "Message sent"}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
from fastapi import Header, HTTPException
|
||||
from typing_extensions import Annotated
|
||||
|
||||
|
||||
async def get_token_header(x_token: Annotated[str, Header()]):
|
||||
if x_token != "fake-super-secret-token":
|
||||
raise HTTPException(status_code=400, detail="X-Token header invalid")
|
||||
|
||||
|
||||
async def get_query_token(token: str):
|
||||
if token != "jessica":
|
||||
raise HTTPException(status_code=400, detail="No Jessica token provided")
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
from fastapi import APIRouter
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/")
|
||||
async def update_admin():
|
||||
return {"message": "Admin getting schwifty"}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
from fastapi import Depends, FastAPI
|
||||
|
||||
from .dependencies import get_query_token, get_token_header
|
||||
from .internal import admin
|
||||
from .routers import items, users
|
||||
|
||||
app = FastAPI(dependencies=[Depends(get_query_token)])
|
||||
|
||||
|
||||
app.include_router(users.router)
|
||||
app.include_router(items.router)
|
||||
app.include_router(
|
||||
admin.router,
|
||||
prefix="/admin",
|
||||
tags=["admin"],
|
||||
dependencies=[Depends(get_token_header)],
|
||||
responses={418: {"description": "I'm a teapot"}},
|
||||
)
|
||||
|
||||
|
||||
@app.get("/")
|
||||
async def root():
|
||||
return {"message": "Hello Bigger Applications!"}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
from fastapi import APIRouter, Depends, HTTPException
|
||||
|
||||
from ..dependencies import get_token_header
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/items",
|
||||
tags=["items"],
|
||||
dependencies=[Depends(get_token_header)],
|
||||
responses={404: {"description": "Not found"}},
|
||||
)
|
||||
|
||||
|
||||
fake_items_db = {"plumbus": {"name": "Plumbus"}, "gun": {"name": "Portal Gun"}}
|
||||
|
||||
|
||||
@router.get("/")
|
||||
async def read_items():
|
||||
return fake_items_db
|
||||
|
||||
|
||||
@router.get("/{item_id}")
|
||||
async def read_item(item_id: str):
|
||||
if item_id not in fake_items_db:
|
||||
raise HTTPException(status_code=404, detail="Item not found")
|
||||
return {"name": fake_items_db[item_id]["name"], "item_id": item_id}
|
||||
|
||||
|
||||
@router.put(
|
||||
"/{item_id}",
|
||||
tags=["custom"],
|
||||
responses={403: {"description": "Operation forbidden"}},
|
||||
)
|
||||
async def update_item(item_id: str):
|
||||
if item_id != "plumbus":
|
||||
raise HTTPException(
|
||||
status_code=403, detail="You can only update the item: plumbus"
|
||||
)
|
||||
return {"item_id": item_id, "name": "The great Plumbus"}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
from fastapi import APIRouter
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/users/", tags=["users"])
|
||||
async def read_users():
|
||||
return [{"username": "Rick"}, {"username": "Morty"}]
|
||||
|
||||
|
||||
@router.get("/users/me", tags=["users"])
|
||||
async def read_user_me():
|
||||
return {"username": "fakecurrentuser"}
|
||||
|
||||
|
||||
@router.get("/users/{username}", tags=["users"])
|
||||
async def read_user(username: str):
|
||||
return {"username": username}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
from typing import Union
|
||||
|
||||
from fastapi import Body, FastAPI
|
||||
from pydantic import BaseModel, Field
|
||||
from typing_extensions import Annotated
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
class Item(BaseModel):
|
||||
name: str
|
||||
description: Union[str, None] = Field(
|
||||
default=None, title="The description of the item", max_length=300
|
||||
)
|
||||
price: float = Field(gt=0, description="The price must be greater than zero")
|
||||
tax: Union[float, None] = None
|
||||
|
||||
|
||||
@app.put("/items/{item_id}")
|
||||
async def update_item(item_id: int, item: Annotated[Item, Body(embed=True)]):
|
||||
results = {"item_id": item_id, "item": item}
|
||||
return results
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
from typing import Union
|
||||
|
||||
from fastapi import FastAPI, Path
|
||||
from pydantic import BaseModel
|
||||
from typing_extensions import Annotated
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
class Item(BaseModel):
|
||||
name: str
|
||||
description: Union[str, None] = None
|
||||
price: float
|
||||
tax: Union[float, None] = None
|
||||
|
||||
|
||||
@app.put("/items/{item_id}")
|
||||
async def update_item(
|
||||
item_id: Annotated[int, Path(title="The ID of the item to get", ge=0, le=1000)],
|
||||
q: Union[str, None] = None,
|
||||
item: Union[Item, None] = None,
|
||||
):
|
||||
results = {"item_id": item_id}
|
||||
if q:
|
||||
results.update({"q": q})
|
||||
if item:
|
||||
results.update({"item": item})
|
||||
return results
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue