From: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com>
Date: Wed, 10 Dec 2025 08:55:32 +0000 (+0100)
Subject: 📝 Add variants for code examples in "Advanced User Guide" (#14413)
X-Git-Tag: 0.124.1~4
X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=9475024640f2e204944c5aa2cd9c67a8826189d9;p=thirdparty%2Ffastapi%2Ffastapi.git
📝 Add variants for code examples in "Advanced User Guide" (#14413)
---
diff --git a/docs/en/docs/advanced/additional-responses.md b/docs/en/docs/advanced/additional-responses.md
index 799532c5b2..cb3a40d13e 100644
--- a/docs/en/docs/advanced/additional-responses.md
+++ b/docs/en/docs/advanced/additional-responses.md
@@ -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 }
diff --git a/docs/en/docs/advanced/dataclasses.md b/docs/en/docs/advanced/dataclasses.md
index b7b9b65c52..574beb65f4 100644
--- a/docs/en/docs/advanced/dataclasses.md
+++ b/docs/en/docs/advanced/dataclasses.md
@@ -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 `dataclasses` 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 internal support for `dataclasses`.
@@ -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`.
diff --git a/docs/en/docs/advanced/openapi-callbacks.md b/docs/en/docs/advanced/openapi-callbacks.md
index 059d893c26..5bd7c2cfd4 100644
--- a/docs/en/docs/advanced/openapi-callbacks.md
+++ b/docs/en/docs/advanced/openapi-callbacks.md
@@ -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
diff --git a/docs/en/docs/advanced/path-operation-advanced-configuration.md b/docs/en/docs/advanced/path-operation-advanced-configuration.md
index b9961f9f38..5879bc5c71 100644
--- a/docs/en/docs/advanced/path-operation-advanced-configuration.md
+++ b/docs/en/docs/advanced/path-operation-advanced-configuration.md
@@ -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] *}
////
diff --git a/docs/en/docs/advanced/response-directly.md b/docs/en/docs/advanced/response-directly.md
index 3197e1bd46..156b4dac7e 100644
--- a/docs/en/docs/advanced/response-directly.md
+++ b/docs/en/docs/advanced/response-directly.md
@@ -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
diff --git a/docs/en/docs/advanced/settings.md b/docs/en/docs/advanced/settings.md
index a218c3d016..0220c52ce1 100644
--- a/docs/en/docs/advanced/settings.md
+++ b/docs/en/docs/advanced/settings.md
@@ -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
diff --git a/docs/en/docs/how-to/configure-swagger-ui.md b/docs/en/docs/how-to/configure-swagger-ui.md
index 2d7b99f8fa..3dbfcffeca 100644
--- a/docs/en/docs/how-to/configure-swagger-ui.md
+++ b/docs/en/docs/how-to/configure-swagger-ui.md
@@ -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`.
diff --git a/docs/en/docs/how-to/custom-request-and-route.md b/docs/en/docs/how-to/custom-request-and-route.md
index 884c8ed04f..bfc60729f8 100644
--- a/docs/en/docs/how-to/custom-request-and-route.md
+++ b/docs/en/docs/how-to/custom-request-and-route.md
@@ -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] *}
diff --git a/docs/en/docs/tutorial/bigger-applications.md b/docs/en/docs/tutorial/bigger-applications.md
index 74daa54835..3cc9d7ecfb 100644
--- a/docs/en/docs/tutorial/bigger-applications.md
+++ b/docs/en/docs/tutorial/bigger-applications.md
@@ -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()`.
diff --git a/docs/en/docs/tutorial/cookie-param-models.md b/docs/en/docs/tutorial/cookie-param-models.md
index 96dc5cf3d8..016a65d7f0 100644
--- a/docs/en/docs/tutorial/cookie-param-models.md
+++ b/docs/en/docs/tutorial/cookie-param-models.md
@@ -50,7 +50,7 @@ Your API now has the power to control its own 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)}
diff --git a/docs_src/custom_request_and_route/tutorial001_an_py310.py b/docs_src/custom_request_and_route/tutorial001_an_py310.py
new file mode 100644
index 0000000000..381bab6d84
--- /dev/null
+++ b/docs_src/custom_request_and_route/tutorial001_an_py310.py
@@ -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)}
diff --git a/docs_src/custom_request_and_route/tutorial001_an_py39.py b/docs_src/custom_request_and_route/tutorial001_an_py39.py
new file mode 100644
index 0000000000..076727e643
--- /dev/null
+++ b/docs_src/custom_request_and_route/tutorial001_an_py39.py
@@ -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)}
diff --git a/docs_src/custom_request_and_route/tutorial001_py310.py b/docs_src/custom_request_and_route/tutorial001_py310.py
new file mode 100644
index 0000000000..c678088ce7
--- /dev/null
+++ b/docs_src/custom_request_and_route/tutorial001_py310.py
@@ -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)}
diff --git a/docs_src/custom_request_and_route/tutorial001_py39.py b/docs_src/custom_request_and_route/tutorial001_py39.py
new file mode 100644
index 0000000000..54b20b9425
--- /dev/null
+++ b/docs_src/custom_request_and_route/tutorial001_py39.py
@@ -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)}
diff --git a/docs_src/custom_request_and_route/tutorial002_an.py b/docs_src/custom_request_and_route/tutorial002_an.py
new file mode 100644
index 0000000000..127f7a9ce0
--- /dev/null
+++ b/docs_src/custom_request_and_route/tutorial002_an.py
@@ -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)
diff --git a/docs_src/custom_request_and_route/tutorial002_an_py310.py b/docs_src/custom_request_and_route/tutorial002_an_py310.py
new file mode 100644
index 0000000000..69b7de4859
--- /dev/null
+++ b/docs_src/custom_request_and_route/tutorial002_an_py310.py
@@ -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)
diff --git a/docs_src/custom_request_and_route/tutorial002_an_py39.py b/docs_src/custom_request_and_route/tutorial002_an_py39.py
new file mode 100644
index 0000000000..e7de09de44
--- /dev/null
+++ b/docs_src/custom_request_and_route/tutorial002_an_py39.py
@@ -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)
diff --git a/docs_src/custom_request_and_route/tutorial002_py310.py b/docs_src/custom_request_and_route/tutorial002_py310.py
new file mode 100644
index 0000000000..13a5ca5426
--- /dev/null
+++ b/docs_src/custom_request_and_route/tutorial002_py310.py
@@ -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)
diff --git a/docs_src/custom_request_and_route/tutorial002_py39.py b/docs_src/custom_request_and_route/tutorial002_py39.py
new file mode 100644
index 0000000000..c4e4748281
--- /dev/null
+++ b/docs_src/custom_request_and_route/tutorial002_py39.py
@@ -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)
diff --git a/docs_src/custom_request_and_route/tutorial003_py310.py b/docs_src/custom_request_and_route/tutorial003_py310.py
new file mode 100644
index 0000000000..f4e60be61b
--- /dev/null
+++ b/docs_src/custom_request_and_route/tutorial003_py310.py
@@ -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)
diff --git a/docs_src/dataclasses/tutorial001_py310.py b/docs_src/dataclasses/tutorial001_py310.py
new file mode 100644
index 0000000000..ab709a7c85
--- /dev/null
+++ b/docs_src/dataclasses/tutorial001_py310.py
@@ -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
diff --git a/docs_src/dataclasses/tutorial002_py310.py b/docs_src/dataclasses/tutorial002_py310.py
new file mode 100644
index 0000000000..e16249f1e1
--- /dev/null
+++ b/docs_src/dataclasses/tutorial002_py310.py
@@ -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"],
+ }
diff --git a/docs_src/dataclasses/tutorial002_py39.py b/docs_src/dataclasses/tutorial002_py39.py
new file mode 100644
index 0000000000..0c23765d84
--- /dev/null
+++ b/docs_src/dataclasses/tutorial002_py39.py
@@ -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"],
+ }
diff --git a/docs_src/dataclasses/tutorial003_py310.py b/docs_src/dataclasses/tutorial003_py310.py
new file mode 100644
index 0000000000..9b9a3fd635
--- /dev/null
+++ b/docs_src/dataclasses/tutorial003_py310.py
@@ -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",
+ },
+ ],
+ },
+ ]
diff --git a/docs_src/dataclasses/tutorial003_py39.py b/docs_src/dataclasses/tutorial003_py39.py
new file mode 100644
index 0000000000..991708c009
--- /dev/null
+++ b/docs_src/dataclasses/tutorial003_py39.py
@@ -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",
+ },
+ ],
+ },
+ ]
diff --git a/docs_src/openapi_callbacks/tutorial001_py310.py b/docs_src/openapi_callbacks/tutorial001_py310.py
new file mode 100644
index 0000000000..3efe0ee25f
--- /dev/null
+++ b/docs_src/openapi_callbacks/tutorial001_py310.py
@@ -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"}
diff --git a/docs_src/path_operation_advanced_configuration/tutorial004_py310.py b/docs_src/path_operation_advanced_configuration/tutorial004_py310.py
new file mode 100644
index 0000000000..a815a564b7
--- /dev/null
+++ b/docs_src/path_operation_advanced_configuration/tutorial004_py310.py
@@ -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
diff --git a/docs_src/path_operation_advanced_configuration/tutorial004_py39.py b/docs_src/path_operation_advanced_configuration/tutorial004_py39.py
new file mode 100644
index 0000000000..d5fe6705ca
--- /dev/null
+++ b/docs_src/path_operation_advanced_configuration/tutorial004_py39.py
@@ -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
diff --git a/docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py b/docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py
new file mode 100644
index 0000000000..831966553f
--- /dev/null
+++ b/docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py
@@ -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
diff --git a/docs_src/path_operation_advanced_configuration/tutorial007_py39.py b/docs_src/path_operation_advanced_configuration/tutorial007_py39.py
new file mode 100644
index 0000000000..ff64ef7923
--- /dev/null
+++ b/docs_src/path_operation_advanced_configuration/tutorial007_py39.py
@@ -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
diff --git a/docs_src/response_directly/tutorial001_py310.py b/docs_src/response_directly/tutorial001_py310.py
new file mode 100644
index 0000000000..81e094dc69
--- /dev/null
+++ b/docs_src/response_directly/tutorial001_py310.py
@@ -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)
diff --git a/docs_src/settings/app03/config.py b/docs_src/settings/app03/config.py
index 942aea3e58..08f8f88c28 100644
--- a/docs_src/settings/app03/config.py
+++ b/docs_src/settings/app03/config.py
@@ -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")
diff --git a/docs_src/settings/app03/config_pv1.py b/docs_src/settings/app03/config_pv1.py
new file mode 100644
index 0000000000..e1c3ee3006
--- /dev/null
+++ b/docs_src/settings/app03/config_pv1.py
@@ -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"
diff --git a/docs_src/settings/app03_an/main.py b/docs_src/settings/app03_an/main.py
index 2f64b9cd17..62f3476396 100644
--- a/docs_src/settings/app03_an/main.py
+++ b/docs_src/settings/app03_an/main.py
@@ -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
diff --git a/docs_src/settings/app03_an_py39/config.py b/docs_src/settings/app03_an_py39/config.py
index 942aea3e58..08f8f88c28 100644
--- a/docs_src/settings/app03_an_py39/config.py
+++ b/docs_src/settings/app03_an_py39/config.py
@@ -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")
diff --git a/docs_src/settings/app03_an_py39/config_pv1.py b/docs_src/settings/app03_an_py39/config_pv1.py
new file mode 100644
index 0000000000..e1c3ee3006
--- /dev/null
+++ b/docs_src/settings/app03_an_py39/config_pv1.py
@@ -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"
diff --git a/docs_src/settings/app03_an_py39/main.py b/docs_src/settings/app03_an_py39/main.py
index 62f3476396..2f64b9cd17 100644
--- a/docs_src/settings/app03_an_py39/main.py
+++ b/docs_src/settings/app03_an_py39/main.py
@@ -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
diff --git a/pyproject.toml b/pyproject.toml
index cafcf65c63..f8d5fa7c7a 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -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"]
diff --git a/tests/test_tutorial/test_additional_responses/test_tutorial002.py b/tests/test_tutorial/test_additional_responses/test_tutorial002.py
index 588a3160a9..91d6ff101f 100644
--- a/tests/test_tutorial/test_additional_responses/test_tutorial002.py
+++ b/tests/test_tutorial/test_additional_responses/test_tutorial002.py
@@ -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
+from tests.utils import needs_py310
-client = TestClient(app)
+@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}")
-def test_path_operation():
+ 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() == {
diff --git a/tests/test_tutorial/test_additional_responses/test_tutorial004.py b/tests/test_tutorial/test_additional_responses/test_tutorial004.py
index 55b556d8e1..2d9491467e 100644
--- a/tests/test_tutorial/test_additional_responses/test_tutorial004.py
+++ b/tests/test_tutorial/test_additional_responses/test_tutorial004.py
@@ -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
+from tests.utils import needs_py310
-client = TestClient(app)
+@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}")
-def test_path_operation():
+ 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() == {
diff --git a/tests/test_tutorial/test_custom_request_and_route/test_tutorial001.py b/tests/test_tutorial/test_custom_request_and_route/test_tutorial001.py
index e6da630e88..f9fd0d1af8 100644
--- a/tests/test_tutorial/test_custom_request_and_route/test_tutorial001.py
+++ b/tests/test_tutorial/test_custom_request_and_route/test_tutorial001.py
@@ -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"}
diff --git a/tests/test_tutorial/test_custom_request_and_route/test_tutorial002.py b/tests/test_tutorial/test_custom_request_and_route/test_tutorial002.py
index 647f1c5ddf..c35752ed13 100644
--- a/tests/test_tutorial/test_custom_request_and_route/test_tutorial002.py
+++ b/tests/test_tutorial/test_custom_request_and_route/test_tutorial002.py
@@ -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
+from tests.utils import needs_py39, needs_py310
+
+
+@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(app)
+ client = TestClient(mod.app)
+ return client
-def test_endpoint_works():
+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(
{
diff --git a/tests/test_tutorial/test_custom_request_and_route/test_tutorial003.py b/tests/test_tutorial/test_custom_request_and_route/test_tutorial003.py
index db5dad7cf6..9e895b2daf 100644
--- a/tests/test_tutorial/test_custom_request_and_route/test_tutorial003.py
+++ b/tests/test_tutorial/test_custom_request_and_route/test_tutorial003.py
@@ -1,17 +1,32 @@
+import importlib
+
+import pytest
from fastapi.testclient import TestClient
-from docs_src.custom_request_and_route.tutorial003 import app
+from tests.utils import needs_py310
+
+
+@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(app)
+ client = TestClient(mod.app)
+ return client
-def test_get():
+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
diff --git a/tests/test_tutorial/test_dataclasses/test_tutorial001.py b/tests/test_tutorial/test_dataclasses/test_tutorial001.py
index 762654d29d..b36dee7684 100644
--- a/tests/test_tutorial/test_dataclasses/test_tutorial001.py
+++ b/tests/test_tutorial/test_dataclasses/test_tutorial001.py
@@ -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
+from tests.utils import needs_py310
+
+
+@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(app)
+ client = TestClient(mod.app)
+ client.headers.clear()
+ return client
-def test_post_item():
+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() == {
diff --git a/tests/test_tutorial/test_dataclasses/test_tutorial002.py b/tests/test_tutorial/test_dataclasses/test_tutorial002.py
index e6d303cfc1..baaea45d8f 100644
--- a/tests/test_tutorial/test_dataclasses/test_tutorial002.py
+++ b/tests/test_tutorial/test_dataclasses/test_tutorial002.py
@@ -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
+from tests.utils import needs_py39, needs_py310
+
+
+@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(app)
+ client = TestClient(mod.app)
+ client.headers.clear()
+ return client
-def test_get_item():
+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() == {
diff --git a/tests/test_tutorial/test_dataclasses/test_tutorial003.py b/tests/test_tutorial/test_dataclasses/test_tutorial003.py
index e1fa45201f..5728d2b6b3 100644
--- a/tests/test_tutorial/test_dataclasses/test_tutorial003.py
+++ b/tests/test_tutorial/test_dataclasses/test_tutorial003.py
@@ -1,13 +1,28 @@
+import importlib
+
+import pytest
from fastapi.testclient import TestClient
-from docs_src.dataclasses.tutorial003 import app
+from ...utils import needs_py39, needs_py310, needs_pydanticv1, needs_pydanticv2
+
-from ...utils import needs_pydanticv1, needs_pydanticv2
+@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(app)
+ client = TestClient(mod.app)
+ client.headers.clear()
+ return client
-def test_post_authors_item():
+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() == {
diff --git a/tests/test_tutorial/test_openapi_callbacks/test_tutorial001.py b/tests/test_tutorial/test_openapi_callbacks/test_tutorial001.py
index 73af420ae1..2df2b98893 100644
--- a/tests/test_tutorial/test_openapi_callbacks/test_tutorial001.py
+++ b/tests/test_tutorial/test_openapi_callbacks/test_tutorial001.py
@@ -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
+from tests.utils import needs_py310
+
+
+@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
+
-client = TestClient(app)
+@pytest.fixture(name="client")
+def get_client(mod: ModuleType):
+ client = TestClient(mod.app)
+ client.headers.clear()
+ return client
-def test_get():
+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() == {
diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial004.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial004.py
index 4f69e4646c..da5782d189 100644
--- a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial004.py
+++ b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial004.py
@@ -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_py39, needs_py310, needs_pydanticv1, needs_pydanticv2
+
-from ...utils import needs_pydanticv1, needs_pydanticv2
+@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(app)
+ client = TestClient(mod.app)
+ client.headers.clear()
+ return client
-def test_query_params_str_validations():
+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() == {
diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007.py
index 8240b60a62..a90337a63d 100644
--- a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007.py
+++ b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007.py
@@ -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
diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007_pv1.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007_pv1.py
index ef012f8a6c..b38e4947cc 100644
--- a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007_pv1.py
+++ b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007_pv1.py
@@ -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
diff --git a/tests/test_tutorial/test_response_directly/__init__.py b/tests/test_tutorial/test_response_directly/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/test_tutorial/test_response_directly/test_tutorial001.py b/tests/test_tutorial/test_response_directly/test_tutorial001.py
new file mode 100644
index 0000000000..2cc4f3b0c1
--- /dev/null
+++ b/tests/test_tutorial/test_response_directly/test_tutorial001.py
@@ -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",
+ },
+ },
+ },
+ }
diff --git a/tests/test_tutorial/test_settings/test_app02.py b/tests/test_tutorial/test_settings/test_app02.py
index eced88c044..5e1232ea01 100644
--- a/tests/test_tutorial/test_settings/test_app02.py
+++ b/tests/test_tutorial/test_settings/test_app02.py
@@ -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
-@needs_pydanticv2
-def test_settings(monkeypatch: MonkeyPatch):
- from docs_src.settings.app02 import main
+@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(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()
diff --git a/tests/test_tutorial/test_settings/test_app03.py b/tests/test_tutorial/test_settings/test_app03.py
new file mode 100644
index 0000000000..d9872c15f2
--- /dev/null
+++ b/tests/test_tutorial/test_settings/test_app03.py
@@ -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,
+ }
diff --git a/tests/test_tutorial/test_using_request_directly/__init__.py b/tests/test_tutorial/test_using_request_directly/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/test_tutorial/test_using_request_directly/test_tutorial001.py b/tests/test_tutorial/test_using_request_directly/test_tutorial001.py
new file mode 100644
index 0000000000..54c53ae1e3
--- /dev/null
+++ b/tests/test_tutorial/test_using_request_directly/test_tutorial001.py
@@ -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",
+ },
+ },
+ },
+ }
diff --git a/tests/test_tutorial/test_websockets/test_tutorial003.py b/tests/test_tutorial/test_websockets/test_tutorial003.py
index dbcad3b02c..85efc18590 100644
--- a/tests/test_tutorial/test_websockets/test_tutorial003.py
+++ b/tests/test_tutorial/test_websockets/test_tutorial003.py
@@ -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
+from ...utils import needs_py39
+
+
+@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)
-client = TestClient(app)
+ return client
-def test_get():
+@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:
diff --git a/tests/test_tutorial/test_websockets/test_tutorial003_py39.py b/tests/test_tutorial/test_websockets/test_tutorial003_py39.py
deleted file mode 100644
index 06c4a92796..0000000000
--- a/tests/test_tutorial/test_websockets/test_tutorial003_py39.py
+++ /dev/null
@@ -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"