mirror of https://github.com/tiangolo/fastapi.git
📝 Add variants for code examples in "Advanced User Guide" (#14413)
This commit is contained in:
parent
5b28a04d55
commit
9475024640
|
|
@ -175,7 +175,7 @@ You can use this same `responses` parameter to add different media types for the
|
|||
|
||||
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] *}
|
||||
{* ../../docs_src/additional_responses/tutorial002_py310.py hl[17:22,26] *}
|
||||
|
||||
/// note
|
||||
|
||||
|
|
@ -237,7 +237,7 @@ You can use that technique to reuse some predefined responses in your *path oper
|
|||
|
||||
For example:
|
||||
|
||||
{* ../../docs_src/additional_responses/tutorial004.py hl[13:17,26] *}
|
||||
{* ../../docs_src/additional_responses/tutorial004_py310.py hl[11:15,24] *}
|
||||
|
||||
## More information about OpenAPI responses { #more-information-about-openapi-responses }
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ FastAPI is built on top of **Pydantic**, and I have been showing you how to use
|
|||
|
||||
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] *}
|
||||
{* ../../docs_src/dataclasses/tutorial001_py310.py hl[1,6:11,18:19] *}
|
||||
|
||||
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>.
|
||||
|
||||
|
|
@ -32,7 +32,7 @@ But if you have a bunch of dataclasses laying around, this is a nice trick to us
|
|||
|
||||
You can also use `dataclasses` in the `response_model` parameter:
|
||||
|
||||
{* ../../docs_src/dataclasses/tutorial002.py hl[1,7:13,19] *}
|
||||
{* ../../docs_src/dataclasses/tutorial002_py310.py hl[1,6:12,18] *}
|
||||
|
||||
The dataclass will be automatically converted to a Pydantic dataclass.
|
||||
|
||||
|
|
@ -48,7 +48,7 @@ In some cases, you might still have to use Pydantic's version of `dataclasses`.
|
|||
|
||||
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] *}
|
||||
{* ../../docs_src/dataclasses/tutorial003_py310.py hl[1,4,7:10,13:16,22:24,27] *}
|
||||
|
||||
1. We still import `field` from standard `dataclasses`.
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ It will have a *path operation* that will receive an `Invoice` body, and a query
|
|||
|
||||
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] *}
|
||||
{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[7:11,34:51] *}
|
||||
|
||||
/// tip
|
||||
|
||||
|
|
@ -90,7 +90,7 @@ Temporarily adopting this point of view (of the *external developer*) can help y
|
|||
|
||||
First create a new `APIRouter` that will contain one or more callbacks.
|
||||
|
||||
{* ../../docs_src/openapi_callbacks/tutorial001.py hl[3,25] *}
|
||||
{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[1,23] *}
|
||||
|
||||
### Create the callback *path operation* { #create-the-callback-path-operation }
|
||||
|
||||
|
|
@ -101,7 +101,7 @@ 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] *}
|
||||
{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[14:16,19:20,26:30] *}
|
||||
|
||||
There are 2 main differences from a normal *path operation*:
|
||||
|
||||
|
|
@ -169,7 +169,7 @@ At this point you have the *callback path operation(s)* needed (the one(s) that
|
|||
|
||||
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] *}
|
||||
{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[33] *}
|
||||
|
||||
/// tip
|
||||
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ Adding an `\f` (an escaped "form feed" character) causes **FastAPI** to truncate
|
|||
|
||||
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] *}
|
||||
{* ../../docs_src/path_operation_advanced_configuration/tutorial004_py310.py hl[17:27] *}
|
||||
|
||||
## Additional Responses { #additional-responses }
|
||||
|
||||
|
|
@ -155,13 +155,13 @@ For example, in this application we don't use FastAPI's integrated functionality
|
|||
|
||||
//// tab | Pydantic v2
|
||||
|
||||
{* ../../docs_src/path_operation_advanced_configuration/tutorial007.py hl[17:22, 24] *}
|
||||
{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[15:20, 22] *}
|
||||
|
||||
////
|
||||
|
||||
//// tab | Pydantic v1
|
||||
|
||||
{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1.py hl[17:22, 24] *}
|
||||
{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py hl[15:20, 22] *}
|
||||
|
||||
////
|
||||
|
||||
|
|
@ -179,13 +179,13 @@ And then in our code, we parse that YAML content directly, and then we are again
|
|||
|
||||
//// tab | Pydantic v2
|
||||
|
||||
{* ../../docs_src/path_operation_advanced_configuration/tutorial007.py hl[26:33] *}
|
||||
{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[24:31] *}
|
||||
|
||||
////
|
||||
|
||||
//// tab | Pydantic v1
|
||||
|
||||
{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1.py hl[26:33] *}
|
||||
{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py hl[24:31] *}
|
||||
|
||||
////
|
||||
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ For example, you cannot put a Pydantic model in a `JSONResponse` without first c
|
|||
|
||||
For those cases, you can use the `jsonable_encoder` to convert your data before passing it to a response:
|
||||
|
||||
{* ../../docs_src/response_directly/tutorial001.py hl[6:7,21:22] *}
|
||||
{* ../../docs_src/response_directly/tutorial001_py310.py hl[5:6,20:21] *}
|
||||
|
||||
/// note | Technical Details
|
||||
|
||||
|
|
|
|||
|
|
@ -148,7 +148,7 @@ This could be especially useful during testing, as it's very easy to override a
|
|||
|
||||
Coming from the previous example, your `config.py` file could look like:
|
||||
|
||||
{* ../../docs_src/settings/app02/config.py hl[10] *}
|
||||
{* ../../docs_src/settings/app02_an_py39/config.py hl[10] *}
|
||||
|
||||
Notice that now we don't create a default instance `settings = Settings()`.
|
||||
|
||||
|
|
@ -174,7 +174,7 @@ And then we can require it from the *path operation function* as a dependency an
|
|||
|
||||
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] *}
|
||||
{* ../../docs_src/settings/app02_an_py39/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.
|
||||
|
||||
|
|
@ -217,7 +217,7 @@ And then update your `config.py` with:
|
|||
|
||||
//// tab | Pydantic v2
|
||||
|
||||
{* ../../docs_src/settings/app03_an/config.py hl[9] *}
|
||||
{* ../../docs_src/settings/app03_an_py39/config.py hl[9] *}
|
||||
|
||||
/// tip
|
||||
|
||||
|
|
@ -229,7 +229,7 @@ The `model_config` attribute is used just for Pydantic configuration. You can re
|
|||
|
||||
//// tab | Pydantic v1
|
||||
|
||||
{* ../../docs_src/settings/app03_an/config_pv1.py hl[9:10] *}
|
||||
{* ../../docs_src/settings/app03_an_py39/config_pv1.py hl[9:10] *}
|
||||
|
||||
/// tip
|
||||
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ FastAPI includes some default configuration parameters appropriate for most of t
|
|||
|
||||
It includes these default configurations:
|
||||
|
||||
{* ../../fastapi/openapi/docs.py ln[8:23] hl[17:23] *}
|
||||
{* ../../fastapi/openapi/docs.py ln[9:24] hl[18:24] *}
|
||||
|
||||
You can override any of them by setting a different value in the argument `swagger_ui_parameters`.
|
||||
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ 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] *}
|
||||
{* ../../docs_src/custom_request_and_route/tutorial001_an_py310.py hl[9:16] *}
|
||||
|
||||
### Create a custom `GzipRoute` class { #create-a-custom-gziproute-class }
|
||||
|
||||
|
|
@ -54,7 +54,7 @@ This method returns a function. And that function is what will receive a request
|
|||
|
||||
Here we use it to create a `GzipRequest` from the original request.
|
||||
|
||||
{* ../../docs_src/custom_request_and_route/tutorial001.py hl[18:26] *}
|
||||
{* ../../docs_src/custom_request_and_route/tutorial001_an_py310.py hl[19:27] *}
|
||||
|
||||
/// note | Technical Details
|
||||
|
||||
|
|
@ -92,18 +92,18 @@ We can also use this same approach to access the request body in an exception ha
|
|||
|
||||
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] *}
|
||||
{* ../../docs_src/custom_request_and_route/tutorial002_an_py310.py hl[14,16] *}
|
||||
|
||||
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] *}
|
||||
{* ../../docs_src/custom_request_and_route/tutorial002_an_py310.py hl[17:19] *}
|
||||
|
||||
## 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] *}
|
||||
{* ../../docs_src/custom_request_and_route/tutorial003_py310.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] *}
|
||||
{* ../../docs_src/custom_request_and_route/tutorial003_py310.py hl[13:20] *}
|
||||
|
|
|
|||
|
|
@ -85,9 +85,7 @@ You can create the *path operations* for that module using `APIRouter`.
|
|||
|
||||
You import it and create an "instance" the same way you would with the class `FastAPI`:
|
||||
|
||||
```Python hl_lines="1 3" title="app/routers/users.py"
|
||||
{!../../docs_src/bigger_applications/app/routers/users.py!}
|
||||
```
|
||||
{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[1,3] title["app/routers/users.py"] *}
|
||||
|
||||
### *Path operations* with `APIRouter` { #path-operations-with-apirouter }
|
||||
|
||||
|
|
@ -95,9 +93,7 @@ And then you use it to declare your *path operations*.
|
|||
|
||||
Use it the same way you would use the `FastAPI` class:
|
||||
|
||||
```Python hl_lines="6 11 16" title="app/routers/users.py"
|
||||
{!../../docs_src/bigger_applications/app/routers/users.py!}
|
||||
```
|
||||
{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[6,11,16] title["app/routers/users.py"] *}
|
||||
|
||||
You can think of `APIRouter` as a "mini `FastAPI`" class.
|
||||
|
||||
|
|
@ -121,35 +117,7 @@ So we put them in their own `dependencies` module (`app/dependencies.py`).
|
|||
|
||||
We will now use a simple dependency to read a custom `X-Token` header:
|
||||
|
||||
//// tab | Python 3.9+
|
||||
|
||||
```Python hl_lines="3 6-8" title="app/dependencies.py"
|
||||
{!> ../../docs_src/bigger_applications/app_an_py39/dependencies.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.8+
|
||||
|
||||
```Python hl_lines="1 5-7" title="app/dependencies.py"
|
||||
{!> ../../docs_src/bigger_applications/app_an/dependencies.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.8+ non-Annotated
|
||||
|
||||
/// tip
|
||||
|
||||
Prefer to use the `Annotated` version if possible.
|
||||
|
||||
///
|
||||
|
||||
```Python hl_lines="1 4-6" title="app/dependencies.py"
|
||||
{!> ../../docs_src/bigger_applications/app/dependencies.py!}
|
||||
```
|
||||
|
||||
////
|
||||
{* ../../docs_src/bigger_applications/app_an_py39/dependencies.py hl[3,6:8] title["app/dependencies.py"] *}
|
||||
|
||||
/// tip
|
||||
|
||||
|
|
@ -181,9 +149,7 @@ We know all the *path operations* in this module have the same:
|
|||
|
||||
So, instead of adding all that to each *path operation*, we can add it to the `APIRouter`.
|
||||
|
||||
```Python hl_lines="5-10 16 21" title="app/routers/items.py"
|
||||
{!../../docs_src/bigger_applications/app/routers/items.py!}
|
||||
```
|
||||
{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[5:10,16,21] title["app/routers/items.py"] *}
|
||||
|
||||
As the path of each *path operation* has to start with `/`, like in:
|
||||
|
||||
|
|
@ -242,9 +208,7 @@ And we need to get the dependency function from the module `app.dependencies`, t
|
|||
|
||||
So we use a relative import with `..` for the dependencies:
|
||||
|
||||
```Python hl_lines="3" title="app/routers/items.py"
|
||||
{!../../docs_src/bigger_applications/app/routers/items.py!}
|
||||
```
|
||||
{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[3] title["app/routers/items.py"] *}
|
||||
|
||||
#### How relative imports work { #how-relative-imports-work }
|
||||
|
||||
|
|
@ -315,9 +279,7 @@ We are not adding the prefix `/items` nor the `tags=["items"]` to each *path ope
|
|||
|
||||
But we can still add _more_ `tags` that will be applied to a specific *path operation*, and also some extra `responses` specific to that *path operation*:
|
||||
|
||||
```Python hl_lines="30-31" title="app/routers/items.py"
|
||||
{!../../docs_src/bigger_applications/app/routers/items.py!}
|
||||
```
|
||||
{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[30:31] title["app/routers/items.py"] *}
|
||||
|
||||
/// tip
|
||||
|
||||
|
|
@ -343,17 +305,13 @@ You import and create a `FastAPI` class as normally.
|
|||
|
||||
And we can even declare [global dependencies](dependencies/global-dependencies.md){.internal-link target=_blank} that will be combined with the dependencies for each `APIRouter`:
|
||||
|
||||
```Python hl_lines="1 3 7" title="app/main.py"
|
||||
{!../../docs_src/bigger_applications/app/main.py!}
|
||||
```
|
||||
{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[1,3,7] title["app/main.py"] *}
|
||||
|
||||
### Import the `APIRouter` { #import-the-apirouter }
|
||||
|
||||
Now we import the other submodules that have `APIRouter`s:
|
||||
|
||||
```Python hl_lines="4-5" title="app/main.py"
|
||||
{!../../docs_src/bigger_applications/app/main.py!}
|
||||
```
|
||||
{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[4:5] title["app/main.py"] *}
|
||||
|
||||
As the files `app/routers/users.py` and `app/routers/items.py` are submodules that are part of the same Python package `app`, we can use a single dot `.` to import them using "relative imports".
|
||||
|
||||
|
|
@ -416,17 +374,13 @@ the `router` from `users` would overwrite the one from `items` and we wouldn't b
|
|||
|
||||
So, to be able to use both of them in the same file, we import the submodules directly:
|
||||
|
||||
```Python hl_lines="5" title="app/main.py"
|
||||
{!../../docs_src/bigger_applications/app/main.py!}
|
||||
```
|
||||
{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[5] title["app/main.py"] *}
|
||||
|
||||
### Include the `APIRouter`s for `users` and `items` { #include-the-apirouters-for-users-and-items }
|
||||
|
||||
Now, let's include the `router`s from the submodules `users` and `items`:
|
||||
|
||||
```Python hl_lines="10-11" title="app/main.py"
|
||||
{!../../docs_src/bigger_applications/app/main.py!}
|
||||
```
|
||||
{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[10:11] title["app/main.py"] *}
|
||||
|
||||
/// info
|
||||
|
||||
|
|
@ -466,17 +420,13 @@ It contains an `APIRouter` with some admin *path operations* that your organizat
|
|||
|
||||
For this example it will be super simple. But let's say that because it is shared with other projects in the organization, we cannot modify it and add a `prefix`, `dependencies`, `tags`, etc. directly to the `APIRouter`:
|
||||
|
||||
```Python hl_lines="3" title="app/internal/admin.py"
|
||||
{!../../docs_src/bigger_applications/app/internal/admin.py!}
|
||||
```
|
||||
{* ../../docs_src/bigger_applications/app_an_py39/internal/admin.py hl[3] title["app/internal/admin.py"] *}
|
||||
|
||||
But we still want to set a custom `prefix` when including the `APIRouter` so that all its *path operations* start with `/admin`, we want to secure it with the `dependencies` we already have for this project, and we want to include `tags` and `responses`.
|
||||
|
||||
We can declare all that without having to modify the original `APIRouter` by passing those parameters to `app.include_router()`:
|
||||
|
||||
```Python hl_lines="14-17" title="app/main.py"
|
||||
{!../../docs_src/bigger_applications/app/main.py!}
|
||||
```
|
||||
{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[14:17] title["app/main.py"] *}
|
||||
|
||||
That way, the original `APIRouter` will stay unmodified, so we can still share that same `app/internal/admin.py` file with other projects in the organization.
|
||||
|
||||
|
|
@ -497,9 +447,7 @@ We can also add *path operations* directly to the `FastAPI` app.
|
|||
|
||||
Here we do it... just to show that we can 🤷:
|
||||
|
||||
```Python hl_lines="21-23" title="app/main.py"
|
||||
{!../../docs_src/bigger_applications/app/main.py!}
|
||||
```
|
||||
{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[21:23] title["app/main.py"] *}
|
||||
|
||||
and it will work correctly, together with all the other *path operations* added with `app.include_router()`.
|
||||
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ Your API now has the power to control its own <abbr title="This is a joke, just
|
|||
|
||||
You can use Pydantic's model configuration to `forbid` any `extra` fields:
|
||||
|
||||
{* ../../docs_src/cookie_param_models/tutorial002_an_py39.py hl[10] *}
|
||||
{* ../../docs_src/cookie_param_models/tutorial002_an_py310.py hl[10] *}
|
||||
|
||||
If a client tries to send some **extra cookies**, they will receive an **error** response.
|
||||
|
||||
|
|
|
|||
|
|
@ -121,63 +121,13 @@ It has a `POST` operation that could return several errors.
|
|||
|
||||
Both *path operations* require an `X-Token` header.
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python
|
||||
{!> ../../docs_src/app_testing/app_b_an_py310/main.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.9+
|
||||
|
||||
```Python
|
||||
{!> ../../docs_src/app_testing/app_b_an_py39/main.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.8+
|
||||
|
||||
```Python
|
||||
{!> ../../docs_src/app_testing/app_b_an/main.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.10+ non-Annotated
|
||||
|
||||
/// tip
|
||||
|
||||
Prefer to use the `Annotated` version if possible.
|
||||
|
||||
///
|
||||
|
||||
```Python
|
||||
{!> ../../docs_src/app_testing/app_b_py310/main.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.8+ non-Annotated
|
||||
|
||||
/// tip
|
||||
|
||||
Prefer to use the `Annotated` version if possible.
|
||||
|
||||
///
|
||||
|
||||
```Python
|
||||
{!> ../../docs_src/app_testing/app_b/main.py!}
|
||||
```
|
||||
|
||||
////
|
||||
{* ../../docs_src/app_testing/app_b_an_py310/main.py *}
|
||||
|
||||
### Extended testing file { #extended-testing-file }
|
||||
|
||||
You could then update `test_main.py` with the extended tests:
|
||||
|
||||
{* ../../docs_src/app_testing/app_b/test_main.py *}
|
||||
{* ../../docs_src/app_testing/app_b_an_py310/test_main.py *}
|
||||
|
||||
|
||||
Whenever you need the client to pass information in the request and you don't know how to, you can search (Google) how to do it in `httpx`, or even how to do it with `requests`, as HTTPX's design is based on Requests' design.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
from fastapi import FastAPI
|
||||
from fastapi.responses import FileResponse
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Item(BaseModel):
|
||||
id: str
|
||||
value: str
|
||||
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.get(
|
||||
"/items/{item_id}",
|
||||
response_model=Item,
|
||||
responses={
|
||||
200: {
|
||||
"content": {"image/png": {}},
|
||||
"description": "Return the JSON item or an image.",
|
||||
}
|
||||
},
|
||||
)
|
||||
async def read_item(item_id: str, img: bool | None = None):
|
||||
if img:
|
||||
return FileResponse("image.png", media_type="image/png")
|
||||
else:
|
||||
return {"id": "foo", "value": "there goes my hero"}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
from fastapi import FastAPI
|
||||
from fastapi.responses import FileResponse
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Item(BaseModel):
|
||||
id: str
|
||||
value: str
|
||||
|
||||
|
||||
responses = {
|
||||
404: {"description": "Item not found"},
|
||||
302: {"description": "The item was moved"},
|
||||
403: {"description": "Not enough privileges"},
|
||||
}
|
||||
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.get(
|
||||
"/items/{item_id}",
|
||||
response_model=Item,
|
||||
responses={**responses, 200: {"content": {"image/png": {}}}},
|
||||
)
|
||||
async def read_item(item_id: str, img: bool | None = None):
|
||||
if img:
|
||||
return FileResponse("image.png", media_type="image/png")
|
||||
else:
|
||||
return {"id": "foo", "value": "there goes my hero"}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
import gzip
|
||||
from typing import Callable, List
|
||||
|
||||
from fastapi import Body, FastAPI, Request, Response
|
||||
from fastapi.routing import APIRoute
|
||||
from typing_extensions import Annotated
|
||||
|
||||
|
||||
class GzipRequest(Request):
|
||||
async def body(self) -> bytes:
|
||||
if not hasattr(self, "_body"):
|
||||
body = await super().body()
|
||||
if "gzip" in self.headers.getlist("Content-Encoding"):
|
||||
body = gzip.decompress(body)
|
||||
self._body = body
|
||||
return self._body
|
||||
|
||||
|
||||
class GzipRoute(APIRoute):
|
||||
def get_route_handler(self) -> Callable:
|
||||
original_route_handler = super().get_route_handler()
|
||||
|
||||
async def custom_route_handler(request: Request) -> Response:
|
||||
request = GzipRequest(request.scope, request.receive)
|
||||
return await original_route_handler(request)
|
||||
|
||||
return custom_route_handler
|
||||
|
||||
|
||||
app = FastAPI()
|
||||
app.router.route_class = GzipRoute
|
||||
|
||||
|
||||
@app.post("/sum")
|
||||
async def sum_numbers(numbers: Annotated[List[int], Body()]):
|
||||
return {"sum": sum(numbers)}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
import gzip
|
||||
from collections.abc import Callable
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import Body, FastAPI, Request, Response
|
||||
from fastapi.routing import APIRoute
|
||||
|
||||
|
||||
class GzipRequest(Request):
|
||||
async def body(self) -> bytes:
|
||||
if not hasattr(self, "_body"):
|
||||
body = await super().body()
|
||||
if "gzip" in self.headers.getlist("Content-Encoding"):
|
||||
body = gzip.decompress(body)
|
||||
self._body = body
|
||||
return self._body
|
||||
|
||||
|
||||
class GzipRoute(APIRoute):
|
||||
def get_route_handler(self) -> Callable:
|
||||
original_route_handler = super().get_route_handler()
|
||||
|
||||
async def custom_route_handler(request: Request) -> Response:
|
||||
request = GzipRequest(request.scope, request.receive)
|
||||
return await original_route_handler(request)
|
||||
|
||||
return custom_route_handler
|
||||
|
||||
|
||||
app = FastAPI()
|
||||
app.router.route_class = GzipRoute
|
||||
|
||||
|
||||
@app.post("/sum")
|
||||
async def sum_numbers(numbers: Annotated[list[int], Body()]):
|
||||
return {"sum": sum(numbers)}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
import gzip
|
||||
from typing import Annotated, Callable
|
||||
|
||||
from fastapi import Body, FastAPI, Request, Response
|
||||
from fastapi.routing import APIRoute
|
||||
|
||||
|
||||
class GzipRequest(Request):
|
||||
async def body(self) -> bytes:
|
||||
if not hasattr(self, "_body"):
|
||||
body = await super().body()
|
||||
if "gzip" in self.headers.getlist("Content-Encoding"):
|
||||
body = gzip.decompress(body)
|
||||
self._body = body
|
||||
return self._body
|
||||
|
||||
|
||||
class GzipRoute(APIRoute):
|
||||
def get_route_handler(self) -> Callable:
|
||||
original_route_handler = super().get_route_handler()
|
||||
|
||||
async def custom_route_handler(request: Request) -> Response:
|
||||
request = GzipRequest(request.scope, request.receive)
|
||||
return await original_route_handler(request)
|
||||
|
||||
return custom_route_handler
|
||||
|
||||
|
||||
app = FastAPI()
|
||||
app.router.route_class = GzipRoute
|
||||
|
||||
|
||||
@app.post("/sum")
|
||||
async def sum_numbers(numbers: Annotated[list[int], Body()]):
|
||||
return {"sum": sum(numbers)}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
import gzip
|
||||
from collections.abc import Callable
|
||||
|
||||
from fastapi import Body, FastAPI, Request, Response
|
||||
from fastapi.routing import APIRoute
|
||||
|
||||
|
||||
class GzipRequest(Request):
|
||||
async def body(self) -> bytes:
|
||||
if not hasattr(self, "_body"):
|
||||
body = await super().body()
|
||||
if "gzip" in self.headers.getlist("Content-Encoding"):
|
||||
body = gzip.decompress(body)
|
||||
self._body = body
|
||||
return self._body
|
||||
|
||||
|
||||
class GzipRoute(APIRoute):
|
||||
def get_route_handler(self) -> Callable:
|
||||
original_route_handler = super().get_route_handler()
|
||||
|
||||
async def custom_route_handler(request: Request) -> Response:
|
||||
request = GzipRequest(request.scope, request.receive)
|
||||
return await original_route_handler(request)
|
||||
|
||||
return custom_route_handler
|
||||
|
||||
|
||||
app = FastAPI()
|
||||
app.router.route_class = GzipRoute
|
||||
|
||||
|
||||
@app.post("/sum")
|
||||
async def sum_numbers(numbers: list[int] = Body()):
|
||||
return {"sum": sum(numbers)}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
import gzip
|
||||
from typing import Callable
|
||||
|
||||
from fastapi import Body, FastAPI, Request, Response
|
||||
from fastapi.routing import APIRoute
|
||||
|
||||
|
||||
class GzipRequest(Request):
|
||||
async def body(self) -> bytes:
|
||||
if not hasattr(self, "_body"):
|
||||
body = await super().body()
|
||||
if "gzip" in self.headers.getlist("Content-Encoding"):
|
||||
body = gzip.decompress(body)
|
||||
self._body = body
|
||||
return self._body
|
||||
|
||||
|
||||
class GzipRoute(APIRoute):
|
||||
def get_route_handler(self) -> Callable:
|
||||
original_route_handler = super().get_route_handler()
|
||||
|
||||
async def custom_route_handler(request: Request) -> Response:
|
||||
request = GzipRequest(request.scope, request.receive)
|
||||
return await original_route_handler(request)
|
||||
|
||||
return custom_route_handler
|
||||
|
||||
|
||||
app = FastAPI()
|
||||
app.router.route_class = GzipRoute
|
||||
|
||||
|
||||
@app.post("/sum")
|
||||
async def sum_numbers(numbers: list[int] = Body()):
|
||||
return {"sum": sum(numbers)}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
from typing import Callable, List
|
||||
|
||||
from fastapi import Body, FastAPI, HTTPException, Request, Response
|
||||
from fastapi.exceptions import RequestValidationError
|
||||
from fastapi.routing import APIRoute
|
||||
from typing_extensions import Annotated
|
||||
|
||||
|
||||
class ValidationErrorLoggingRoute(APIRoute):
|
||||
def get_route_handler(self) -> Callable:
|
||||
original_route_handler = super().get_route_handler()
|
||||
|
||||
async def custom_route_handler(request: Request) -> Response:
|
||||
try:
|
||||
return await original_route_handler(request)
|
||||
except RequestValidationError as exc:
|
||||
body = await request.body()
|
||||
detail = {"errors": exc.errors(), "body": body.decode()}
|
||||
raise HTTPException(status_code=422, detail=detail)
|
||||
|
||||
return custom_route_handler
|
||||
|
||||
|
||||
app = FastAPI()
|
||||
app.router.route_class = ValidationErrorLoggingRoute
|
||||
|
||||
|
||||
@app.post("/")
|
||||
async def sum_numbers(numbers: Annotated[List[int], Body()]):
|
||||
return sum(numbers)
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
from collections.abc import Callable
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import Body, FastAPI, HTTPException, Request, Response
|
||||
from fastapi.exceptions import RequestValidationError
|
||||
from fastapi.routing import APIRoute
|
||||
|
||||
|
||||
class ValidationErrorLoggingRoute(APIRoute):
|
||||
def get_route_handler(self) -> Callable:
|
||||
original_route_handler = super().get_route_handler()
|
||||
|
||||
async def custom_route_handler(request: Request) -> Response:
|
||||
try:
|
||||
return await original_route_handler(request)
|
||||
except RequestValidationError as exc:
|
||||
body = await request.body()
|
||||
detail = {"errors": exc.errors(), "body": body.decode()}
|
||||
raise HTTPException(status_code=422, detail=detail)
|
||||
|
||||
return custom_route_handler
|
||||
|
||||
|
||||
app = FastAPI()
|
||||
app.router.route_class = ValidationErrorLoggingRoute
|
||||
|
||||
|
||||
@app.post("/")
|
||||
async def sum_numbers(numbers: Annotated[list[int], Body()]):
|
||||
return sum(numbers)
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
from typing import Annotated, Callable
|
||||
|
||||
from fastapi import Body, FastAPI, HTTPException, Request, Response
|
||||
from fastapi.exceptions import RequestValidationError
|
||||
from fastapi.routing import APIRoute
|
||||
|
||||
|
||||
class ValidationErrorLoggingRoute(APIRoute):
|
||||
def get_route_handler(self) -> Callable:
|
||||
original_route_handler = super().get_route_handler()
|
||||
|
||||
async def custom_route_handler(request: Request) -> Response:
|
||||
try:
|
||||
return await original_route_handler(request)
|
||||
except RequestValidationError as exc:
|
||||
body = await request.body()
|
||||
detail = {"errors": exc.errors(), "body": body.decode()}
|
||||
raise HTTPException(status_code=422, detail=detail)
|
||||
|
||||
return custom_route_handler
|
||||
|
||||
|
||||
app = FastAPI()
|
||||
app.router.route_class = ValidationErrorLoggingRoute
|
||||
|
||||
|
||||
@app.post("/")
|
||||
async def sum_numbers(numbers: Annotated[list[int], Body()]):
|
||||
return sum(numbers)
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
from collections.abc import Callable
|
||||
|
||||
from fastapi import Body, FastAPI, HTTPException, Request, Response
|
||||
from fastapi.exceptions import RequestValidationError
|
||||
from fastapi.routing import APIRoute
|
||||
|
||||
|
||||
class ValidationErrorLoggingRoute(APIRoute):
|
||||
def get_route_handler(self) -> Callable:
|
||||
original_route_handler = super().get_route_handler()
|
||||
|
||||
async def custom_route_handler(request: Request) -> Response:
|
||||
try:
|
||||
return await original_route_handler(request)
|
||||
except RequestValidationError as exc:
|
||||
body = await request.body()
|
||||
detail = {"errors": exc.errors(), "body": body.decode()}
|
||||
raise HTTPException(status_code=422, detail=detail)
|
||||
|
||||
return custom_route_handler
|
||||
|
||||
|
||||
app = FastAPI()
|
||||
app.router.route_class = ValidationErrorLoggingRoute
|
||||
|
||||
|
||||
@app.post("/")
|
||||
async def sum_numbers(numbers: list[int] = Body()):
|
||||
return sum(numbers)
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
from typing import Callable
|
||||
|
||||
from fastapi import Body, FastAPI, HTTPException, Request, Response
|
||||
from fastapi.exceptions import RequestValidationError
|
||||
from fastapi.routing import APIRoute
|
||||
|
||||
|
||||
class ValidationErrorLoggingRoute(APIRoute):
|
||||
def get_route_handler(self) -> Callable:
|
||||
original_route_handler = super().get_route_handler()
|
||||
|
||||
async def custom_route_handler(request: Request) -> Response:
|
||||
try:
|
||||
return await original_route_handler(request)
|
||||
except RequestValidationError as exc:
|
||||
body = await request.body()
|
||||
detail = {"errors": exc.errors(), "body": body.decode()}
|
||||
raise HTTPException(status_code=422, detail=detail)
|
||||
|
||||
return custom_route_handler
|
||||
|
||||
|
||||
app = FastAPI()
|
||||
app.router.route_class = ValidationErrorLoggingRoute
|
||||
|
||||
|
||||
@app.post("/")
|
||||
async def sum_numbers(numbers: list[int] = Body()):
|
||||
return sum(numbers)
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
import time
|
||||
from collections.abc import Callable
|
||||
|
||||
from fastapi import APIRouter, FastAPI, Request, Response
|
||||
from fastapi.routing import APIRoute
|
||||
|
||||
|
||||
class TimedRoute(APIRoute):
|
||||
def get_route_handler(self) -> Callable:
|
||||
original_route_handler = super().get_route_handler()
|
||||
|
||||
async def custom_route_handler(request: Request) -> Response:
|
||||
before = time.time()
|
||||
response: Response = await original_route_handler(request)
|
||||
duration = time.time() - before
|
||||
response.headers["X-Response-Time"] = str(duration)
|
||||
print(f"route duration: {duration}")
|
||||
print(f"route response: {response}")
|
||||
print(f"route response headers: {response.headers}")
|
||||
return response
|
||||
|
||||
return custom_route_handler
|
||||
|
||||
|
||||
app = FastAPI()
|
||||
router = APIRouter(route_class=TimedRoute)
|
||||
|
||||
|
||||
@app.get("/")
|
||||
async def not_timed():
|
||||
return {"message": "Not timed"}
|
||||
|
||||
|
||||
@router.get("/timed")
|
||||
async def timed():
|
||||
return {"message": "It's the time of my life"}
|
||||
|
||||
|
||||
app.include_router(router)
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
from dataclasses import dataclass
|
||||
|
||||
from fastapi import FastAPI
|
||||
|
||||
|
||||
@dataclass
|
||||
class Item:
|
||||
name: str
|
||||
price: float
|
||||
description: str | None = None
|
||||
tax: float | None = None
|
||||
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.post("/items/")
|
||||
async def create_item(item: Item):
|
||||
return item
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
from dataclasses import dataclass, field
|
||||
|
||||
from fastapi import FastAPI
|
||||
|
||||
|
||||
@dataclass
|
||||
class Item:
|
||||
name: str
|
||||
price: float
|
||||
tags: list[str] = field(default_factory=list)
|
||||
description: str | None = None
|
||||
tax: float | None = None
|
||||
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.get("/items/next", response_model=Item)
|
||||
async def read_next_item():
|
||||
return {
|
||||
"name": "Island In The Moon",
|
||||
"price": 12.99,
|
||||
"description": "A place to be playin' and havin' fun",
|
||||
"tags": ["breater"],
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
from dataclasses import dataclass, field
|
||||
from typing import Union
|
||||
|
||||
from fastapi import FastAPI
|
||||
|
||||
|
||||
@dataclass
|
||||
class Item:
|
||||
name: str
|
||||
price: float
|
||||
tags: list[str] = field(default_factory=list)
|
||||
description: Union[str, None] = None
|
||||
tax: Union[float, None] = None
|
||||
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.get("/items/next", response_model=Item)
|
||||
async def read_next_item():
|
||||
return {
|
||||
"name": "Island In The Moon",
|
||||
"price": 12.99,
|
||||
"description": "A place to be playin' and havin' fun",
|
||||
"tags": ["breater"],
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
from dataclasses import field # (1)
|
||||
|
||||
from fastapi import FastAPI
|
||||
from pydantic.dataclasses import dataclass # (2)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Item:
|
||||
name: str
|
||||
description: str | None = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class Author:
|
||||
name: str
|
||||
items: list[Item] = field(default_factory=list) # (3)
|
||||
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.post("/authors/{author_id}/items/", response_model=Author) # (4)
|
||||
async def create_author_items(author_id: str, items: list[Item]): # (5)
|
||||
return {"name": author_id, "items": items} # (6)
|
||||
|
||||
|
||||
@app.get("/authors/", response_model=list[Author]) # (7)
|
||||
def get_authors(): # (8)
|
||||
return [ # (9)
|
||||
{
|
||||
"name": "Breaters",
|
||||
"items": [
|
||||
{
|
||||
"name": "Island In The Moon",
|
||||
"description": "A place to be playin' and havin' fun",
|
||||
},
|
||||
{"name": "Holy Buddies"},
|
||||
],
|
||||
},
|
||||
{
|
||||
"name": "System of an Up",
|
||||
"items": [
|
||||
{
|
||||
"name": "Salt",
|
||||
"description": "The kombucha mushroom people's favorite",
|
||||
},
|
||||
{"name": "Pad Thai"},
|
||||
{
|
||||
"name": "Lonely Night",
|
||||
"description": "The mostests lonliest nightiest of allest",
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
from dataclasses import field # (1)
|
||||
from typing import Union
|
||||
|
||||
from fastapi import FastAPI
|
||||
from pydantic.dataclasses import dataclass # (2)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Item:
|
||||
name: str
|
||||
description: Union[str, None] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class Author:
|
||||
name: str
|
||||
items: list[Item] = field(default_factory=list) # (3)
|
||||
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.post("/authors/{author_id}/items/", response_model=Author) # (4)
|
||||
async def create_author_items(author_id: str, items: list[Item]): # (5)
|
||||
return {"name": author_id, "items": items} # (6)
|
||||
|
||||
|
||||
@app.get("/authors/", response_model=list[Author]) # (7)
|
||||
def get_authors(): # (8)
|
||||
return [ # (9)
|
||||
{
|
||||
"name": "Breaters",
|
||||
"items": [
|
||||
{
|
||||
"name": "Island In The Moon",
|
||||
"description": "A place to be playin' and havin' fun",
|
||||
},
|
||||
{"name": "Holy Buddies"},
|
||||
],
|
||||
},
|
||||
{
|
||||
"name": "System of an Up",
|
||||
"items": [
|
||||
{
|
||||
"name": "Salt",
|
||||
"description": "The kombucha mushroom people's favorite",
|
||||
},
|
||||
{"name": "Pad Thai"},
|
||||
{
|
||||
"name": "Lonely Night",
|
||||
"description": "The mostests lonliest nightiest of allest",
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
from fastapi import APIRouter, FastAPI
|
||||
from pydantic import BaseModel, HttpUrl
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
class Invoice(BaseModel):
|
||||
id: str
|
||||
title: str | None = None
|
||||
customer: str
|
||||
total: float
|
||||
|
||||
|
||||
class InvoiceEvent(BaseModel):
|
||||
description: str
|
||||
paid: bool
|
||||
|
||||
|
||||
class InvoiceEventReceived(BaseModel):
|
||||
ok: bool
|
||||
|
||||
|
||||
invoices_callback_router = APIRouter()
|
||||
|
||||
|
||||
@invoices_callback_router.post(
|
||||
"{$callback_url}/invoices/{$request.body.id}", response_model=InvoiceEventReceived
|
||||
)
|
||||
def invoice_notification(body: InvoiceEvent):
|
||||
pass
|
||||
|
||||
|
||||
@app.post("/invoices/", callbacks=invoices_callback_router.routes)
|
||||
def create_invoice(invoice: Invoice, callback_url: HttpUrl | None = None):
|
||||
"""
|
||||
Create an invoice.
|
||||
|
||||
This will (let's imagine) let the API user (some external developer) create an
|
||||
invoice.
|
||||
|
||||
And this path operation will:
|
||||
|
||||
* Send the invoice to the client.
|
||||
* Collect the money from the client.
|
||||
* Send a notification back to the API user (the external developer), as a callback.
|
||||
* At this point is that the API will somehow send a POST request to the
|
||||
external API with the notification of the invoice event
|
||||
(e.g. "payment successful").
|
||||
"""
|
||||
# Send the invoice, collect the money, send the notification (the callback)
|
||||
return {"msg": "Invoice received"}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
from fastapi import FastAPI
|
||||
from pydantic import BaseModel
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
class Item(BaseModel):
|
||||
name: str
|
||||
description: str | None = None
|
||||
price: float
|
||||
tax: float | None = None
|
||||
tags: set[str] = set()
|
||||
|
||||
|
||||
@app.post("/items/", response_model=Item, summary="Create an item")
|
||||
async def create_item(item: Item):
|
||||
"""
|
||||
Create an item with all the information:
|
||||
|
||||
- **name**: each item must have a name
|
||||
- **description**: a long description
|
||||
- **price**: required
|
||||
- **tax**: if the item doesn't have tax, you can omit this
|
||||
- **tags**: a set of unique tag strings for this item
|
||||
\f
|
||||
:param item: User input.
|
||||
"""
|
||||
return item
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
from typing import Union
|
||||
|
||||
from fastapi import FastAPI
|
||||
from pydantic import BaseModel
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
class Item(BaseModel):
|
||||
name: str
|
||||
description: Union[str, None] = None
|
||||
price: float
|
||||
tax: Union[float, None] = None
|
||||
tags: set[str] = set()
|
||||
|
||||
|
||||
@app.post("/items/", response_model=Item, summary="Create an item")
|
||||
async def create_item(item: Item):
|
||||
"""
|
||||
Create an item with all the information:
|
||||
|
||||
- **name**: each item must have a name
|
||||
- **description**: a long description
|
||||
- **price**: required
|
||||
- **tax**: if the item doesn't have tax, you can omit this
|
||||
- **tags**: a set of unique tag strings for this item
|
||||
\f
|
||||
:param item: User input.
|
||||
"""
|
||||
return item
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
import yaml
|
||||
from fastapi import FastAPI, HTTPException, Request
|
||||
from pydantic import BaseModel, ValidationError
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
class Item(BaseModel):
|
||||
name: str
|
||||
tags: list[str]
|
||||
|
||||
|
||||
@app.post(
|
||||
"/items/",
|
||||
openapi_extra={
|
||||
"requestBody": {
|
||||
"content": {"application/x-yaml": {"schema": Item.schema()}},
|
||||
"required": True,
|
||||
},
|
||||
},
|
||||
)
|
||||
async def create_item(request: Request):
|
||||
raw_body = await request.body()
|
||||
try:
|
||||
data = yaml.safe_load(raw_body)
|
||||
except yaml.YAMLError:
|
||||
raise HTTPException(status_code=422, detail="Invalid YAML")
|
||||
try:
|
||||
item = Item.parse_obj(data)
|
||||
except ValidationError as e:
|
||||
raise HTTPException(status_code=422, detail=e.errors())
|
||||
return item
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
import yaml
|
||||
from fastapi import FastAPI, HTTPException, Request
|
||||
from pydantic import BaseModel, ValidationError
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
class Item(BaseModel):
|
||||
name: str
|
||||
tags: list[str]
|
||||
|
||||
|
||||
@app.post(
|
||||
"/items/",
|
||||
openapi_extra={
|
||||
"requestBody": {
|
||||
"content": {"application/x-yaml": {"schema": Item.model_json_schema()}},
|
||||
"required": True,
|
||||
},
|
||||
},
|
||||
)
|
||||
async def create_item(request: Request):
|
||||
raw_body = await request.body()
|
||||
try:
|
||||
data = yaml.safe_load(raw_body)
|
||||
except yaml.YAMLError:
|
||||
raise HTTPException(status_code=422, detail="Invalid YAML")
|
||||
try:
|
||||
item = Item.model_validate(data)
|
||||
except ValidationError as e:
|
||||
raise HTTPException(status_code=422, detail=e.errors(include_url=False))
|
||||
return item
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
from datetime import datetime
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from fastapi.responses import JSONResponse
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Item(BaseModel):
|
||||
title: str
|
||||
timestamp: datetime
|
||||
description: str | None = None
|
||||
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.put("/items/{id}")
|
||||
def update_item(id: str, item: Item):
|
||||
json_compatible_item_data = jsonable_encoder(item)
|
||||
return JSONResponse(content=json_compatible_item_data)
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
from pydantic_settings import BaseSettings
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
|
|
@ -6,5 +6,4 @@ class Settings(BaseSettings):
|
|||
admin_email: str
|
||||
items_per_user: int = 50
|
||||
|
||||
class Config:
|
||||
env_file = ".env"
|
||||
model_config = SettingsConfigDict(env_file=".env")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
from pydantic import BaseSettings
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
app_name: str = "Awesome API"
|
||||
admin_email: str
|
||||
items_per_user: int = 50
|
||||
|
||||
class Config:
|
||||
env_file = ".env"
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
from functools import lru_cache
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import Depends, FastAPI
|
||||
from typing_extensions import Annotated
|
||||
|
||||
from . import config
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from pydantic_settings import BaseSettings
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
|
|
@ -6,5 +6,4 @@ class Settings(BaseSettings):
|
|||
admin_email: str
|
||||
items_per_user: int = 50
|
||||
|
||||
class Config:
|
||||
env_file = ".env"
|
||||
model_config = SettingsConfigDict(env_file=".env")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
from pydantic import BaseSettings
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
app_name: str = "Awesome API"
|
||||
admin_email: str
|
||||
items_per_user: int = 50
|
||||
|
||||
class Config:
|
||||
env_file = ".env"
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
from functools import lru_cache
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import Depends, FastAPI
|
||||
from typing_extensions import Annotated
|
||||
|
||||
from . import config
|
||||
|
||||
|
|
|
|||
|
|
@ -236,8 +236,15 @@ ignore = [
|
|||
"docs_src/custom_response/tutorial007.py" = ["B007"]
|
||||
"docs_src/dataclasses/tutorial003.py" = ["I001"]
|
||||
"docs_src/path_operation_advanced_configuration/tutorial007.py" = ["B904"]
|
||||
"docs_src/path_operation_advanced_configuration/tutorial007_py39.py" = ["B904"]
|
||||
"docs_src/path_operation_advanced_configuration/tutorial007_pv1.py" = ["B904"]
|
||||
"docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py" = ["B904"]
|
||||
"docs_src/custom_request_and_route/tutorial002.py" = ["B904"]
|
||||
"docs_src/custom_request_and_route/tutorial002_py39.py" = ["B904"]
|
||||
"docs_src/custom_request_and_route/tutorial002_py310.py" = ["B904"]
|
||||
"docs_src/custom_request_and_route/tutorial002_an.py" = ["B904"]
|
||||
"docs_src/custom_request_and_route/tutorial002_an_py39.py" = ["B904"]
|
||||
"docs_src/custom_request_and_route/tutorial002_an_py310.py" = ["B904"]
|
||||
"docs_src/dependencies/tutorial008_an.py" = ["F821"]
|
||||
"docs_src/dependencies/tutorial008_an_py39.py" = ["F821"]
|
||||
"docs_src/query_params_str_validations/tutorial012_an.py" = ["B006"]
|
||||
|
|
|
|||
|
|
@ -1,21 +1,36 @@
|
|||
import importlib
|
||||
import os
|
||||
import shutil
|
||||
|
||||
import pytest
|
||||
from dirty_equals import IsDict
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from docs_src.additional_responses.tutorial002 import app
|
||||
|
||||
client = TestClient(app)
|
||||
from tests.utils import needs_py310
|
||||
|
||||
|
||||
def test_path_operation():
|
||||
@pytest.fixture(
|
||||
name="client",
|
||||
params=[
|
||||
pytest.param("tutorial002"),
|
||||
pytest.param("tutorial002_py310", marks=needs_py310),
|
||||
],
|
||||
)
|
||||
def get_client(request: pytest.FixtureRequest):
|
||||
mod = importlib.import_module(f"docs_src.additional_responses.{request.param}")
|
||||
|
||||
client = TestClient(mod.app)
|
||||
client.headers.clear()
|
||||
return client
|
||||
|
||||
|
||||
def test_path_operation(client: TestClient):
|
||||
response = client.get("/items/foo")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {"id": "foo", "value": "there goes my hero"}
|
||||
|
||||
|
||||
def test_path_operation_img():
|
||||
def test_path_operation_img(client: TestClient):
|
||||
shutil.copy("./docs/en/docs/img/favicon.png", "./image.png")
|
||||
response = client.get("/items/foo?img=1")
|
||||
assert response.status_code == 200, response.text
|
||||
|
|
@ -24,7 +39,7 @@ def test_path_operation_img():
|
|||
os.remove("./image.png")
|
||||
|
||||
|
||||
def test_openapi_schema():
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
|
|
|
|||
|
|
@ -1,21 +1,36 @@
|
|||
import importlib
|
||||
import os
|
||||
import shutil
|
||||
|
||||
import pytest
|
||||
from dirty_equals import IsDict
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from docs_src.additional_responses.tutorial004 import app
|
||||
|
||||
client = TestClient(app)
|
||||
from tests.utils import needs_py310
|
||||
|
||||
|
||||
def test_path_operation():
|
||||
@pytest.fixture(
|
||||
name="client",
|
||||
params=[
|
||||
pytest.param("tutorial004"),
|
||||
pytest.param("tutorial004_py310", marks=needs_py310),
|
||||
],
|
||||
)
|
||||
def get_client(request: pytest.FixtureRequest):
|
||||
mod = importlib.import_module(f"docs_src.additional_responses.{request.param}")
|
||||
|
||||
client = TestClient(mod.app)
|
||||
client.headers.clear()
|
||||
return client
|
||||
|
||||
|
||||
def test_path_operation(client: TestClient):
|
||||
response = client.get("/items/foo")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {"id": "foo", "value": "there goes my hero"}
|
||||
|
||||
|
||||
def test_path_operation_img():
|
||||
def test_path_operation_img(client: TestClient):
|
||||
shutil.copy("./docs/en/docs/img/favicon.png", "./image.png")
|
||||
response = client.get("/items/foo?img=1")
|
||||
assert response.status_code == 200, response.text
|
||||
|
|
@ -24,7 +39,7 @@ def test_path_operation_img():
|
|||
os.remove("./image.png")
|
||||
|
||||
|
||||
def test_openapi_schema():
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
|
|
|
|||
|
|
@ -1,23 +1,38 @@
|
|||
import gzip
|
||||
import importlib
|
||||
import json
|
||||
|
||||
import pytest
|
||||
from fastapi import Request
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from docs_src.custom_request_and_route.tutorial001 import app
|
||||
from tests.utils import needs_py39, needs_py310
|
||||
|
||||
|
||||
@app.get("/check-class")
|
||||
async def check_gzip_request(request: Request):
|
||||
return {"request_class": type(request).__name__}
|
||||
@pytest.fixture(
|
||||
name="client",
|
||||
params=[
|
||||
pytest.param("tutorial001"),
|
||||
pytest.param("tutorial001_py39", marks=needs_py39),
|
||||
pytest.param("tutorial001_py310", marks=needs_py310),
|
||||
pytest.param("tutorial001_an"),
|
||||
pytest.param("tutorial001_an_py39", marks=needs_py39),
|
||||
pytest.param("tutorial001_an_py310", marks=needs_py310),
|
||||
],
|
||||
)
|
||||
def get_client(request: pytest.FixtureRequest):
|
||||
mod = importlib.import_module(f"docs_src.custom_request_and_route.{request.param}")
|
||||
|
||||
@mod.app.get("/check-class")
|
||||
async def check_gzip_request(request: Request):
|
||||
return {"request_class": type(request).__name__}
|
||||
|
||||
client = TestClient(app)
|
||||
client = TestClient(mod.app)
|
||||
return client
|
||||
|
||||
|
||||
@pytest.mark.parametrize("compress", [True, False])
|
||||
def test_gzip_request(compress):
|
||||
def test_gzip_request(client: TestClient, compress):
|
||||
n = 1000
|
||||
headers = {}
|
||||
body = [1] * n
|
||||
|
|
@ -30,6 +45,6 @@ def test_gzip_request(compress):
|
|||
assert response.json() == {"sum": n}
|
||||
|
||||
|
||||
def test_request_class():
|
||||
def test_request_class(client: TestClient):
|
||||
response = client.get("/check-class")
|
||||
assert response.json() == {"request_class": "GzipRequest"}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,36 @@
|
|||
import importlib
|
||||
|
||||
import pytest
|
||||
from dirty_equals import IsDict, IsOneOf
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from docs_src.custom_request_and_route.tutorial002 import app
|
||||
|
||||
client = TestClient(app)
|
||||
from tests.utils import needs_py39, needs_py310
|
||||
|
||||
|
||||
def test_endpoint_works():
|
||||
@pytest.fixture(
|
||||
name="client",
|
||||
params=[
|
||||
pytest.param("tutorial002"),
|
||||
pytest.param("tutorial002_py39", marks=needs_py39),
|
||||
pytest.param("tutorial002_py310", marks=needs_py310),
|
||||
pytest.param("tutorial002_an"),
|
||||
pytest.param("tutorial002_an_py39", marks=needs_py39),
|
||||
pytest.param("tutorial002_an_py310", marks=needs_py310),
|
||||
],
|
||||
)
|
||||
def get_client(request: pytest.FixtureRequest):
|
||||
mod = importlib.import_module(f"docs_src.custom_request_and_route.{request.param}")
|
||||
|
||||
client = TestClient(mod.app)
|
||||
return client
|
||||
|
||||
|
||||
def test_endpoint_works(client: TestClient):
|
||||
response = client.post("/", json=[1, 2, 3])
|
||||
assert response.json() == 6
|
||||
|
||||
|
||||
def test_exception_handler_body_access():
|
||||
def test_exception_handler_body_access(client: TestClient):
|
||||
response = client.post("/", json={"numbers": [1, 2, 3]})
|
||||
assert response.json() == IsDict(
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,17 +1,32 @@
|
|||
import importlib
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from docs_src.custom_request_and_route.tutorial003 import app
|
||||
|
||||
client = TestClient(app)
|
||||
from tests.utils import needs_py310
|
||||
|
||||
|
||||
def test_get():
|
||||
@pytest.fixture(
|
||||
name="client",
|
||||
params=[
|
||||
pytest.param("tutorial003"),
|
||||
pytest.param("tutorial003_py310", marks=needs_py310),
|
||||
],
|
||||
)
|
||||
def get_client(request: pytest.FixtureRequest):
|
||||
mod = importlib.import_module(f"docs_src.custom_request_and_route.{request.param}")
|
||||
|
||||
client = TestClient(mod.app)
|
||||
return client
|
||||
|
||||
|
||||
def test_get(client: TestClient):
|
||||
response = client.get("/")
|
||||
assert response.json() == {"message": "Not timed"}
|
||||
assert "X-Response-Time" not in response.headers
|
||||
|
||||
|
||||
def test_get_timed():
|
||||
def test_get_timed(client: TestClient):
|
||||
response = client.get("/timed")
|
||||
assert response.json() == {"message": "It's the time of my life"}
|
||||
assert "X-Response-Time" in response.headers
|
||||
|
|
|
|||
|
|
@ -1,12 +1,28 @@
|
|||
import importlib
|
||||
|
||||
import pytest
|
||||
from dirty_equals import IsDict
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from docs_src.dataclasses.tutorial001 import app
|
||||
|
||||
client = TestClient(app)
|
||||
from tests.utils import needs_py310
|
||||
|
||||
|
||||
def test_post_item():
|
||||
@pytest.fixture(
|
||||
name="client",
|
||||
params=[
|
||||
pytest.param("tutorial001"),
|
||||
pytest.param("tutorial001_py310", marks=needs_py310),
|
||||
],
|
||||
)
|
||||
def get_client(request: pytest.FixtureRequest):
|
||||
mod = importlib.import_module(f"docs_src.dataclasses.{request.param}")
|
||||
|
||||
client = TestClient(mod.app)
|
||||
client.headers.clear()
|
||||
return client
|
||||
|
||||
|
||||
def test_post_item(client: TestClient):
|
||||
response = client.post("/items/", json={"name": "Foo", "price": 3})
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
|
|
@ -17,7 +33,7 @@ def test_post_item():
|
|||
}
|
||||
|
||||
|
||||
def test_post_invalid_item():
|
||||
def test_post_invalid_item(client: TestClient):
|
||||
response = client.post("/items/", json={"name": "Foo", "price": "invalid price"})
|
||||
assert response.status_code == 422
|
||||
assert response.json() == IsDict(
|
||||
|
|
@ -45,7 +61,7 @@ def test_post_invalid_item():
|
|||
)
|
||||
|
||||
|
||||
def test_openapi_schema():
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,29 @@
|
|||
import importlib
|
||||
|
||||
import pytest
|
||||
from dirty_equals import IsDict, IsOneOf
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from docs_src.dataclasses.tutorial002 import app
|
||||
|
||||
client = TestClient(app)
|
||||
from tests.utils import needs_py39, needs_py310
|
||||
|
||||
|
||||
def test_get_item():
|
||||
@pytest.fixture(
|
||||
name="client",
|
||||
params=[
|
||||
pytest.param("tutorial002"),
|
||||
pytest.param("tutorial002_py39", marks=needs_py39),
|
||||
pytest.param("tutorial002_py310", marks=needs_py310),
|
||||
],
|
||||
)
|
||||
def get_client(request: pytest.FixtureRequest):
|
||||
mod = importlib.import_module(f"docs_src.dataclasses.{request.param}")
|
||||
|
||||
client = TestClient(mod.app)
|
||||
client.headers.clear()
|
||||
return client
|
||||
|
||||
|
||||
def test_get_item(client: TestClient):
|
||||
response = client.get("/items/next")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
|
|
@ -18,7 +35,7 @@ def test_get_item():
|
|||
}
|
||||
|
||||
|
||||
def test_openapi_schema():
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,28 @@
|
|||
import importlib
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from docs_src.dataclasses.tutorial003 import app
|
||||
|
||||
from ...utils import needs_pydanticv1, needs_pydanticv2
|
||||
|
||||
client = TestClient(app)
|
||||
from ...utils import needs_py39, needs_py310, needs_pydanticv1, needs_pydanticv2
|
||||
|
||||
|
||||
def test_post_authors_item():
|
||||
@pytest.fixture(
|
||||
name="client",
|
||||
params=[
|
||||
pytest.param("tutorial003"),
|
||||
pytest.param("tutorial003_py39", marks=needs_py39),
|
||||
pytest.param("tutorial003_py310", marks=needs_py310),
|
||||
],
|
||||
)
|
||||
def get_client(request: pytest.FixtureRequest):
|
||||
mod = importlib.import_module(f"docs_src.dataclasses.{request.param}")
|
||||
|
||||
client = TestClient(mod.app)
|
||||
client.headers.clear()
|
||||
return client
|
||||
|
||||
|
||||
def test_post_authors_item(client: TestClient):
|
||||
response = client.post(
|
||||
"/authors/foo/items/",
|
||||
json=[{"name": "Bar"}, {"name": "Baz", "description": "Drop the Baz"}],
|
||||
|
|
@ -22,7 +37,7 @@ def test_post_authors_item():
|
|||
}
|
||||
|
||||
|
||||
def test_get_authors():
|
||||
def test_get_authors(client: TestClient):
|
||||
response = client.get("/authors/")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == [
|
||||
|
|
@ -54,7 +69,7 @@ def test_get_authors():
|
|||
|
||||
|
||||
@needs_pydanticv2
|
||||
def test_openapi_schema():
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
|
|
@ -191,7 +206,7 @@ def test_openapi_schema():
|
|||
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
@needs_pydanticv1
|
||||
def test_openapi_schema_pv1():
|
||||
def test_openapi_schema_pv1(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,33 @@
|
|||
import importlib
|
||||
from types import ModuleType
|
||||
|
||||
import pytest
|
||||
from dirty_equals import IsDict
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from docs_src.openapi_callbacks.tutorial001 import app, invoice_notification
|
||||
|
||||
client = TestClient(app)
|
||||
from tests.utils import needs_py310
|
||||
|
||||
|
||||
def test_get():
|
||||
@pytest.fixture(
|
||||
name="mod",
|
||||
params=[
|
||||
pytest.param("tutorial001"),
|
||||
pytest.param("tutorial001_py310", marks=needs_py310),
|
||||
],
|
||||
)
|
||||
def get_mod(request: pytest.FixtureRequest):
|
||||
mod = importlib.import_module(f"docs_src.openapi_callbacks.{request.param}")
|
||||
return mod
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client(mod: ModuleType):
|
||||
client = TestClient(mod.app)
|
||||
client.headers.clear()
|
||||
return client
|
||||
|
||||
|
||||
def test_get(client: TestClient):
|
||||
response = client.post(
|
||||
"/invoices/", json={"id": "fooinvoice", "customer": "John", "total": 5.3}
|
||||
)
|
||||
|
|
@ -14,12 +35,12 @@ def test_get():
|
|||
assert response.json() == {"msg": "Invoice received"}
|
||||
|
||||
|
||||
def test_dummy_callback():
|
||||
def test_dummy_callback(mod: ModuleType):
|
||||
# Just for coverage
|
||||
invoice_notification({})
|
||||
mod.invoice_notification({})
|
||||
|
||||
|
||||
def test_openapi_schema():
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,30 @@
|
|||
import importlib
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from docs_src.path_operation_advanced_configuration.tutorial004 import app
|
||||
|
||||
from ...utils import needs_pydanticv1, needs_pydanticv2
|
||||
|
||||
client = TestClient(app)
|
||||
from ...utils import needs_py39, needs_py310, needs_pydanticv1, needs_pydanticv2
|
||||
|
||||
|
||||
def test_query_params_str_validations():
|
||||
@pytest.fixture(
|
||||
name="client",
|
||||
params=[
|
||||
pytest.param("tutorial004"),
|
||||
pytest.param("tutorial004_py39", marks=needs_py39),
|
||||
pytest.param("tutorial004_py310", marks=needs_py310),
|
||||
],
|
||||
)
|
||||
def get_client(request: pytest.FixtureRequest):
|
||||
mod = importlib.import_module(
|
||||
f"docs_src.path_operation_advanced_configuration.{request.param}"
|
||||
)
|
||||
|
||||
client = TestClient(mod.app)
|
||||
client.headers.clear()
|
||||
return client
|
||||
|
||||
|
||||
def test_query_params_str_validations(client: TestClient):
|
||||
response = client.post("/items/", json={"name": "Foo", "price": 42})
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
|
|
@ -20,7 +37,7 @@ def test_query_params_str_validations():
|
|||
|
||||
|
||||
@needs_pydanticv2
|
||||
def test_openapi_schema():
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
|
|
@ -123,7 +140,7 @@ def test_openapi_schema():
|
|||
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
@needs_pydanticv1
|
||||
def test_openapi_schema_pv1():
|
||||
def test_openapi_schema_pv1(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
|
|
|
|||
|
|
@ -1,14 +1,24 @@
|
|||
import importlib
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from ...utils import needs_pydanticv2
|
||||
from ...utils import needs_py39, needs_pydanticv2
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client():
|
||||
from docs_src.path_operation_advanced_configuration.tutorial007 import app
|
||||
@pytest.fixture(
|
||||
name="client",
|
||||
params=[
|
||||
pytest.param("tutorial007"),
|
||||
pytest.param("tutorial007_py39", marks=needs_py39),
|
||||
],
|
||||
)
|
||||
def get_client(request: pytest.FixtureRequest):
|
||||
mod = importlib.import_module(
|
||||
f"docs_src.path_operation_advanced_configuration.{request.param}"
|
||||
)
|
||||
|
||||
client = TestClient(app)
|
||||
client = TestClient(mod.app)
|
||||
return client
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,24 @@
|
|||
import importlib
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from ...utils import needs_pydanticv1
|
||||
from ...utils import needs_py39, needs_pydanticv1
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client():
|
||||
from docs_src.path_operation_advanced_configuration.tutorial007_pv1 import app
|
||||
@pytest.fixture(
|
||||
name="client",
|
||||
params=[
|
||||
pytest.param("tutorial007_pv1"),
|
||||
pytest.param("tutorial007_pv1_py39", marks=needs_py39),
|
||||
],
|
||||
)
|
||||
def get_client(request: pytest.FixtureRequest):
|
||||
mod = importlib.import_module(
|
||||
f"docs_src.path_operation_advanced_configuration.{request.param}"
|
||||
)
|
||||
|
||||
client = TestClient(app)
|
||||
client = TestClient(mod.app)
|
||||
return client
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,288 @@
|
|||
import importlib
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from ...utils import needs_py310, needs_pydanticv1, needs_pydanticv2
|
||||
|
||||
|
||||
@pytest.fixture(
|
||||
name="client",
|
||||
params=[
|
||||
pytest.param("tutorial001"),
|
||||
pytest.param("tutorial001_py310", marks=needs_py310),
|
||||
],
|
||||
)
|
||||
def get_client(request: pytest.FixtureRequest):
|
||||
mod = importlib.import_module(f"docs_src.response_directly.{request.param}")
|
||||
|
||||
client = TestClient(mod.app)
|
||||
return client
|
||||
|
||||
|
||||
def test_path_operation(client: TestClient):
|
||||
response = client.put(
|
||||
"/items/1",
|
||||
json={
|
||||
"title": "Foo",
|
||||
"timestamp": "2023-01-01T12:00:00",
|
||||
"description": "A test item",
|
||||
},
|
||||
)
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"description": "A test item",
|
||||
"timestamp": "2023-01-01T12:00:00",
|
||||
"title": "Foo",
|
||||
}
|
||||
|
||||
|
||||
@needs_pydanticv2
|
||||
def test_openapi_schema_pv2(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"info": {
|
||||
"title": "FastAPI",
|
||||
"version": "0.1.0",
|
||||
},
|
||||
"openapi": "3.1.0",
|
||||
"paths": {
|
||||
"/items/{id}": {
|
||||
"put": {
|
||||
"operationId": "update_item_items__id__put",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "path",
|
||||
"name": "id",
|
||||
"required": True,
|
||||
"schema": {"title": "Id", "type": "string"},
|
||||
},
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Item",
|
||||
},
|
||||
},
|
||||
},
|
||||
"required": True,
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {"schema": {}},
|
||||
},
|
||||
"description": "Successful Response",
|
||||
},
|
||||
"422": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError",
|
||||
},
|
||||
},
|
||||
},
|
||||
"description": "Validation Error",
|
||||
},
|
||||
},
|
||||
"summary": "Update Item",
|
||||
},
|
||||
},
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"HTTPValidationError": {
|
||||
"properties": {
|
||||
"detail": {
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/ValidationError",
|
||||
},
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
},
|
||||
},
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
},
|
||||
"Item": {
|
||||
"properties": {
|
||||
"description": {
|
||||
"anyOf": [
|
||||
{"type": "string"},
|
||||
{"type": "null"},
|
||||
],
|
||||
"title": "Description",
|
||||
},
|
||||
"timestamp": {
|
||||
"format": "date-time",
|
||||
"title": "Timestamp",
|
||||
"type": "string",
|
||||
},
|
||||
"title": {"title": "Title", "type": "string"},
|
||||
},
|
||||
"required": [
|
||||
"title",
|
||||
"timestamp",
|
||||
],
|
||||
"title": "Item",
|
||||
"type": "object",
|
||||
},
|
||||
"ValidationError": {
|
||||
"properties": {
|
||||
"loc": {
|
||||
"items": {
|
||||
"anyOf": [
|
||||
{"type": "string"},
|
||||
{"type": "integer"},
|
||||
],
|
||||
},
|
||||
"title": "Location",
|
||||
"type": "array",
|
||||
},
|
||||
"msg": {"title": "Message", "type": "string"},
|
||||
"type": {"title": "Error Type", "type": "string"},
|
||||
},
|
||||
"required": ["loc", "msg", "type"],
|
||||
"title": "ValidationError",
|
||||
"type": "object",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@needs_pydanticv1
|
||||
def test_openapi_schema_pv1(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"info": {
|
||||
"title": "FastAPI",
|
||||
"version": "0.1.0",
|
||||
},
|
||||
"openapi": "3.1.0",
|
||||
"paths": {
|
||||
"/items/{id}": {
|
||||
"put": {
|
||||
"operationId": "update_item_items__id__put",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "path",
|
||||
"name": "id",
|
||||
"required": True,
|
||||
"schema": {
|
||||
"title": "Id",
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Item",
|
||||
},
|
||||
},
|
||||
},
|
||||
"required": True,
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {},
|
||||
},
|
||||
},
|
||||
"description": "Successful Response",
|
||||
},
|
||||
"422": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError",
|
||||
},
|
||||
},
|
||||
},
|
||||
"description": "Validation Error",
|
||||
},
|
||||
},
|
||||
"summary": "Update Item",
|
||||
},
|
||||
},
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"HTTPValidationError": {
|
||||
"properties": {
|
||||
"detail": {
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/ValidationError",
|
||||
},
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
},
|
||||
},
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
},
|
||||
"Item": {
|
||||
"properties": {
|
||||
"description": {
|
||||
"title": "Description",
|
||||
"type": "string",
|
||||
},
|
||||
"timestamp": {
|
||||
"format": "date-time",
|
||||
"title": "Timestamp",
|
||||
"type": "string",
|
||||
},
|
||||
"title": {
|
||||
"title": "Title",
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
"required": [
|
||||
"title",
|
||||
"timestamp",
|
||||
],
|
||||
"title": "Item",
|
||||
"type": "object",
|
||||
},
|
||||
"ValidationError": {
|
||||
"properties": {
|
||||
"loc": {
|
||||
"items": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
},
|
||||
],
|
||||
},
|
||||
"title": "Location",
|
||||
"type": "array",
|
||||
},
|
||||
"msg": {
|
||||
"title": "Message",
|
||||
"type": "string",
|
||||
},
|
||||
"type": {
|
||||
"title": "Error Type",
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
"required": [
|
||||
"loc",
|
||||
"msg",
|
||||
"type",
|
||||
],
|
||||
"title": "ValidationError",
|
||||
"type": "object",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
@ -1,20 +1,45 @@
|
|||
import importlib
|
||||
from types import ModuleType
|
||||
|
||||
import pytest
|
||||
from pytest import MonkeyPatch
|
||||
|
||||
from ...utils import needs_pydanticv2
|
||||
from ...utils import needs_py39, needs_pydanticv2
|
||||
|
||||
|
||||
@pytest.fixture(
|
||||
name="mod_path",
|
||||
params=[
|
||||
pytest.param("app02"),
|
||||
pytest.param("app02_an"),
|
||||
pytest.param("app02_an_py39", marks=needs_py39),
|
||||
],
|
||||
)
|
||||
def get_mod_path(request: pytest.FixtureRequest):
|
||||
mod_path = f"docs_src.settings.{request.param}"
|
||||
return mod_path
|
||||
|
||||
|
||||
@pytest.fixture(name="main_mod")
|
||||
def get_main_mod(mod_path: str) -> ModuleType:
|
||||
main_mod = importlib.import_module(f"{mod_path}.main")
|
||||
return main_mod
|
||||
|
||||
|
||||
@pytest.fixture(name="test_main_mod")
|
||||
def get_test_main_mod(mod_path: str) -> ModuleType:
|
||||
test_main_mod = importlib.import_module(f"{mod_path}.test_main")
|
||||
return test_main_mod
|
||||
|
||||
|
||||
@needs_pydanticv2
|
||||
def test_settings(monkeypatch: MonkeyPatch):
|
||||
from docs_src.settings.app02 import main
|
||||
|
||||
def test_settings(main_mod: ModuleType, monkeypatch: MonkeyPatch):
|
||||
monkeypatch.setenv("ADMIN_EMAIL", "admin@example.com")
|
||||
settings = main.get_settings()
|
||||
settings = main_mod.get_settings()
|
||||
assert settings.app_name == "Awesome API"
|
||||
assert settings.items_per_user == 50
|
||||
|
||||
|
||||
@needs_pydanticv2
|
||||
def test_override_settings():
|
||||
from docs_src.settings.app02 import test_main
|
||||
|
||||
test_main.test_app()
|
||||
def test_override_settings(test_main_mod: ModuleType):
|
||||
test_main_mod.test_app()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
import importlib
|
||||
from types import ModuleType
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
from pytest import MonkeyPatch
|
||||
|
||||
from ...utils import needs_py39, needs_pydanticv1, needs_pydanticv2
|
||||
|
||||
|
||||
@pytest.fixture(
|
||||
name="mod_path",
|
||||
params=[
|
||||
pytest.param("app03"),
|
||||
pytest.param("app03_an"),
|
||||
pytest.param("app03_an_py39", marks=needs_py39),
|
||||
],
|
||||
)
|
||||
def get_mod_path(request: pytest.FixtureRequest):
|
||||
mod_path = f"docs_src.settings.{request.param}"
|
||||
return mod_path
|
||||
|
||||
|
||||
@pytest.fixture(name="main_mod")
|
||||
def get_main_mod(mod_path: str) -> ModuleType:
|
||||
main_mod = importlib.import_module(f"{mod_path}.main")
|
||||
return main_mod
|
||||
|
||||
|
||||
@needs_pydanticv2
|
||||
def test_settings(main_mod: ModuleType, monkeypatch: MonkeyPatch):
|
||||
monkeypatch.setenv("ADMIN_EMAIL", "admin@example.com")
|
||||
settings = main_mod.get_settings()
|
||||
assert settings.app_name == "Awesome API"
|
||||
assert settings.admin_email == "admin@example.com"
|
||||
assert settings.items_per_user == 50
|
||||
|
||||
|
||||
@needs_pydanticv1
|
||||
def test_settings_pv1(mod_path: str, monkeypatch: MonkeyPatch):
|
||||
monkeypatch.setenv("ADMIN_EMAIL", "admin@example.com")
|
||||
config_mod = importlib.import_module(f"{mod_path}.config_pv1")
|
||||
settings = config_mod.Settings()
|
||||
assert settings.app_name == "Awesome API"
|
||||
assert settings.admin_email == "admin@example.com"
|
||||
assert settings.items_per_user == 50
|
||||
|
||||
|
||||
@needs_pydanticv2
|
||||
def test_endpoint(main_mod: ModuleType, monkeypatch: MonkeyPatch):
|
||||
monkeypatch.setenv("ADMIN_EMAIL", "admin@example.com")
|
||||
client = TestClient(main_mod.app)
|
||||
response = client.get("/info")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"app_name": "Awesome API",
|
||||
"admin_email": "admin@example.com",
|
||||
"items_per_user": 50,
|
||||
}
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
from fastapi.testclient import TestClient
|
||||
|
||||
from docs_src.using_request_directly.tutorial001 import app
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
def test_path_operation():
|
||||
response = client.get("/items/foo")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"client_host": "testclient", "item_id": "foo"}
|
||||
|
||||
|
||||
def test_openapi():
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"info": {
|
||||
"title": "FastAPI",
|
||||
"version": "0.1.0",
|
||||
},
|
||||
"openapi": "3.1.0",
|
||||
"paths": {
|
||||
"/items/{item_id}": {
|
||||
"get": {
|
||||
"operationId": "read_root_items__item_id__get",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "path",
|
||||
"name": "item_id",
|
||||
"required": True,
|
||||
"schema": {
|
||||
"title": "Item Id",
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {},
|
||||
},
|
||||
},
|
||||
"description": "Successful Response",
|
||||
},
|
||||
"422": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError",
|
||||
},
|
||||
},
|
||||
},
|
||||
"description": "Validation Error",
|
||||
},
|
||||
},
|
||||
"summary": "Read Root",
|
||||
},
|
||||
},
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"HTTPValidationError": {
|
||||
"properties": {
|
||||
"detail": {
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/ValidationError",
|
||||
},
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
},
|
||||
},
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
},
|
||||
"ValidationError": {
|
||||
"properties": {
|
||||
"loc": {
|
||||
"items": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
},
|
||||
],
|
||||
},
|
||||
"title": "Location",
|
||||
"type": "array",
|
||||
},
|
||||
"msg": {
|
||||
"title": "Message",
|
||||
"type": "string",
|
||||
},
|
||||
"type": {
|
||||
"title": "Error Type",
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
"required": [
|
||||
"loc",
|
||||
"msg",
|
||||
"type",
|
||||
],
|
||||
"title": "ValidationError",
|
||||
"type": "object",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
@ -1,16 +1,45 @@
|
|||
import importlib
|
||||
from types import ModuleType
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from docs_src.websockets.tutorial003 import app, html
|
||||
|
||||
client = TestClient(app)
|
||||
from ...utils import needs_py39
|
||||
|
||||
|
||||
def test_get():
|
||||
@pytest.fixture(
|
||||
name="mod",
|
||||
params=[
|
||||
pytest.param("tutorial003"),
|
||||
pytest.param("tutorial003_py39", marks=needs_py39),
|
||||
],
|
||||
)
|
||||
def get_mod(request: pytest.FixtureRequest):
|
||||
mod = importlib.import_module(f"docs_src.websockets.{request.param}")
|
||||
|
||||
return mod
|
||||
|
||||
|
||||
@pytest.fixture(name="html")
|
||||
def get_html(mod: ModuleType):
|
||||
return mod.html
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client(mod: ModuleType):
|
||||
client = TestClient(mod.app)
|
||||
|
||||
return client
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_get(client: TestClient, html: str):
|
||||
response = client.get("/")
|
||||
assert response.text == html
|
||||
|
||||
|
||||
def test_websocket_handle_disconnection():
|
||||
@needs_py39
|
||||
def test_websocket_handle_disconnection(client: TestClient):
|
||||
with client.websocket_connect("/ws/1234") as connection, client.websocket_connect(
|
||||
"/ws/5678"
|
||||
) as connection_two:
|
||||
|
|
|
|||
|
|
@ -1,50 +0,0 @@
|
|||
import pytest
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from ...utils import needs_py39
|
||||
|
||||
|
||||
@pytest.fixture(name="app")
|
||||
def get_app():
|
||||
from docs_src.websockets.tutorial003_py39 import app
|
||||
|
||||
return app
|
||||
|
||||
|
||||
@pytest.fixture(name="html")
|
||||
def get_html():
|
||||
from docs_src.websockets.tutorial003_py39 import html
|
||||
|
||||
return html
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def get_client(app: FastAPI):
|
||||
client = TestClient(app)
|
||||
|
||||
return client
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_get(client: TestClient, html: str):
|
||||
response = client.get("/")
|
||||
assert response.text == html
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_websocket_handle_disconnection(client: TestClient):
|
||||
with client.websocket_connect("/ws/1234") as connection, client.websocket_connect(
|
||||
"/ws/5678"
|
||||
) as connection_two:
|
||||
connection.send_text("Hello from 1234")
|
||||
data1 = connection.receive_text()
|
||||
assert data1 == "You wrote: Hello from 1234"
|
||||
data2 = connection_two.receive_text()
|
||||
client1_says = "Client #1234 says: Hello from 1234"
|
||||
assert data2 == client1_says
|
||||
data1 = connection.receive_text()
|
||||
assert data1 == client1_says
|
||||
connection_two.close()
|
||||
data1 = connection.receive_text()
|
||||
assert data1 == "Client #5678 left the chat"
|
||||
Loading…
Reference in New Issue