mirror of
https://github.com/fastapi/fastapi.git
synced 2025-12-28 00:30:58 -05:00
Compare commits
53 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b87072bc12 | ||
|
|
04e2bfafbc | ||
|
|
181a32236a | ||
|
|
1f54a8e0a1 | ||
|
|
d63475bb7d | ||
|
|
5a3c5f1523 | ||
|
|
12bc9285f7 | ||
|
|
31df2ea940 | ||
|
|
50b90dd6a4 | ||
|
|
7dd881334d | ||
|
|
530fc8ff3f | ||
|
|
ef460b4d23 | ||
|
|
b591de2ace | ||
|
|
34c857b7cb | ||
|
|
c78bc0c82d | ||
|
|
194446e51a | ||
|
|
777e2151e6 | ||
|
|
5ce5bdba0b | ||
|
|
e4300769ac | ||
|
|
c6dd627bdd | ||
|
|
6576f724bb | ||
|
|
91a6736d0e | ||
|
|
8fb755703d | ||
|
|
748bedd37c | ||
|
|
bf58788f29 | ||
|
|
5f78ba4a31 | ||
|
|
db9f827263 | ||
|
|
dd9e94cf21 | ||
|
|
e482d74241 | ||
|
|
bd2acbcabb | ||
|
|
f913d469a8 | ||
|
|
66cb266641 | ||
|
|
74954894c5 | ||
|
|
ceedfccde0 | ||
|
|
2ee0eedf23 | ||
|
|
c0f3019764 | ||
|
|
dd6d0cb23c | ||
|
|
fe15620df3 | ||
|
|
6af857f206 | ||
|
|
7ce756f9dd | ||
|
|
c0b1fddb31 | ||
|
|
9aea85a84e | ||
|
|
fddd1c12de | ||
|
|
b13a4baf32 | ||
|
|
5ffa18f10f | ||
|
|
828915baf5 | ||
|
|
a071ddf3cd | ||
|
|
3651b8a30f | ||
|
|
0d73b9ff1c | ||
|
|
43235cf236 | ||
|
|
269a155583 | ||
|
|
12433d51dd | ||
|
|
3699e17212 |
@@ -203,6 +203,21 @@ File responses will include appropriate `Content-Length`, `Last-Modified` and `E
|
||||
{!../../../docs_src/custom_response/tutorial009.py!}
|
||||
```
|
||||
|
||||
## Default response class
|
||||
|
||||
When creating a **FastAPI** class instance or an `APIRouter` you can specify which response class to use by default.
|
||||
|
||||
The parameter that defines this is `default_response_class`.
|
||||
|
||||
In the example below, **FastAPI** will use `ORJSONResponse` by default, in all *path operations*, instead of `JSONResponse`.
|
||||
|
||||
```Python hl_lines="2 4"
|
||||
{!../../../docs_src/custom_response/tutorial010.py!}
|
||||
```
|
||||
|
||||
!!! tip
|
||||
You can still override `response_class` in *path operations* as before.
|
||||
|
||||
## Additional documentation
|
||||
|
||||
You can also declare the media type and many other details in OpenAPI using `responses`: [Additional Responses in OpenAPI](additional-responses.md){.internal-link target=_blank}.
|
||||
|
||||
@@ -4,6 +4,9 @@ You can define event handlers (functions) that need to be executed before the ap
|
||||
|
||||
These functions can be declared with `async def` or normal `def`.
|
||||
|
||||
!!! warning
|
||||
Only event handlers for the main application will be executed, not for [Sub Applications - Mounts](./sub-applications.md){.internal-link target=_blank}.
|
||||
|
||||
## `startup` event
|
||||
|
||||
To add a function that should be run before the application starts, declare it with the event `"startup"`:
|
||||
@@ -41,4 +44,4 @@ Here, the `shutdown` event handler function will write a text line `"Application
|
||||
So, we declare the event handler function with standard `def` instead of `async def`.
|
||||
|
||||
!!! info
|
||||
You can read more about these event handlers in <a href="https://www.starlette.io/events/" class="external-link" target="_blank">Starlette's Events' docs</a>.
|
||||
You can read more about these event handlers in <a href="https://www.starlette.io/events/" class="external-link" target="_blank">Starlette's Events' docs</a>.
|
||||
|
||||
@@ -51,38 +51,7 @@ In your WebSocket route you can `await` for messages and send messages.
|
||||
|
||||
You can receive and send binary, text, and JSON data.
|
||||
|
||||
## Using `Depends` and others
|
||||
|
||||
In WebSocket endpoints you can import from `fastapi` and use:
|
||||
|
||||
* `Depends`
|
||||
* `Security`
|
||||
* `Cookie`
|
||||
* `Header`
|
||||
* `Path`
|
||||
* `Query`
|
||||
|
||||
They work the same way as for other FastAPI endpoints/*path operations*:
|
||||
|
||||
```Python hl_lines="53 54 55 56 57 58 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76"
|
||||
{!../../../docs_src/websockets/tutorial002.py!}
|
||||
```
|
||||
|
||||
!!! info
|
||||
In a WebSocket it doesn't really make sense to raise an `HTTPException`. So it's better to close the WebSocket connection directly.
|
||||
|
||||
You can use a closing code from the <a href="https://tools.ietf.org/html/rfc6455#section-7.4.1" class="external-link" target="_blank">valid codes defined in the specification</a>.
|
||||
|
||||
In the future, there will be a `WebSocketException` that you will be able to `raise` from anywhere, and add exception handlers for it. It depends on the <a href="https://github.com/encode/starlette/pull/527" class="external-link" target="_blank">PR #527</a> in Starlette.
|
||||
|
||||
## More info
|
||||
|
||||
To learn more about the options, check Starlette's documentation for:
|
||||
|
||||
* <a href="https://www.starlette.io/websockets/" class="external-link" target="_blank">The `WebSocket` class</a>.
|
||||
* <a href="https://www.starlette.io/endpoints/#websocketendpoint" class="external-link" target="_blank">Class-based WebSocket handling</a>.
|
||||
|
||||
## Test it
|
||||
## Try it
|
||||
|
||||
If your file is named `main.py`, run your application with:
|
||||
|
||||
@@ -115,3 +84,62 @@ You can send (and receive) many messages:
|
||||
<img src="/img/tutorial/websockets/image04.png">
|
||||
|
||||
And all of them will use the same WebSocket connection.
|
||||
|
||||
## Using `Depends` and others
|
||||
|
||||
In WebSocket endpoints you can import from `fastapi` and use:
|
||||
|
||||
* `Depends`
|
||||
* `Security`
|
||||
* `Cookie`
|
||||
* `Header`
|
||||
* `Path`
|
||||
* `Query`
|
||||
|
||||
They work the same way as for other FastAPI endpoints/*path operations*:
|
||||
|
||||
```Python hl_lines="56 57 58 59 60 61 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79"
|
||||
{!../../../docs_src/websockets/tutorial002.py!}
|
||||
```
|
||||
|
||||
!!! info
|
||||
In a WebSocket it doesn't really make sense to raise an `HTTPException`. So it's better to close the WebSocket connection directly.
|
||||
|
||||
You can use a closing code from the <a href="https://tools.ietf.org/html/rfc6455#section-7.4.1" class="external-link" target="_blank">valid codes defined in the specification</a>.
|
||||
|
||||
In the future, there will be a `WebSocketException` that you will be able to `raise` from anywhere, and add exception handlers for it. It depends on the <a href="https://github.com/encode/starlette/pull/527" class="external-link" target="_blank">PR #527</a> in Starlette.
|
||||
|
||||
### Try the WebSockets with dependencies
|
||||
|
||||
If your file is named `main.py`, run your application with:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ uvicorn main:app --reload
|
||||
|
||||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
Open your browser at <a href="http://127.0.0.1:8000" class="external-link" target="_blank">http://127.0.0.1:8000</a>.
|
||||
|
||||
There you can set:
|
||||
|
||||
* The "Item ID", used in the path.
|
||||
* The "Token" used as a query parameter.
|
||||
|
||||
!!! tip
|
||||
Notice that the query `token` will be handled by a dependency.
|
||||
|
||||
With that you can connect the WebSocket and then send and receive messages:
|
||||
|
||||
<img src="/img/tutorial/websockets/image05.png">
|
||||
|
||||
## More info
|
||||
|
||||
To learn more about the options, check Starlette's documentation for:
|
||||
|
||||
* <a href="https://www.starlette.io/websockets/" class="external-link" target="_blank">The `WebSocket` class</a>.
|
||||
* <a href="https://www.starlette.io/endpoints/#websocketendpoint" class="external-link" target="_blank">Class-based WebSocket handling</a>.
|
||||
|
||||
@@ -25,8 +25,6 @@ Here's an incomplete list of some of them.
|
||||
|
||||
* <a href="https://medium.com/data-rebels/fastapi-authentication-revisited-enabling-api-key-authentication-122dc5975680" class="external-link" target="_blank">FastAPI authentication revisited: Enabling API key authentication</a> by <a href="https://medium.com/@nils_29588" class="external-link" target="_blank">Nils de Bruin</a>.
|
||||
|
||||
* <a href="https://blog.bartab.fr/fastapi-logging-on-the-fly/" class="external-link" target="_blank">FastAPI, a simple use case on logging</a> by <a href="https://blog.bartab.fr/" class="external-link" target="_blank">@euri10</a>.
|
||||
|
||||
* <a href="https://medium.com/@nico.axtmann95/deploying-a-scikit-learn-model-with-onnx-und-fastapi-1af398268915" class="external-link" target="_blank">Deploying a scikit-learn model with ONNX and FastAPI</a> by <a href="https://www.linkedin.com/in/nico-axtmann" class="external-link" target="_blank">Nico Axtmann</a>.
|
||||
|
||||
* <a href="https://geekflare.com/python-asynchronous-web-frameworks/" class="external-link" target="_blank">Top 5 Asynchronous Web Frameworks for Python</a> by <a href="https://geekflare.com/author/ankush/" class="external-link" target="_blank">Ankush Thakur</a> on <a href="https://geekflare.com" class="external-link" target="_blank">GeekFlare</a>.
|
||||
@@ -104,12 +102,6 @@ Here's an incomplete list of some of them.
|
||||
|
||||
* <a href="https://qiita.com/bee2/items/75d9c0d7ba20e7a4a0e9" class="external-link" target="_blank">[FastAPI] Python製のASGI Web フレームワーク FastAPIに入門する</a> by <a href="https://qiita.com/bee2" class="external-link" target="_blank">@bee2</a>.
|
||||
|
||||
### Chinese
|
||||
|
||||
* <a href="https://cloud.tencent.com/developer/article/1431448" class="external-link" target="_blank">使用FastAPI框架快速构建高性能的api服务</a> by <a href="https://cloud.tencent.com/developer/user/5471722" class="external-link" target="_blank">逍遥散人</a>.
|
||||
|
||||
* <a href="https://wxq0309.github.io/" class="external-link" target="_blank">FastAPI框架中文文档</a> by <a href="https://wxq0309.github.io/" class="external-link" target="_blank">何大仙</a>.
|
||||
|
||||
### Vietnamese
|
||||
|
||||
* <a href="https://fullstackstation.com/fastapi-trien-khai-bang-docker/" class="external-link" target="_blank">FASTAPI: TRIỂN KHAI BẰNG DOCKER</a> by <a href="https://fullstackstation.com/author/figonking/" class="external-link" target="_blank">Nguyễn Nhân</a>.
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 71 KiB |
BIN
docs/en/docs/img/tutorial/metadata/image02.png
Normal file
BIN
docs/en/docs/img/tutorial/metadata/image02.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 47 KiB |
BIN
docs/en/docs/img/tutorial/websockets/image05.png
Normal file
BIN
docs/en/docs/img/tutorial/websockets/image05.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 52 KiB |
@@ -2,6 +2,37 @@
|
||||
|
||||
## Latest changes
|
||||
|
||||
## 0.58.0
|
||||
|
||||
* Deep merge OpenAPI responses to preserve all the additional metadata. PR [#1577](https://github.com/tiangolo/fastapi/pull/1577).
|
||||
* Mention in docs that only main app events are run (not sub-apps). PR [#1554](https://github.com/tiangolo/fastapi/pull/1554) by [@amacfie](https://github.com/amacfie).
|
||||
* Fix body validation error response, do not include body variable when it is not embedded. PR [#1553](https://github.com/tiangolo/fastapi/pull/1553) by [@amacfie](https://github.com/amacfie).
|
||||
* Fix testing OAuth2 security scopes when using dependency overrides. PR [#1549](https://github.com/tiangolo/fastapi/pull/1549) by [@amacfie](https://github.com/amacfie).
|
||||
* Fix Model for JSON Schema keyword `not` as a JSON Schema instead of a list. PR [#1548](https://github.com/tiangolo/fastapi/pull/1548) by [@v-do](https://github.com/v-do).
|
||||
* Add support for OpenAPI `servers`. PR [#1547](https://github.com/tiangolo/fastapi/pull/1547) by [@mikaello](https://github.com/mikaello).
|
||||
|
||||
## 0.57.0
|
||||
|
||||
* Remove broken link from "External Links". PR [#1565](https://github.com/tiangolo/fastapi/pull/1565) by [@victorphoenix3](https://github.com/victorphoenix3).
|
||||
* Update/fix docs for [WebSockets with dependencies](https://fastapi.tiangolo.com/advanced/websockets/#using-depends-and-others). Original PR [#1540](https://github.com/tiangolo/fastapi/pull/1540) by [@ChihSeanHsu](https://github.com/ChihSeanHsu).
|
||||
* Add support for Python's `http.HTTPStatus` in `status_code` parameters. PR [#1534](https://github.com/tiangolo/fastapi/pull/1534) by [@retnikt](https://github.com/retnikt).
|
||||
* When using Pydantic models with `__root__`, use the internal value in `jsonable_encoder`. PR [#1524](https://github.com/tiangolo/fastapi/pull/1524) by [@patrickkwang](https://github.com/patrickkwang).
|
||||
* Update docs for path parameters. PR [#1521](https://github.com/tiangolo/fastapi/pull/1521) by [@yankeexe](https://github.com/yankeexe).
|
||||
* Update docs for first steps, links and rewording. PR [#1518](https://github.com/tiangolo/fastapi/pull/1518) by [@yankeexe](https://github.com/yankeexe).
|
||||
* Enable `showCommonExtensions` in Swagger UI to show additional validations like `maxLength`, etc. PR [#1466](https://github.com/tiangolo/fastapi/pull/1466) by [@TiewKH](https://github.com/TiewKH).
|
||||
* Make `OAuth2PasswordRequestFormStrict` importable directly from `fastapi.security`. PR [#1462](https://github.com/tiangolo/fastapi/pull/1462) by [@RichardHoekstra](https://github.com/RichardHoekstra).
|
||||
* Add docs about [Default response class](https://fastapi.tiangolo.com/advanced/custom-response/#default-response-class). PR [#1455](https://github.com/tiangolo/fastapi/pull/1455) by [@TezRomacH](https://github.com/TezRomacH).
|
||||
* Add note in docs about additional parameters `response_model_exclude_defaults` and `response_model_exclude_none` in [Response Model](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). PR [#1427](https://github.com/tiangolo/fastapi/pull/1427) by [@wshayes](https://github.com/wshayes).
|
||||
* Add note about [PyCharm Pydantic plugin](https://github.com/koxudaxi/pydantic-pycharm-plugin) to docs. PR [#1420](https://github.com/tiangolo/fastapi/pull/1420) by [@koxudaxi](https://github.com/koxudaxi).
|
||||
* Update and clarify testing function name. PR [#1395](https://github.com/tiangolo/fastapi/pull/1395) by [@chenl](https://github.com/chenl).
|
||||
* Fix duplicated headers created by indirect dependencies that use the request directly. PR [#1386](https://github.com/tiangolo/fastapi/pull/1386) by [@obataku](https://github.com/obataku) from tests by [@scottsmith2gmail](https://github.com/scottsmith2gmail).
|
||||
* Upgrade Starlette version to `0.13.4`. PR [#1361](https://github.com/tiangolo/fastapi/pull/1361) by [@rushton](https://github.com/rushton).
|
||||
* Improve error handling and feedback for requests with invalid JSON. PR [#1354](https://github.com/tiangolo/fastapi/pull/1354) by [@aviramha](https://github.com/aviramha).
|
||||
* Add support for declaring metadata for tags in OpenAPI. New docs at [Tutorial - Metadata and Docs URLs - Metadata for tags](https://fastapi.tiangolo.com/tutorial/metadata/#metadata-for-tags). PR [#1348](https://github.com/tiangolo/fastapi/pull/1348) by [@thomas-maschler](https://github.com/thomas-maschler).
|
||||
* Add basic setup for Russian translations. PR [#1566](https://github.com/tiangolo/fastapi/pull/1566).
|
||||
* Remove obsolete Chinese articles after adding official community translations. PR [#1510](https://github.com/tiangolo/fastapi/pull/1510) by [@waynerv](https://github.com/waynerv).
|
||||
* Add `__repr__` for *path operation function* parameter helpers (like `Query`, `Depends`, etc) to simplify debugging. PR [#1560](https://github.com/tiangolo/fastapi/pull/1560) by [@rkbeatss](https://github.com/rkbeatss) and [@victorphoenix3](https://github.com/victorphoenix3).
|
||||
|
||||
## 0.56.1
|
||||
|
||||
* Add link to advanced docs from tutorial. PR [#1512](https://github.com/tiangolo/fastapi/pull/1512) by [@kx-chen](https://github.com/kx-chen).
|
||||
|
||||
@@ -108,6 +108,17 @@ But you would get the same editor support with <a href="https://www.jetbrains.co
|
||||
|
||||
<img src="/img/tutorial/body/image05.png">
|
||||
|
||||
!!! tip
|
||||
If you use <a href="https://www.jetbrains.com/pycharm/" class="external-link" target="_blank">PyCharm</a> as your editor, you can use the <a href="https://github.com/koxudaxi/pydantic-pycharm-plugin/" class="external-link" target="_blank">Pydantic PyCharm Plugin</a>.
|
||||
|
||||
It improves editor support for Pydantic models, with:
|
||||
|
||||
* auto-completion
|
||||
* type checks
|
||||
* refactoring
|
||||
* searching
|
||||
* inspections
|
||||
|
||||
## Use the model
|
||||
|
||||
Inside of the function, you can access all the attributes of the model object directly:
|
||||
|
||||
@@ -71,13 +71,13 @@ You will see the alternative automatic documentation (provided by <a href="https
|
||||
|
||||
#### "Schema"
|
||||
|
||||
A "schema" is a definition or description of something. Not the code that implements it, but just the abstract description.
|
||||
A "schema" is a definition or description of something. Not the code that implements it, but just an abstract description.
|
||||
|
||||
#### API "schema"
|
||||
|
||||
In this case, OpenAPI is a specification that dictates how to define a schema of your API.
|
||||
In this case, <a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank">OpenAPI</a> is a specification that dictates how to define a schema of your API.
|
||||
|
||||
This OpenAPI schema would include your API paths, the possible parameters they take, etc.
|
||||
This schema definition includes your API paths, the possible parameters they take, etc.
|
||||
|
||||
#### Data "schema"
|
||||
|
||||
@@ -91,7 +91,7 @@ OpenAPI defines an API schema for your API. And that schema includes definitions
|
||||
|
||||
#### Check the `openapi.json`
|
||||
|
||||
If you are curious about how the raw OpenAPI schema looks like, it is just an automatically generated JSON with the descriptions of all your API.
|
||||
If you are curious about how the raw OpenAPI schema looks like, FastAPI automatically generates a JSON (schema) with the descriptions of all your API.
|
||||
|
||||
You can see it directly at: <a href="http://127.0.0.1:8000/openapi.json" class="external-link" target="_blank">http://127.0.0.1:8000/openapi.json</a>.
|
||||
|
||||
@@ -120,7 +120,7 @@ It will show a JSON starting with something like:
|
||||
|
||||
#### What is OpenAPI for
|
||||
|
||||
This OpenAPI schema is what powers the 2 interactive documentation systems included.
|
||||
The OpenAPI schema is what powers the two interactive documentation systems included.
|
||||
|
||||
And there are dozens of alternatives, all based on OpenAPI. You could easily add any of those alternatives to your application built with **FastAPI**.
|
||||
|
||||
@@ -139,7 +139,7 @@ You could also use it to generate code automatically, for clients that communica
|
||||
!!! note "Technical Details"
|
||||
`FastAPI` is a class that inherits directly from `Starlette`.
|
||||
|
||||
You can use all the Starlette functionality with `FastAPI` too.
|
||||
You can use all the <a href="https://www.starlette.io/" class="external-link" target="_blank">Starlette</a> functionality with `FastAPI` too.
|
||||
|
||||
### Step 2: create a `FastAPI` "instance"
|
||||
|
||||
@@ -202,7 +202,7 @@ https://example.com/items/foo
|
||||
!!! info
|
||||
A "path" is also commonly called an "endpoint" or a "route".
|
||||
|
||||
Building an API, the "path" is the main way to separate "concerns" and "resources".
|
||||
While building an API, the "path" is the main way to separate "concerns" and "resources".
|
||||
|
||||
#### Operation
|
||||
|
||||
@@ -281,7 +281,7 @@ And the more exotic ones:
|
||||
|
||||
The information here is presented as a guideline, not a requirement.
|
||||
|
||||
For example, when using GraphQL you normally perform all the actions using only `post`.
|
||||
For example, when using GraphQL you normally perform all the actions using only `POST` operations.
|
||||
|
||||
### Step 4: define the **path operation function**
|
||||
|
||||
@@ -297,7 +297,7 @@ This is our "**path operation function**":
|
||||
|
||||
This is a Python function.
|
||||
|
||||
It will be called by **FastAPI** whenever it receives a request to the URL "`/`" using `GET`.
|
||||
It will be called by **FastAPI** whenever it receives a request to the URL "`/`" using a `GET` operation.
|
||||
|
||||
In this case, it is an `async` function.
|
||||
|
||||
|
||||
@@ -215,7 +215,6 @@ You will receive a response telling you that the data is invalid containing the
|
||||
{
|
||||
"loc": [
|
||||
"body",
|
||||
"item",
|
||||
"size"
|
||||
],
|
||||
"msg": "value is not a valid integer",
|
||||
|
||||
@@ -21,6 +21,58 @@ With this configuration, the automatic API docs would look like:
|
||||
|
||||
<img src="/img/tutorial/metadata/image01.png">
|
||||
|
||||
## Metadata for tags
|
||||
|
||||
You can also add additional metadata for the different tags used to group your path operations with the parameter `openapi_tags`.
|
||||
|
||||
It takes a list containing one dictionary for each tag.
|
||||
|
||||
Each dictionary can contain:
|
||||
|
||||
* `name` (**required**): a `str` with the same tag name you use in the `tags` parameter in your *path operations* and `APIRouter`s.
|
||||
* `description`: a `str` with a short description for the tag. It can have Markdown and will be shown in the docs UI.
|
||||
* `externalDocs`: a `dict` describing external documentation with:
|
||||
* `description`: a `str` with a short description for the external docs.
|
||||
* `url` (**required**): a `str` with the URL for the external documentation.
|
||||
|
||||
### Create metadata for tags
|
||||
|
||||
Let's try that in an example with tags for `users` and `items`.
|
||||
|
||||
Create metadata for your tags and pass it to the `openapi_tags` parameter:
|
||||
|
||||
```Python hl_lines="3 4 5 6 7 8 9 10 11 12 13 14 15 16 18"
|
||||
{!../../../docs_src/metadata/tutorial004.py!}
|
||||
```
|
||||
|
||||
Notice that you can use Markdown inside of the descriptions, for example "login" will be shown in bold (**login**) and "fancy" will be shown in italics (_fancy_).
|
||||
|
||||
!!! tip
|
||||
You don't have to add metadata for all the tags that you use.
|
||||
|
||||
### Use your tags
|
||||
|
||||
Use the `tags` parameter with your *path operations* (and `APIRouter`s) to assign them to different tags:
|
||||
|
||||
```Python hl_lines="21 26"
|
||||
{!../../../docs_src/metadata/tutorial004.py!}
|
||||
```
|
||||
|
||||
!!! info
|
||||
Read more about tags in [Path Operation Configuration](../path-operation-configuration/#tags){.internal-link target=_blank}.
|
||||
|
||||
### Check the docs
|
||||
|
||||
Now, if you check the docs, they will show all the additional metadata:
|
||||
|
||||
<img src="/img/tutorial/metadata/image02.png">
|
||||
|
||||
### Order of tags
|
||||
|
||||
The order of each tag metadata dictionary also defines the order shown in the docs UI.
|
||||
|
||||
For example, even though `users` would go after `items` in alphabetical order, it is shown before them, because we added their metadata as the first dictionary in the list.
|
||||
|
||||
## OpenAPI URL
|
||||
|
||||
By default, the OpenAPI schema is served at `/openapi.json`.
|
||||
|
||||
@@ -85,7 +85,7 @@ And when you open your browser at <a href="http://127.0.0.1:8000/docs" class="ex
|
||||
|
||||
And because the generated schema is from the <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md" class="external-link" target="_blank">OpenAPI</a> standard, there are many compatible tools.
|
||||
|
||||
Because of this, **FastAPI** itself provides an alternative API documentation (using ReDoc):
|
||||
Because of this, **FastAPI** itself provides an alternative API documentation (using ReDoc), which you can access at <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>:
|
||||
|
||||
<img src="/img/tutorial/path-params/image02.png">
|
||||
|
||||
@@ -125,7 +125,7 @@ Import `Enum` and create a sub-class that inherits from `str` and from `Enum`.
|
||||
|
||||
By inheriting from `str` the API docs will be able to know that the values must be of type `string` and will be able to render correctly.
|
||||
|
||||
And create class attributes with fixed values, those fixed values will be the available valid values:
|
||||
Then create class attributes with fixed values, which will be the available valid values:
|
||||
|
||||
```Python hl_lines="1 6 7 8 9"
|
||||
{!../../../docs_src/path_params/tutorial005.py!}
|
||||
@@ -147,7 +147,7 @@ Then create a *path parameter* with a type annotation using the enum class you c
|
||||
|
||||
### Check the docs
|
||||
|
||||
Because the available values for the *path parameter* are specified, the interactive docs can show them nicely:
|
||||
Because the available values for the *path parameter* are predefined, the interactive docs can show them nicely:
|
||||
|
||||
<img src="/img/tutorial/path-params/image03.png">
|
||||
|
||||
@@ -167,7 +167,7 @@ You can compare it with the *enumeration member* in your created enum `ModelName
|
||||
|
||||
You can get the actual value (a `str` in this case) using `model_name.value`, or in general, `your_enum_member.value`:
|
||||
|
||||
```Python hl_lines="19"
|
||||
```Python hl_lines="20"
|
||||
{!../../../docs_src/path_params/tutorial005.py!}
|
||||
```
|
||||
|
||||
@@ -178,12 +178,21 @@ You can get the actual value (a `str` in this case) using `model_name.value`, or
|
||||
|
||||
You can return *enum members* from your *path operation*, even nested in a JSON body (e.g. a `dict`).
|
||||
|
||||
They will be converted to their corresponding values before returning them to the client:
|
||||
They will be converted to their corresponding values (strings in this case) before returning them to the client:
|
||||
|
||||
```Python hl_lines="18 20 21"
|
||||
```Python hl_lines="18 21 23"
|
||||
{!../../../docs_src/path_params/tutorial005.py!}
|
||||
```
|
||||
|
||||
In your client you will get a JSON response like:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"model_name": "alexnet",
|
||||
"message": "Deep Learning FTW!"
|
||||
}
|
||||
```
|
||||
|
||||
## Path parameters containing paths
|
||||
|
||||
Let's say you have a *path operation* with a path `/files/{file_path}`.
|
||||
|
||||
@@ -124,6 +124,14 @@ So, if you send a request to that *path operation* for the item with ID `foo`, t
|
||||
!!! info
|
||||
FastAPI uses Pydantic model's `.dict()` with <a href="https://pydantic-docs.helpmanual.io/usage/exporting_models/#modeldict" class="external-link" target="_blank">its `exclude_unset` parameter</a> to achieve this.
|
||||
|
||||
!!! info
|
||||
You can also use:
|
||||
|
||||
* `response_model_exclude_defaults=True`
|
||||
* `response_model_exclude_none=True`
|
||||
|
||||
as described in <a href="https://pydantic-docs.helpmanual.io/usage/exporting_models/#modeldict" class="external-link" target="_blank">the Pydantic docs</a> for `exclude_defaults` and `exclude_none`.
|
||||
|
||||
#### Data with values for fields with defaults
|
||||
|
||||
But if your data has values for the model's fields with default values, like the item with ID `bar`:
|
||||
|
||||
@@ -17,6 +17,9 @@ The same way you can specify a response model, you can also declare the HTTP sta
|
||||
|
||||
The `status_code` parameter receives a number with the HTTP status code.
|
||||
|
||||
!!! info
|
||||
`status_code` can alternatively also receive an `IntEnum`, such as Python's <a href="https://docs.python.org/3/library/http.html#http.HTTPStatus" class="external-link" target="_blank">`http.HTTPStatus`</a>.
|
||||
|
||||
It will:
|
||||
|
||||
* Return that status code in the response.
|
||||
|
||||
@@ -24,6 +24,7 @@ nav:
|
||||
- es: /es/
|
||||
- it: /it/
|
||||
- pt: /pt/
|
||||
- ru: /ru/
|
||||
- zh: /zh/
|
||||
- features.md
|
||||
- python-types.md
|
||||
|
||||
@@ -24,6 +24,7 @@ nav:
|
||||
- es: /es/
|
||||
- it: /it/
|
||||
- pt: /pt/
|
||||
- ru: /ru/
|
||||
- zh: /zh/
|
||||
- features.md
|
||||
- python-types.md
|
||||
|
||||
@@ -24,6 +24,7 @@ nav:
|
||||
- es: /es/
|
||||
- it: /it/
|
||||
- pt: /pt/
|
||||
- ru: /ru/
|
||||
- zh: /zh/
|
||||
markdown_extensions:
|
||||
- toc:
|
||||
|
||||
@@ -24,6 +24,7 @@ nav:
|
||||
- es: /es/
|
||||
- it: /it/
|
||||
- pt: /pt/
|
||||
- ru: /ru/
|
||||
- zh: /zh/
|
||||
- features.md
|
||||
- Tutorial - Guia de Usuário:
|
||||
|
||||
447
docs/ru/docs/index.md
Normal file
447
docs/ru/docs/index.md
Normal file
@@ -0,0 +1,447 @@
|
||||
|
||||
{!../../../docs/missing-translation.md!}
|
||||
|
||||
|
||||
<p align="center">
|
||||
<a href="https://fastapi.tiangolo.com"><img src="https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png" alt="FastAPI"></a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<em>FastAPI framework, high performance, easy to learn, fast to code, ready for production</em>
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://travis-ci.com/tiangolo/fastapi" target="_blank">
|
||||
<img src="https://travis-ci.com/tiangolo/fastapi.svg?branch=master" alt="Build Status">
|
||||
</a>
|
||||
<a href="https://codecov.io/gh/tiangolo/fastapi" target="_blank">
|
||||
<img src="https://img.shields.io/codecov/c/github/tiangolo/fastapi" alt="Coverage">
|
||||
</a>
|
||||
<a href="https://pypi.org/project/fastapi" target="_blank">
|
||||
<img src="https://badge.fury.io/py/fastapi.svg" alt="Package version">
|
||||
</a>
|
||||
<a href="https://gitter.im/tiangolo/fastapi?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge" target="_blank">
|
||||
<img src="https://badges.gitter.im/tiangolo/fastapi.svg" alt="Join the chat at https://gitter.im/tiangolo/fastapi">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
**Documentation**: <a href="https://fastapi.tiangolo.com" target="_blank">https://fastapi.tiangolo.com</a>
|
||||
|
||||
**Source Code**: <a href="https://github.com/tiangolo/fastapi" target="_blank">https://github.com/tiangolo/fastapi</a>
|
||||
|
||||
---
|
||||
|
||||
FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3.6+ based on standard Python type hints.
|
||||
|
||||
The key features are:
|
||||
|
||||
* **Fast**: Very high performance, on par with **NodeJS** and **Go** (thanks to Starlette and Pydantic). [One of the fastest Python frameworks available](#performance).
|
||||
|
||||
* **Fast to code**: Increase the speed to develop features by about 200% to 300%. *
|
||||
* **Fewer bugs**: Reduce about 40% of human (developer) induced errors. *
|
||||
* **Intuitive**: Great editor support. <abbr title="also known as auto-complete, autocompletion, IntelliSense">Completion</abbr> everywhere. Less time debugging.
|
||||
* **Easy**: Designed to be easy to use and learn. Less time reading docs.
|
||||
* **Short**: Minimize code duplication. Multiple features from each parameter declaration. Fewer bugs.
|
||||
* **Robust**: Get production-ready code. With automatic interactive documentation.
|
||||
* **Standards-based**: Based on (and fully compatible with) the open standards for APIs: <a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank">OpenAPI</a> (previously known as Swagger) and <a href="http://json-schema.org/" class="external-link" target="_blank">JSON Schema</a>.
|
||||
|
||||
<small>* estimation based on tests on an internal development team, building production applications.</small>
|
||||
|
||||
## Opinions
|
||||
|
||||
"_[...] I'm using **FastAPI** a ton these days. [...] I'm actually planning to use it for all of my team's **ML services at Microsoft**. Some of them are getting integrated into the core **Windows** product and some **Office** products._"
|
||||
|
||||
<div style="text-align: right; margin-right: 10%;">Kabir Khan - <strong>Microsoft</strong> <a href="https://github.com/tiangolo/fastapi/pull/26" target="_blank"><small>(ref)</small></a></div>
|
||||
|
||||
---
|
||||
|
||||
"_We adopted the **FastAPI** library to spawn a **REST** server that can be queried to obtain **predictions**. [for Ludwig]_"
|
||||
|
||||
<div style="text-align: right; margin-right: 10%;">Piero Molino, Yaroslav Dudin, and Sai Sumanth Miryala - <strong>Uber</strong> <a href="https://eng.uber.com/ludwig-v0-2/" target="_blank"><small>(ref)</small></a></div>
|
||||
|
||||
---
|
||||
|
||||
"_**Netflix** is pleased to announce the open-source release of our **crisis management** orchestration framework: **Dispatch**! [built with **FastAPI**]_"
|
||||
|
||||
<div style="text-align: right; margin-right: 10%;">Kevin Glisson, Marc Vilanova, Forest Monsen - <strong>Netflix</strong> <a href="https://netflixtechblog.com/introducing-dispatch-da4b8a2a8072" target="_blank"><small>(ref)</small></a></div>
|
||||
|
||||
---
|
||||
|
||||
"_I’m over the moon excited about **FastAPI**. It’s so fun!_"
|
||||
|
||||
<div style="text-align: right; margin-right: 10%;">Brian Okken - <strong><a href="https://pythonbytes.fm/episodes/show/123/time-to-right-the-py-wrongs?time_in_sec=855" target="_blank">Python Bytes</a> podcast host</strong> <a href="https://twitter.com/brianokken/status/1112220079972728832" target="_blank"><small>(ref)</small></a></div>
|
||||
|
||||
---
|
||||
|
||||
"_Honestly, what you've built looks super solid and polished. In many ways, it's what I wanted **Hug** to be - it's really inspiring to see someone build that._"
|
||||
|
||||
<div style="text-align: right; margin-right: 10%;">Timothy Crosley - <strong><a href="http://www.hug.rest/" target="_blank">Hug</a> creator</strong> <a href="https://news.ycombinator.com/item?id=19455465" target="_blank"><small>(ref)</small></a></div>
|
||||
|
||||
---
|
||||
|
||||
"_If you're looking to learn one **modern framework** for building REST APIs, check out **FastAPI** [...] It's fast, easy to use and easy to learn [...]_"
|
||||
|
||||
"_We've switched over to **FastAPI** for our **APIs** [...] I think you'll like it [...]_"
|
||||
|
||||
<div style="text-align: right; margin-right: 10%;">Ines Montani - Matthew Honnibal - <strong><a href="https://explosion.ai" target="_blank">Explosion AI</a> founders - <a href="https://spacy.io" target="_blank">spaCy</a> creators</strong> <a href="https://twitter.com/_inesmontani/status/1144173225322143744" target="_blank"><small>(ref)</small></a> - <a href="https://twitter.com/honnibal/status/1144031421859655680" target="_blank"><small>(ref)</small></a></div>
|
||||
|
||||
---
|
||||
|
||||
## **Typer**, the FastAPI of CLIs
|
||||
|
||||
<a href="https://typer.tiangolo.com" target="_blank"><img src="https://typer.tiangolo.com/img/logo-margin/logo-margin-vector.svg" style="width: 20%;"></a>
|
||||
|
||||
If you are building a <abbr title="Command Line Interface">CLI</abbr> app to be used in the terminal instead of a web API, check out <a href="https://typer.tiangolo.com/" class="external-link" target="_blank">**Typer**</a>.
|
||||
|
||||
**Typer** is FastAPI's little sibling. And it's intended to be the **FastAPI of CLIs**. ⌨️ 🚀
|
||||
|
||||
## Requirements
|
||||
|
||||
Python 3.6+
|
||||
|
||||
FastAPI stands on the shoulders of giants:
|
||||
|
||||
* <a href="https://www.starlette.io/" class="external-link" target="_blank">Starlette</a> for the web parts.
|
||||
* <a href="https://pydantic-docs.helpmanual.io/" class="external-link" target="_blank">Pydantic</a> for the data parts.
|
||||
|
||||
## Installation
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install fastapi
|
||||
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
You will also need an ASGI server, for production such as <a href="http://www.uvicorn.org" class="external-link" target="_blank">Uvicorn</a> or <a href="https://gitlab.com/pgjones/hypercorn" class="external-link" target="_blank">Hypercorn</a>.
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install uvicorn
|
||||
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## Example
|
||||
|
||||
### Create it
|
||||
|
||||
* Create a file `main.py` with:
|
||||
|
||||
```Python
|
||||
from fastapi import FastAPI
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.get("/")
|
||||
def read_root():
|
||||
return {"Hello": "World"}
|
||||
|
||||
|
||||
@app.get("/items/{item_id}")
|
||||
def read_item(item_id: int, q: str = None):
|
||||
return {"item_id": item_id, "q": q}
|
||||
```
|
||||
|
||||
<details markdown="1">
|
||||
<summary>Or use <code>async def</code>...</summary>
|
||||
|
||||
If your code uses `async` / `await`, use `async def`:
|
||||
|
||||
```Python hl_lines="7 12"
|
||||
from fastapi import FastAPI
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.get("/")
|
||||
async def read_root():
|
||||
return {"Hello": "World"}
|
||||
|
||||
|
||||
@app.get("/items/{item_id}")
|
||||
async def read_item(item_id: int, q: str = None):
|
||||
return {"item_id": item_id, "q": q}
|
||||
```
|
||||
|
||||
**Note**:
|
||||
|
||||
If you don't know, check the _"In a hurry?"_ section about <a href="https://fastapi.tiangolo.com/async/#in-a-hurry" target="_blank">`async` and `await` in the docs</a>.
|
||||
|
||||
</details>
|
||||
|
||||
### Run it
|
||||
|
||||
Run the server with:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ uvicorn main:app --reload
|
||||
|
||||
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
INFO: Started reloader process [28720]
|
||||
INFO: Started server process [28722]
|
||||
INFO: Waiting for application startup.
|
||||
INFO: Application startup complete.
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
<details markdown="1">
|
||||
<summary>About the command <code>uvicorn main:app --reload</code>...</summary>
|
||||
|
||||
The command `uvicorn main:app` refers to:
|
||||
|
||||
* `main`: the file `main.py` (the Python "module").
|
||||
* `app`: the object created inside of `main.py` with the line `app = FastAPI()`.
|
||||
* `--reload`: make the server restart after code changes. Only do this for development.
|
||||
|
||||
</details>
|
||||
|
||||
### Check it
|
||||
|
||||
Open your browser at <a href="http://127.0.0.1:8000/items/5?q=somequery" class="external-link" target="_blank">http://127.0.0.1:8000/items/5?q=somequery</a>.
|
||||
|
||||
You will see the JSON response as:
|
||||
|
||||
```JSON
|
||||
{"item_id": 5, "q": "somequery"}
|
||||
```
|
||||
|
||||
You already created an API that:
|
||||
|
||||
* Receives HTTP requests in the _paths_ `/` and `/items/{item_id}`.
|
||||
* Both _paths_ take `GET` <em>operations</em> (also known as HTTP _methods_).
|
||||
* The _path_ `/items/{item_id}` has a _path parameter_ `item_id` that should be an `int`.
|
||||
* The _path_ `/items/{item_id}` has an optional `str` _query parameter_ `q`.
|
||||
|
||||
### Interactive API docs
|
||||
|
||||
Now go to <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
|
||||
|
||||
You will see the automatic interactive API documentation (provided by <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank">Swagger UI</a>):
|
||||
|
||||

|
||||
|
||||
### Alternative API docs
|
||||
|
||||
And now, go to <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>.
|
||||
|
||||
You will see the alternative automatic documentation (provided by <a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank">ReDoc</a>):
|
||||
|
||||

|
||||
|
||||
## Example upgrade
|
||||
|
||||
Now modify the file `main.py` to receive a body from a `PUT` request.
|
||||
|
||||
Declare the body using standard Python types, thanks to Pydantic.
|
||||
|
||||
```Python hl_lines="2 7 8 9 10 23 24 25"
|
||||
from fastapi import FastAPI
|
||||
from pydantic import BaseModel
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
class Item(BaseModel):
|
||||
name: str
|
||||
price: float
|
||||
is_offer: bool = None
|
||||
|
||||
|
||||
@app.get("/")
|
||||
def read_root():
|
||||
return {"Hello": "World"}
|
||||
|
||||
|
||||
@app.get("/items/{item_id}")
|
||||
def read_item(item_id: int, q: str = None):
|
||||
return {"item_id": item_id, "q": q}
|
||||
|
||||
|
||||
@app.put("/items/{item_id}")
|
||||
def update_item(item_id: int, item: Item):
|
||||
return {"item_name": item.name, "item_id": item_id}
|
||||
```
|
||||
|
||||
The server should reload automatically (because you added `--reload` to the `uvicorn` command above).
|
||||
|
||||
### Interactive API docs upgrade
|
||||
|
||||
Now go to <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
|
||||
|
||||
* The interactive API documentation will be automatically updated, including the new body:
|
||||
|
||||

|
||||
|
||||
* Click on the button "Try it out", it allows you to fill the parameters and directly interact with the API:
|
||||
|
||||

|
||||
|
||||
* Then click on the "Execute" button, the user interface will communicate with your API, send the parameters, get the results and show them on the screen:
|
||||
|
||||

|
||||
|
||||
### Alternative API docs upgrade
|
||||
|
||||
And now, go to <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>.
|
||||
|
||||
* The alternative documentation will also reflect the new query parameter and body:
|
||||
|
||||

|
||||
|
||||
### Recap
|
||||
|
||||
In summary, you declare **once** the types of parameters, body, etc. as function parameters.
|
||||
|
||||
You do that with standard modern Python types.
|
||||
|
||||
You don't have to learn a new syntax, the methods or classes of a specific library, etc.
|
||||
|
||||
Just standard **Python 3.6+**.
|
||||
|
||||
For example, for an `int`:
|
||||
|
||||
```Python
|
||||
item_id: int
|
||||
```
|
||||
|
||||
or for a more complex `Item` model:
|
||||
|
||||
```Python
|
||||
item: Item
|
||||
```
|
||||
|
||||
...and with that single declaration you get:
|
||||
|
||||
* Editor support, including:
|
||||
* Completion.
|
||||
* Type checks.
|
||||
* Validation of data:
|
||||
* Automatic and clear errors when the data is invalid.
|
||||
* Validation even for deeply nested JSON objects.
|
||||
* <abbr title="also known as: serialization, parsing, marshalling">Conversion</abbr> of input data: coming from the network to Python data and types. Reading from:
|
||||
* JSON.
|
||||
* Path parameters.
|
||||
* Query parameters.
|
||||
* Cookies.
|
||||
* Headers.
|
||||
* Forms.
|
||||
* Files.
|
||||
* <abbr title="also known as: serialization, parsing, marshalling">Conversion</abbr> of output data: converting from Python data and types to network data (as JSON):
|
||||
* Convert Python types (`str`, `int`, `float`, `bool`, `list`, etc).
|
||||
* `datetime` objects.
|
||||
* `UUID` objects.
|
||||
* Database models.
|
||||
* ...and many more.
|
||||
* Automatic interactive API documentation, including 2 alternative user interfaces:
|
||||
* Swagger UI.
|
||||
* ReDoc.
|
||||
|
||||
---
|
||||
|
||||
Coming back to the previous code example, **FastAPI** will:
|
||||
|
||||
* Validate that there is an `item_id` in the path for `GET` and `PUT` requests.
|
||||
* Validate that the `item_id` is of type `int` for `GET` and `PUT` requests.
|
||||
* If it is not, the client will see a useful, clear error.
|
||||
* Check if there is an optional query parameter named `q` (as in `http://127.0.0.1:8000/items/foo?q=somequery`) for `GET` requests.
|
||||
* As the `q` parameter is declared with `= None`, it is optional.
|
||||
* Without the `None` it would be required (as is the body in the case with `PUT`).
|
||||
* For `PUT` requests to `/items/{item_id}`, Read the body as JSON:
|
||||
* Check that it has a required attribute `name` that should be a `str`.
|
||||
* Check that it has a required attribute `price` that has to be a `float`.
|
||||
* Check that it has an optional attribute `is_offer`, that should be a `bool`, if present.
|
||||
* All this would also work for deeply nested JSON objects.
|
||||
* Convert from and to JSON automatically.
|
||||
* Document everything with OpenAPI, that can be used by:
|
||||
* Interactive documentation systems.
|
||||
* Automatic client code generation systems, for many languages.
|
||||
* Provide 2 interactive documentation web interfaces directly.
|
||||
|
||||
---
|
||||
|
||||
We just scratched the surface, but you already get the idea of how it all works.
|
||||
|
||||
Try changing the line with:
|
||||
|
||||
```Python
|
||||
return {"item_name": item.name, "item_id": item_id}
|
||||
```
|
||||
|
||||
...from:
|
||||
|
||||
```Python
|
||||
... "item_name": item.name ...
|
||||
```
|
||||
|
||||
...to:
|
||||
|
||||
```Python
|
||||
... "item_price": item.price ...
|
||||
```
|
||||
|
||||
...and see how your editor will auto-complete the attributes and know their types:
|
||||
|
||||

|
||||
|
||||
For a more complete example including more features, see the <a href="https://fastapi.tiangolo.com/tutorial/">Tutorial - User Guide</a>.
|
||||
|
||||
**Spoiler alert**: the tutorial - user guide includes:
|
||||
|
||||
* Declaration of **parameters** from other different places as: **headers**, **cookies**, **form fields** and **files**.
|
||||
* How to set **validation constraints** as `maximum_length` or `regex`.
|
||||
* A very powerful and easy to use **<abbr title="also known as components, resources, providers, services, injectables">Dependency Injection</abbr>** system.
|
||||
* Security and authentication, including support for **OAuth2** with **JWT tokens** and **HTTP Basic** auth.
|
||||
* More advanced (but equally easy) techniques for declaring **deeply nested JSON models** (thanks to Pydantic).
|
||||
* Many extra features (thanks to Starlette) as:
|
||||
* **WebSockets**
|
||||
* **GraphQL**
|
||||
* extremely easy tests based on `requests` and `pytest`
|
||||
* **CORS**
|
||||
* **Cookie Sessions**
|
||||
* ...and more.
|
||||
|
||||
## Performance
|
||||
|
||||
Independent TechEmpower benchmarks show **FastAPI** applications running under Uvicorn as <a href="https://www.techempower.com/benchmarks/#section=test&runid=7464e520-0dc2-473d-bd34-dbdfd7e85911&hw=ph&test=query&l=zijzen-7" class="external-link" target="_blank">one of the fastest Python frameworks available</a>, only below Starlette and Uvicorn themselves (used internally by FastAPI). (*)
|
||||
|
||||
To understand more about it, see the section <a href="https://fastapi.tiangolo.com/benchmarks/" class="internal-link" target="_blank">Benchmarks</a>.
|
||||
|
||||
## Optional Dependencies
|
||||
|
||||
Used by Pydantic:
|
||||
|
||||
* <a href="https://github.com/esnme/ultrajson" target="_blank"><code>ujson</code></a> - for faster JSON <abbr title="converting the string that comes from an HTTP request into Python data">"parsing"</abbr>.
|
||||
* <a href="https://github.com/JoshData/python-email-validator" target="_blank"><code>email_validator</code></a> - for email validation.
|
||||
|
||||
Used by Starlette:
|
||||
|
||||
* <a href="http://docs.python-requests.org" target="_blank"><code>requests</code></a> - Required if you want to use the `TestClient`.
|
||||
* <a href="https://github.com/Tinche/aiofiles" target="_blank"><code>aiofiles</code></a> - Required if you want to use `FileResponse` or `StaticFiles`.
|
||||
* <a href="http://jinja.pocoo.org" target="_blank"><code>jinja2</code></a> - Required if you want to use the default template configuration.
|
||||
* <a href="https://andrew-d.github.io/python-multipart/" target="_blank"><code>python-multipart</code></a> - Required if you want to support form <abbr title="converting the string that comes from an HTTP request into Python data">"parsing"</abbr>, with `request.form()`.
|
||||
* <a href="https://pythonhosted.org/itsdangerous/" target="_blank"><code>itsdangerous</code></a> - Required for `SessionMiddleware` support.
|
||||
* <a href="https://pyyaml.org/wiki/PyYAMLDocumentation" target="_blank"><code>pyyaml</code></a> - Required for Starlette's `SchemaGenerator` support (you probably don't need it with FastAPI).
|
||||
* <a href="https://graphene-python.org/" target="_blank"><code>graphene</code></a> - Required for `GraphQLApp` support.
|
||||
* <a href="https://github.com/esnme/ultrajson" target="_blank"><code>ujson</code></a> - Required if you want to use `UJSONResponse`.
|
||||
|
||||
Used by FastAPI / Starlette:
|
||||
|
||||
* <a href="http://www.uvicorn.org" target="_blank"><code>uvicorn</code></a> - for the server that loads and serves your application.
|
||||
* <a href="https://github.com/ijl/orjson" target="_blank"><code>orjson</code></a> - Required if you want to use `ORJSONResponse`.
|
||||
|
||||
You can install all of these with `pip install fastapi[all]`.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the terms of the MIT license.
|
||||
67
docs/ru/mkdocs.yml
Normal file
67
docs/ru/mkdocs.yml
Normal file
@@ -0,0 +1,67 @@
|
||||
site_name: FastAPI
|
||||
site_description: FastAPI framework, high performance, easy to learn, fast to code, ready for production
|
||||
site_url: https://fastapi.tiangolo.com/ru/
|
||||
theme:
|
||||
name: material
|
||||
palette:
|
||||
primary: teal
|
||||
accent: amber
|
||||
icon:
|
||||
repo: fontawesome/brands/github-alt
|
||||
logo: https://fastapi.tiangolo.com/img/icon-white.svg
|
||||
favicon: https://fastapi.tiangolo.com/img/favicon.png
|
||||
language: ru
|
||||
repo_name: tiangolo/fastapi
|
||||
repo_url: https://github.com/tiangolo/fastapi
|
||||
edit_uri: ''
|
||||
google_analytics:
|
||||
- UA-133183413-1
|
||||
- auto
|
||||
nav:
|
||||
- FastAPI: index.md
|
||||
- Languages:
|
||||
- en: /
|
||||
- es: /es/
|
||||
- it: /it/
|
||||
- pt: /pt/
|
||||
- ru: /ru/
|
||||
- zh: /zh/
|
||||
markdown_extensions:
|
||||
- toc:
|
||||
permalink: true
|
||||
- markdown.extensions.codehilite:
|
||||
guess_lang: false
|
||||
- markdown_include.include:
|
||||
base_path: docs
|
||||
- admonition
|
||||
- codehilite
|
||||
- extra
|
||||
- pymdownx.superfences:
|
||||
custom_fences:
|
||||
- name: mermaid
|
||||
class: mermaid
|
||||
format: !!python/name:pymdownx.superfences.fence_div_format ''
|
||||
- pymdownx.tabbed
|
||||
extra:
|
||||
social:
|
||||
- icon: fontawesome/brands/github-alt
|
||||
link: https://github.com/tiangolo/typer
|
||||
- icon: fontawesome/brands/twitter
|
||||
link: https://twitter.com/tiangolo
|
||||
- icon: fontawesome/brands/linkedin
|
||||
link: https://www.linkedin.com/in/tiangolo
|
||||
- icon: fontawesome/brands/dev
|
||||
link: https://dev.to/tiangolo
|
||||
- icon: fontawesome/brands/medium
|
||||
link: https://medium.com/@tiangolo
|
||||
- icon: fontawesome/solid/globe
|
||||
link: https://tiangolo.com
|
||||
extra_css:
|
||||
- https://fastapi.tiangolo.com/css/termynal.css
|
||||
- https://fastapi.tiangolo.com/css/custom.css
|
||||
extra_javascript:
|
||||
- https://unpkg.com/mermaid@8.4.6/dist/mermaid.min.js
|
||||
- https://fastapi.tiangolo.com/js/termynal.js
|
||||
- https://fastapi.tiangolo.com/js/custom.js
|
||||
- https://fastapi.tiangolo.com/js/chat.js
|
||||
- https://sidecar.gitter.im/dist/sidecar.v1.js
|
||||
@@ -24,6 +24,7 @@ nav:
|
||||
- es: /es/
|
||||
- it: /it/
|
||||
- pt: /pt/
|
||||
- ru: /ru/
|
||||
- zh: /zh/
|
||||
- features.md
|
||||
- python-types.md
|
||||
|
||||
@@ -51,7 +51,7 @@ def test_create_item_bad_token():
|
||||
assert response.json() == {"detail": "Invalid X-Token header"}
|
||||
|
||||
|
||||
def test_create_existing_token():
|
||||
def test_create_existing_item():
|
||||
response = client.post(
|
||||
"/items/",
|
||||
headers={"X-Token": "coneofsilence"},
|
||||
|
||||
9
docs_src/custom_response/tutorial010.py
Normal file
9
docs_src/custom_response/tutorial010.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from fastapi import FastAPI
|
||||
from fastapi.responses import ORJSONResponse
|
||||
|
||||
app = FastAPI(default_response_class=ORJSONResponse)
|
||||
|
||||
|
||||
@app.get("/items/")
|
||||
async def read_items():
|
||||
return [{"item_id": "Foo"}]
|
||||
28
docs_src/metadata/tutorial004.py
Normal file
28
docs_src/metadata/tutorial004.py
Normal file
@@ -0,0 +1,28 @@
|
||||
from fastapi import FastAPI
|
||||
|
||||
tags_metadata = [
|
||||
{
|
||||
"name": "users",
|
||||
"description": "Operations with users. The **login** logic is also here.",
|
||||
},
|
||||
{
|
||||
"name": "items",
|
||||
"description": "Manage items. So _fancy_ they have their own docs.",
|
||||
"externalDocs": {
|
||||
"description": "Items external docs",
|
||||
"url": "https://fastapi.tiangolo.com/",
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
app = FastAPI(openapi_tags=tags_metadata)
|
||||
|
||||
|
||||
@app.get("/users/", tags=["users"])
|
||||
async def get_users():
|
||||
return [{"name": "Harry"}, {"name": "Ron"}]
|
||||
|
||||
|
||||
@app.get("/items/", tags=["items"])
|
||||
async def get_items():
|
||||
return [{"name": "wand"}, {"name": "flying broom"}]
|
||||
@@ -16,6 +16,8 @@ app = FastAPI()
|
||||
async def get_model(model_name: ModelName):
|
||||
if model_name == ModelName.alexnet:
|
||||
return {"model_name": model_name, "message": "Deep Learning FTW!"}
|
||||
|
||||
if model_name.value == "lenet":
|
||||
return {"model_name": model_name, "message": "LeCNN all the images"}
|
||||
|
||||
return {"model_name": model_name, "message": "Have some residuals"}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from fastapi import Cookie, Depends, FastAPI, Header, WebSocket, status
|
||||
from fastapi import Cookie, Depends, FastAPI, Query, WebSocket, status
|
||||
from fastapi.responses import HTMLResponse
|
||||
|
||||
app = FastAPI()
|
||||
@@ -13,8 +13,9 @@ html = """
|
||||
<h1>WebSocket Chat</h1>
|
||||
<form action="" onsubmit="sendMessage(event)">
|
||||
<label>Item ID: <input type="text" id="itemId" autocomplete="off" value="foo"/></label>
|
||||
<label>Token: <input type="text" id="token" autocomplete="off" value="some-key-token"/></label>
|
||||
<button onclick="connect(event)">Connect</button>
|
||||
<br>
|
||||
<hr>
|
||||
<label>Message: <input type="text" id="messageText" autocomplete="off"/></label>
|
||||
<button>Send</button>
|
||||
</form>
|
||||
@@ -23,8 +24,9 @@ html = """
|
||||
<script>
|
||||
var ws = null;
|
||||
function connect(event) {
|
||||
var input = document.getElementById("itemId")
|
||||
ws = new WebSocket("ws://localhost:8000/items/" + input.value + "/ws");
|
||||
var itemId = document.getElementById("itemId")
|
||||
var token = document.getElementById("token")
|
||||
ws = new WebSocket("ws://localhost:8000/items/" + itemId.value + "/ws?token=" + token.value);
|
||||
ws.onmessage = function(event) {
|
||||
var messages = document.getElementById('messages')
|
||||
var message = document.createElement('li')
|
||||
@@ -32,6 +34,7 @@ html = """
|
||||
message.appendChild(content)
|
||||
messages.appendChild(message)
|
||||
};
|
||||
event.preventDefault()
|
||||
}
|
||||
function sendMessage(event) {
|
||||
var input = document.getElementById("messageText")
|
||||
@@ -50,26 +53,26 @@ async def get():
|
||||
return HTMLResponse(html)
|
||||
|
||||
|
||||
async def get_cookie_or_client(
|
||||
websocket: WebSocket, session: str = Cookie(None), x_client: str = Header(None)
|
||||
async def get_cookie_or_token(
|
||||
websocket: WebSocket, session: str = Cookie(None), token: str = Query(None)
|
||||
):
|
||||
if session is None and x_client is None:
|
||||
if session is None and token is None:
|
||||
await websocket.close(code=status.WS_1008_POLICY_VIOLATION)
|
||||
return session or x_client
|
||||
return session or token
|
||||
|
||||
|
||||
@app.websocket("/items/{item_id}/ws")
|
||||
async def websocket_endpoint(
|
||||
websocket: WebSocket,
|
||||
item_id: int,
|
||||
q: str = None,
|
||||
cookie_or_client: str = Depends(get_cookie_or_client),
|
||||
item_id: str,
|
||||
q: int = None,
|
||||
cookie_or_token: str = Depends(get_cookie_or_token),
|
||||
):
|
||||
await websocket.accept()
|
||||
while True:
|
||||
data = await websocket.receive_text()
|
||||
await websocket.send_text(
|
||||
f"Session Cookie or X-Client Header value is: {cookie_or_client}"
|
||||
f"Session cookie or query token value is: {cookie_or_token}"
|
||||
)
|
||||
if q is not None:
|
||||
await websocket.send_text(f"Query parameter q is: {q}")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
|
||||
|
||||
__version__ = "0.56.1"
|
||||
__version__ = "0.58.0"
|
||||
|
||||
from starlette import status
|
||||
|
||||
|
||||
@@ -37,6 +37,8 @@ class FastAPI(Starlette):
|
||||
description: str = "",
|
||||
version: str = "0.1.0",
|
||||
openapi_url: Optional[str] = "/openapi.json",
|
||||
openapi_tags: Optional[List[Dict[str, Any]]] = None,
|
||||
servers: Optional[List[Dict[str, Union[str, Any]]]] = None,
|
||||
default_response_class: Type[Response] = JSONResponse,
|
||||
docs_url: Optional[str] = "/docs",
|
||||
redoc_url: Optional[str] = "/redoc",
|
||||
@@ -69,7 +71,9 @@ class FastAPI(Starlette):
|
||||
self.title = title
|
||||
self.description = description
|
||||
self.version = version
|
||||
self.servers = servers
|
||||
self.openapi_url = openapi_url
|
||||
self.openapi_tags = openapi_tags
|
||||
# TODO: remove when discarding the openapi_prefix parameter
|
||||
if openapi_prefix:
|
||||
logger.warning(
|
||||
@@ -103,6 +107,8 @@ class FastAPI(Starlette):
|
||||
description=self.description,
|
||||
routes=self.routes,
|
||||
openapi_prefix=openapi_prefix,
|
||||
tags=self.openapi_tags,
|
||||
servers=self.servers,
|
||||
)
|
||||
return self.openapi_schema
|
||||
|
||||
|
||||
@@ -500,6 +500,7 @@ async def solve_dependencies(
|
||||
name=sub_dependant.name,
|
||||
security_scopes=sub_dependant.security_scopes,
|
||||
)
|
||||
use_sub_dependant.security_scopes = sub_dependant.security_scopes
|
||||
|
||||
solved_result = await solve_dependencies(
|
||||
request=request,
|
||||
@@ -514,13 +515,9 @@ async def solve_dependencies(
|
||||
sub_values,
|
||||
sub_errors,
|
||||
background_tasks,
|
||||
sub_response,
|
||||
_, # the subdependency returns the same response we have
|
||||
sub_dependency_cache,
|
||||
) = solved_result
|
||||
sub_response = cast(Response, sub_response)
|
||||
response.headers.raw.extend(sub_response.headers.raw)
|
||||
if sub_response.status_code:
|
||||
response.status_code = sub_response.status_code
|
||||
dependency_cache.update(sub_dependency_cache)
|
||||
if sub_errors:
|
||||
errors.extend(sub_errors)
|
||||
@@ -645,9 +642,17 @@ async def request_body_to_args(
|
||||
field = required_params[0]
|
||||
field_info = get_field_info(field)
|
||||
embed = getattr(field_info, "embed", None)
|
||||
if len(required_params) == 1 and not embed:
|
||||
field_alias_omitted = len(required_params) == 1 and not embed
|
||||
if field_alias_omitted:
|
||||
received_body = {field.alias: received_body}
|
||||
|
||||
for field in required_params:
|
||||
loc: Tuple[str, ...]
|
||||
if field_alias_omitted:
|
||||
loc = ("body",)
|
||||
else:
|
||||
loc = ("body", field.alias)
|
||||
|
||||
value: Any = None
|
||||
if received_body is not None:
|
||||
if (
|
||||
@@ -658,7 +663,7 @@ async def request_body_to_args(
|
||||
try:
|
||||
value = received_body.get(field.alias)
|
||||
except AttributeError:
|
||||
errors.append(get_missing_field_error(field.alias))
|
||||
errors.append(get_missing_field_error(loc))
|
||||
continue
|
||||
if (
|
||||
value is None
|
||||
@@ -670,7 +675,7 @@ async def request_body_to_args(
|
||||
)
|
||||
):
|
||||
if field.required:
|
||||
errors.append(get_missing_field_error(field.alias))
|
||||
errors.append(get_missing_field_error(loc))
|
||||
else:
|
||||
values[field.name] = deepcopy(field.default)
|
||||
continue
|
||||
@@ -689,7 +694,9 @@ async def request_body_to_args(
|
||||
awaitables = [sub_value.read() for sub_value in value]
|
||||
contents = await asyncio.gather(*awaitables)
|
||||
value = sequence_shape_to_type[field.shape](contents)
|
||||
v_, errors_ = field.validate(value, values, loc=("body", field.alias))
|
||||
|
||||
v_, errors_ = field.validate(value, values, loc=loc)
|
||||
|
||||
if isinstance(errors_, ErrorWrapper):
|
||||
errors.append(errors_)
|
||||
elif isinstance(errors_, list):
|
||||
@@ -699,12 +706,12 @@ async def request_body_to_args(
|
||||
return values, errors
|
||||
|
||||
|
||||
def get_missing_field_error(field_alias: str) -> ErrorWrapper:
|
||||
def get_missing_field_error(loc: Tuple[str, ...]) -> ErrorWrapper:
|
||||
if PYDANTIC_1:
|
||||
missing_field_error = ErrorWrapper(MissingError(), loc=("body", field_alias))
|
||||
missing_field_error = ErrorWrapper(MissingError(), loc=loc)
|
||||
else: # pragma: no cover
|
||||
missing_field_error = ErrorWrapper( # type: ignore
|
||||
MissingError(), loc=("body", field_alias), config=BaseConfig,
|
||||
MissingError(), loc=loc, config=BaseConfig,
|
||||
)
|
||||
return missing_field_error
|
||||
|
||||
|
||||
@@ -71,6 +71,8 @@ def jsonable_encoder(
|
||||
by_alias=by_alias,
|
||||
skip_defaults=bool(exclude_unset or skip_defaults),
|
||||
)
|
||||
if "__root__" in obj_dict:
|
||||
obj_dict = obj_dict["__root__"]
|
||||
return jsonable_encoder(
|
||||
obj_dict,
|
||||
exclude_none=exclude_none,
|
||||
|
||||
@@ -44,7 +44,9 @@ def get_swagger_ui_html(
|
||||
SwaggerUIBundle.SwaggerUIStandalonePreset
|
||||
],
|
||||
layout: "BaseLayout",
|
||||
deepLinking: true
|
||||
deepLinking: true,
|
||||
showExtensions: true,
|
||||
showCommonExtensions: true
|
||||
})"""
|
||||
|
||||
if init_oauth:
|
||||
|
||||
@@ -63,7 +63,7 @@ class ServerVariable(BaseModel):
|
||||
|
||||
|
||||
class Server(BaseModel):
|
||||
url: AnyUrl
|
||||
url: Union[AnyUrl, str]
|
||||
description: Optional[str] = None
|
||||
variables: Optional[Dict[str, ServerVariable]] = None
|
||||
|
||||
@@ -112,7 +112,7 @@ class SchemaBase(BaseModel):
|
||||
allOf: Optional[List[Any]] = None
|
||||
oneOf: Optional[List[Any]] = None
|
||||
anyOf: Optional[List[Any]] = None
|
||||
not_: Optional[List[Any]] = Field(None, alias="not")
|
||||
not_: Optional[Any] = Field(None, alias="not")
|
||||
items: Optional[Any] = None
|
||||
properties: Optional[Dict[str, Any]] = None
|
||||
additionalProperties: Optional[Union[Dict[str, Any], bool]] = None
|
||||
@@ -133,7 +133,7 @@ class Schema(SchemaBase):
|
||||
allOf: Optional[List[SchemaBase]] = None
|
||||
oneOf: Optional[List[SchemaBase]] = None
|
||||
anyOf: Optional[List[SchemaBase]] = None
|
||||
not_: Optional[List[SchemaBase]] = Field(None, alias="not")
|
||||
not_: Optional[SchemaBase] = Field(None, alias="not")
|
||||
items: Optional[SchemaBase] = None
|
||||
properties: Optional[Dict[str, SchemaBase]] = None
|
||||
additionalProperties: Optional[Union[Dict[str, Any], bool]] = None
|
||||
|
||||
@@ -14,6 +14,7 @@ from fastapi.openapi.constants import (
|
||||
from fastapi.openapi.models import OpenAPI
|
||||
from fastapi.params import Body, Param
|
||||
from fastapi.utils import (
|
||||
deep_dict_update,
|
||||
generate_operation_id_for_path,
|
||||
get_field_info,
|
||||
get_model_definitions,
|
||||
@@ -86,7 +87,7 @@ def get_openapi_security_definitions(flat_dependant: Dependant) -> Tuple[Dict, L
|
||||
def get_openapi_operation_parameters(
|
||||
*,
|
||||
all_route_params: Sequence[ModelField],
|
||||
model_name_map: Dict[Union[Type[BaseModel], Type[Enum]], str]
|
||||
model_name_map: Dict[Union[Type[BaseModel], Type[Enum]], str],
|
||||
) -> List[Dict[str, Any]]:
|
||||
parameters = []
|
||||
for param in all_route_params:
|
||||
@@ -112,7 +113,7 @@ def get_openapi_operation_parameters(
|
||||
def get_openapi_operation_request_body(
|
||||
*,
|
||||
body_field: Optional[ModelField],
|
||||
model_name_map: Dict[Union[Type[BaseModel], Type[Enum]], str]
|
||||
model_name_map: Dict[Union[Type[BaseModel], Type[Enum]], str],
|
||||
) -> Optional[Dict]:
|
||||
if not body_field:
|
||||
return None
|
||||
@@ -201,33 +202,6 @@ def get_openapi_path(
|
||||
)
|
||||
callbacks[callback.name] = {callback.path: cb_path}
|
||||
operation["callbacks"] = callbacks
|
||||
if route.responses:
|
||||
for (additional_status_code, response) in route.responses.items():
|
||||
process_response = response.copy()
|
||||
assert isinstance(
|
||||
process_response, dict
|
||||
), "An additional response must be a dict"
|
||||
field = route.response_fields.get(additional_status_code)
|
||||
if field:
|
||||
response_schema, _, _ = field_schema(
|
||||
field, model_name_map=model_name_map, ref_prefix=REF_PREFIX
|
||||
)
|
||||
process_response.setdefault("content", {}).setdefault(
|
||||
route_response_media_type or "application/json", {}
|
||||
)["schema"] = response_schema
|
||||
status_text: Optional[str] = status_code_ranges.get(
|
||||
str(additional_status_code).upper()
|
||||
) or http.client.responses.get(int(additional_status_code))
|
||||
process_response.setdefault(
|
||||
"description", status_text or "Additional Response"
|
||||
)
|
||||
status_code_key = str(additional_status_code).upper()
|
||||
if status_code_key == "DEFAULT":
|
||||
status_code_key = "default"
|
||||
process_response.pop("model", None)
|
||||
operation.setdefault("responses", {})[
|
||||
status_code_key
|
||||
] = process_response
|
||||
status_code = str(route.status_code)
|
||||
operation.setdefault("responses", {}).setdefault(status_code, {})[
|
||||
"description"
|
||||
@@ -251,7 +225,47 @@ def get_openapi_path(
|
||||
).setdefault("content", {}).setdefault(route_response_media_type, {})[
|
||||
"schema"
|
||||
] = response_schema
|
||||
|
||||
if route.responses:
|
||||
operation_responses = operation.setdefault("responses", {})
|
||||
for (
|
||||
additional_status_code,
|
||||
additional_response,
|
||||
) in route.responses.items():
|
||||
process_response = additional_response.copy()
|
||||
process_response.pop("model", None)
|
||||
status_code_key = str(additional_status_code).upper()
|
||||
if status_code_key == "DEFAULT":
|
||||
status_code_key = "default"
|
||||
openapi_response = operation_responses.setdefault(
|
||||
status_code_key, {}
|
||||
)
|
||||
assert isinstance(
|
||||
process_response, dict
|
||||
), "An additional response must be a dict"
|
||||
field = route.response_fields.get(additional_status_code)
|
||||
additional_field_schema: Optional[Dict[str, Any]] = None
|
||||
if field:
|
||||
additional_field_schema, _, _ = field_schema(
|
||||
field, model_name_map=model_name_map, ref_prefix=REF_PREFIX
|
||||
)
|
||||
media_type = route_response_media_type or "application/json"
|
||||
additional_schema = (
|
||||
process_response.setdefault("content", {})
|
||||
.setdefault(media_type, {})
|
||||
.setdefault("schema", {})
|
||||
)
|
||||
deep_dict_update(additional_schema, additional_field_schema)
|
||||
status_text: Optional[str] = status_code_ranges.get(
|
||||
str(additional_status_code).upper()
|
||||
) or http.client.responses.get(int(additional_status_code))
|
||||
description = (
|
||||
process_response.get("description")
|
||||
or openapi_response.get("description")
|
||||
or status_text
|
||||
or "Additional Response"
|
||||
)
|
||||
deep_dict_update(openapi_response, process_response)
|
||||
openapi_response["description"] = description
|
||||
http422 = str(HTTP_422_UNPROCESSABLE_ENTITY)
|
||||
if (all_route_params or route.body_field) and not any(
|
||||
[
|
||||
@@ -317,12 +331,16 @@ def get_openapi(
|
||||
openapi_version: str = "3.0.2",
|
||||
description: str = None,
|
||||
routes: Sequence[BaseRoute],
|
||||
openapi_prefix: str = ""
|
||||
openapi_prefix: str = "",
|
||||
tags: Optional[List[Dict[str, Any]]] = None,
|
||||
servers: Optional[List[Dict[str, Union[str, Any]]]] = None,
|
||||
) -> Dict:
|
||||
info = {"title": title, "version": version}
|
||||
if description:
|
||||
info["description"] = description
|
||||
output = {"openapi": openapi_version, "info": info}
|
||||
output: Dict[str, Any] = {"openapi": openapi_version, "info": info}
|
||||
if servers:
|
||||
output["servers"] = servers
|
||||
components: Dict[str, Dict] = {}
|
||||
paths: Dict[str, Dict] = {}
|
||||
flat_models = get_flat_models_from_routes(routes)
|
||||
@@ -352,4 +370,6 @@ def get_openapi(
|
||||
if components:
|
||||
output["components"] = components
|
||||
output["paths"] = paths
|
||||
if tags:
|
||||
output["tags"] = tags
|
||||
return jsonable_encoder(OpenAPI(**output), by_alias=True, exclude_none=True)
|
||||
|
||||
@@ -51,6 +51,9 @@ class Param(FieldInfo):
|
||||
**extra,
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self.__class__.__name__}({self.default})"
|
||||
|
||||
|
||||
class Path(Param):
|
||||
in_ = ParamTypes.path
|
||||
@@ -239,6 +242,9 @@ class Body(FieldInfo):
|
||||
**extra,
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self.__class__.__name__}({self.default})"
|
||||
|
||||
|
||||
class Form(Body):
|
||||
def __init__(
|
||||
@@ -316,6 +322,11 @@ class Depends:
|
||||
self.dependency = dependency
|
||||
self.use_cache = use_cache
|
||||
|
||||
def __repr__(self) -> str:
|
||||
attr = getattr(self.dependency, "__name__", type(self.dependency).__name__)
|
||||
cache = "" if self.use_cache else ", use_cache=False"
|
||||
return f"{self.__class__.__name__}({attr}{cache})"
|
||||
|
||||
|
||||
class Security(Depends):
|
||||
def __init__(
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import asyncio
|
||||
import enum
|
||||
import inspect
|
||||
import json
|
||||
from typing import Any, Callable, Dict, List, Optional, Sequence, Set, Type, Union
|
||||
|
||||
from fastapi import params
|
||||
@@ -12,7 +14,6 @@ from fastapi.dependencies.utils import (
|
||||
)
|
||||
from fastapi.encoders import DictIntStrAny, SetIntStr, jsonable_encoder
|
||||
from fastapi.exceptions import RequestValidationError, WebSocketRequestValidationError
|
||||
from fastapi.logger import logger
|
||||
from fastapi.openapi.constants import STATUS_CODES_WITH_NO_BODY
|
||||
from fastapi.utils import (
|
||||
PYDANTIC_1,
|
||||
@@ -178,6 +179,8 @@ def get_request_handler(
|
||||
body_bytes = await request.body()
|
||||
if body_bytes:
|
||||
body = await request.json()
|
||||
except json.JSONDecodeError as e:
|
||||
raise RequestValidationError([ErrorWrapper(e, ("body", e.pos))], body=e.doc)
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=400, detail="There was an error parsing the body"
|
||||
@@ -294,6 +297,9 @@ class APIRoute(routing.Route):
|
||||
dependency_overrides_provider: Any = None,
|
||||
callbacks: Optional[List["APIRoute"]] = None,
|
||||
) -> None:
|
||||
# normalise enums e.g. http.HTTPStatus
|
||||
if isinstance(status_code, enum.IntEnum):
|
||||
status_code = int(status_code)
|
||||
self.path = path
|
||||
self.endpoint = endpoint
|
||||
self.name = get_name(endpoint) if name is None else name
|
||||
|
||||
@@ -11,6 +11,7 @@ from .oauth2 import (
|
||||
OAuth2AuthorizationCodeBearer,
|
||||
OAuth2PasswordBearer,
|
||||
OAuth2PasswordRequestForm,
|
||||
OAuth2PasswordRequestFormStrict,
|
||||
SecurityScopes,
|
||||
)
|
||||
from .open_id_connect_url import OpenIdConnect
|
||||
|
||||
@@ -172,3 +172,15 @@ def generate_operation_id_for_path(*, name: str, path: str, method: str) -> str:
|
||||
operation_id = re.sub("[^0-9a-zA-Z_]", "_", operation_id)
|
||||
operation_id = operation_id + "_" + method.lower()
|
||||
return operation_id
|
||||
|
||||
|
||||
def deep_dict_update(main_dict: dict, update_dict: dict) -> None:
|
||||
for key in update_dict:
|
||||
if (
|
||||
key in main_dict
|
||||
and isinstance(main_dict[key], dict)
|
||||
and isinstance(update_dict[key], dict)
|
||||
):
|
||||
deep_dict_update(main_dict[key], update_dict[key])
|
||||
else:
|
||||
main_dict[key] = update_dict[key]
|
||||
|
||||
@@ -32,7 +32,7 @@ classifiers = [
|
||||
"Topic :: Internet :: WWW/HTTP",
|
||||
]
|
||||
requires = [
|
||||
"starlette ==0.13.2",
|
||||
"starlette ==0.13.4",
|
||||
"pydantic >=0.32.2,<2.0.0"
|
||||
]
|
||||
description-file = "README.md"
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import http
|
||||
|
||||
from fastapi import FastAPI, Path, Query
|
||||
|
||||
app = FastAPI()
|
||||
@@ -184,3 +186,8 @@ def get_query_param_required(query=Query(...)):
|
||||
@app.get("/query/param-required/int")
|
||||
def get_query_param_required_type(query: int = Query(...)):
|
||||
return f"foo bar {query}"
|
||||
|
||||
|
||||
@app.get("/enum-status-code", status_code=http.HTTPStatus.CREATED)
|
||||
def get_enum_status_code():
|
||||
return "foo bar"
|
||||
|
||||
@@ -1078,6 +1078,18 @@ openapi_schema = {
|
||||
],
|
||||
}
|
||||
},
|
||||
"/enum-status-code": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
},
|
||||
"summary": "Get Enum Status Code",
|
||||
"operationId": "get_enum_status_code_enum_status_code_get",
|
||||
}
|
||||
},
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
@@ -1149,3 +1161,9 @@ def test_redoc():
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.headers["content-type"] == "text/html; charset=utf-8"
|
||||
assert "redoc@next" in response.text
|
||||
|
||||
|
||||
def test_enum_status_code_response():
|
||||
response = client.get("/enum-status-code")
|
||||
assert response.status_code == 201, response.text
|
||||
assert response.json() == "foo bar"
|
||||
|
||||
65
tests/test_dependency_security_overrides.py
Normal file
65
tests/test_dependency_security_overrides.py
Normal file
@@ -0,0 +1,65 @@
|
||||
from typing import List, Tuple
|
||||
|
||||
from fastapi import Depends, FastAPI, Security
|
||||
from fastapi.security import SecurityScopes
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
def get_user(required_scopes: SecurityScopes):
|
||||
return "john", required_scopes.scopes
|
||||
|
||||
|
||||
def get_user_override(required_scopes: SecurityScopes):
|
||||
return "alice", required_scopes.scopes
|
||||
|
||||
|
||||
def get_data():
|
||||
return [1, 2, 3]
|
||||
|
||||
|
||||
def get_data_override():
|
||||
return [3, 4, 5]
|
||||
|
||||
|
||||
@app.get("/user")
|
||||
def read_user(
|
||||
user_data: Tuple[str, List[str]] = Security(get_user, scopes=["foo", "bar"]),
|
||||
data: List[int] = Depends(get_data),
|
||||
):
|
||||
return {"user": user_data[0], "scopes": user_data[1], "data": data}
|
||||
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
def test_normal():
|
||||
response = client.get("/user")
|
||||
assert response.json() == {
|
||||
"user": "john",
|
||||
"scopes": ["foo", "bar"],
|
||||
"data": [1, 2, 3],
|
||||
}
|
||||
|
||||
|
||||
def test_override_data():
|
||||
app.dependency_overrides[get_data] = get_data_override
|
||||
response = client.get("/user")
|
||||
assert response.json() == {
|
||||
"user": "john",
|
||||
"scopes": ["foo", "bar"],
|
||||
"data": [3, 4, 5],
|
||||
}
|
||||
app.dependency_overrides = {}
|
||||
|
||||
|
||||
def test_override_security():
|
||||
app.dependency_overrides[get_user] = get_user_override
|
||||
response = client.get("/user")
|
||||
assert response.json() == {
|
||||
"user": "alice",
|
||||
"scopes": ["foo", "bar"],
|
||||
"data": [1, 2, 3],
|
||||
}
|
||||
app.dependency_overrides = {}
|
||||
@@ -76,6 +76,10 @@ class ModelWithDefault(BaseModel):
|
||||
bla: str = "bla"
|
||||
|
||||
|
||||
class ModelWithRoot(BaseModel):
|
||||
__root__: str
|
||||
|
||||
|
||||
@pytest.fixture(
|
||||
name="model_with_path", params=[PurePath, PurePosixPath, PureWindowsPath]
|
||||
)
|
||||
@@ -158,3 +162,8 @@ def test_encode_model_with_path(model_with_path):
|
||||
else:
|
||||
expected = "/foo/bar"
|
||||
assert jsonable_encoder(model_with_path) == {"path": expected}
|
||||
|
||||
|
||||
def test_encode_root():
|
||||
model = ModelWithRoot(__root__="Foo")
|
||||
assert jsonable_encoder(model) == "Foo"
|
||||
|
||||
@@ -104,7 +104,7 @@ single_error = {
|
||||
"detail": [
|
||||
{
|
||||
"ctx": {"limit_value": 0.0},
|
||||
"loc": ["body", "item", 0, "age"],
|
||||
"loc": ["body", 0, "age"],
|
||||
"msg": "ensure this value is greater than 0",
|
||||
"type": "value_error.number.not_gt",
|
||||
}
|
||||
@@ -114,22 +114,22 @@ single_error = {
|
||||
multiple_errors = {
|
||||
"detail": [
|
||||
{
|
||||
"loc": ["body", "item", 0, "name"],
|
||||
"loc": ["body", 0, "name"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
{
|
||||
"loc": ["body", "item", 0, "age"],
|
||||
"loc": ["body", 0, "age"],
|
||||
"msg": "value is not a valid decimal",
|
||||
"type": "type_error.decimal",
|
||||
},
|
||||
{
|
||||
"loc": ["body", "item", 1, "name"],
|
||||
"loc": ["body", 1, "name"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
{
|
||||
"loc": ["body", "item", 1, "age"],
|
||||
"loc": ["body", 1, "age"],
|
||||
"msg": "value is not a valid decimal",
|
||||
"type": "type_error.decimal",
|
||||
},
|
||||
|
||||
60
tests/test_openapi_servers.py
Normal file
60
tests/test_openapi_servers.py
Normal file
@@ -0,0 +1,60 @@
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
app = FastAPI(
|
||||
servers=[
|
||||
{"url": "/", "description": "Default, relative server"},
|
||||
{
|
||||
"url": "http://staging.localhost.tiangolo.com:8000",
|
||||
"description": "Staging but actually localhost still",
|
||||
},
|
||||
{"url": "https://prod.example.com"},
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@app.get("/foo")
|
||||
def foo():
|
||||
return {"message": "Hello World"}
|
||||
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
openapi_schema = {
|
||||
"openapi": "3.0.2",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"servers": [
|
||||
{"url": "/", "description": "Default, relative server"},
|
||||
{
|
||||
"url": "http://staging.localhost.tiangolo.com:8000",
|
||||
"description": "Staging but actually localhost still",
|
||||
},
|
||||
{"url": "https://prod.example.com"},
|
||||
],
|
||||
"paths": {
|
||||
"/foo": {
|
||||
"get": {
|
||||
"summary": "Foo",
|
||||
"operationId": "foo_foo_get",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def test_openapi_servers():
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == openapi_schema
|
||||
|
||||
|
||||
def test_app():
|
||||
response = client.get("/foo")
|
||||
assert response.status_code == 200, response.text
|
||||
46
tests/test_params_repr.py
Normal file
46
tests/test_params_repr.py
Normal file
@@ -0,0 +1,46 @@
|
||||
import pytest
|
||||
from fastapi.params import Body, Cookie, Depends, Header, Param, Path, Query
|
||||
|
||||
test_data = ["teststr", None, ..., 1, []]
|
||||
|
||||
|
||||
def get_user():
|
||||
return {} # pragma: no cover
|
||||
|
||||
|
||||
@pytest.fixture(scope="function", params=test_data)
|
||||
def params(request):
|
||||
return request.param
|
||||
|
||||
|
||||
def test_param_repr(params):
|
||||
assert repr(Param(params)) == "Param(" + str(params) + ")"
|
||||
|
||||
|
||||
def test_path_repr(params):
|
||||
assert repr(Path(params)) == "Path(Ellipsis)"
|
||||
|
||||
|
||||
def test_query_repr(params):
|
||||
assert repr(Query(params)) == "Query(" + str(params) + ")"
|
||||
|
||||
|
||||
def test_header_repr(params):
|
||||
assert repr(Header(params)) == "Header(" + str(params) + ")"
|
||||
|
||||
|
||||
def test_cookie_repr(params):
|
||||
assert repr(Cookie(params)) == "Cookie(" + str(params) + ")"
|
||||
|
||||
|
||||
def test_body_repr(params):
|
||||
assert repr(Body(params)) == "Body(" + str(params) + ")"
|
||||
|
||||
|
||||
def test_depends_repr():
|
||||
assert repr(Depends()) == "Depends(NoneType)"
|
||||
assert repr(Depends(get_user)) == "Depends(get_user)"
|
||||
assert repr(Depends(use_cache=False)) == "Depends(NoneType, use_cache=False)"
|
||||
assert (
|
||||
repr(Depends(get_user, use_cache=False)) == "Depends(get_user, use_cache=False)"
|
||||
)
|
||||
34
tests/test_repeated_cookie_headers.py
Normal file
34
tests/test_repeated_cookie_headers.py
Normal file
@@ -0,0 +1,34 @@
|
||||
from fastapi import Depends, FastAPI, Response
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
def set_cookie(*, response: Response):
|
||||
response.set_cookie("cookie-name", "cookie-value")
|
||||
return {}
|
||||
|
||||
|
||||
def set_indirect_cookie(*, dep: str = Depends(set_cookie)):
|
||||
return dep
|
||||
|
||||
|
||||
@app.get("/directCookie")
|
||||
def get_direct_cookie(dep: str = Depends(set_cookie)):
|
||||
return {"dep": dep}
|
||||
|
||||
|
||||
@app.get("/indirectCookie")
|
||||
def get_indirect_cookie(dep: str = Depends(set_indirect_cookie)):
|
||||
return {"dep": dep}
|
||||
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
def test_cookie_is_set_once():
|
||||
direct_response = client.get("/directCookie")
|
||||
indirect_response = client.get("/indirectCookie")
|
||||
assert (
|
||||
direct_response.headers["set-cookie"] == indirect_response.headers["set-cookie"]
|
||||
)
|
||||
@@ -1,7 +1,6 @@
|
||||
import pytest
|
||||
from fastapi import Depends, FastAPI, Security
|
||||
from fastapi.security import OAuth2
|
||||
from fastapi.security.oauth2 import OAuth2PasswordRequestFormStrict
|
||||
from fastapi.security import OAuth2, OAuth2PasswordRequestFormStrict
|
||||
from fastapi.testclient import TestClient
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
@@ -2,8 +2,7 @@ from typing import Optional
|
||||
|
||||
import pytest
|
||||
from fastapi import Depends, FastAPI, Security
|
||||
from fastapi.security import OAuth2
|
||||
from fastapi.security.oauth2 import OAuth2PasswordRequestFormStrict
|
||||
from fastapi.security import OAuth2, OAuth2PasswordRequestFormStrict
|
||||
from fastapi.testclient import TestClient
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ openapi_schema = {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"description": "Return the JSON item or an image.",
|
||||
"content": {
|
||||
"image/png": {},
|
||||
"application/json": {
|
||||
|
||||
@@ -20,7 +20,7 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"description": "Item requested by ID",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {"$ref": "#/components/schemas/Item"},
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
@@ -92,7 +94,7 @@ def test_openapi_schema():
|
||||
price_missing = {
|
||||
"detail": [
|
||||
{
|
||||
"loc": ["body", "item", "price"],
|
||||
"loc": ["body", "price"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
}
|
||||
@@ -102,7 +104,7 @@ price_missing = {
|
||||
price_not_float = {
|
||||
"detail": [
|
||||
{
|
||||
"loc": ["body", "item", "price"],
|
||||
"loc": ["body", "price"],
|
||||
"msg": "value is not a valid float",
|
||||
"type": "type_error.float",
|
||||
}
|
||||
@@ -112,12 +114,12 @@ price_not_float = {
|
||||
name_price_missing = {
|
||||
"detail": [
|
||||
{
|
||||
"loc": ["body", "item", "name"],
|
||||
"loc": ["body", "name"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
{
|
||||
"loc": ["body", "item", "price"],
|
||||
"loc": ["body", "price"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
},
|
||||
@@ -126,11 +128,7 @@ name_price_missing = {
|
||||
|
||||
body_missing = {
|
||||
"detail": [
|
||||
{
|
||||
"loc": ["body", "item"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
}
|
||||
{"loc": ["body"], "msg": "field required", "type": "value_error.missing",}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -176,5 +174,24 @@ def test_post_body(path, body, expected_status, expected_response):
|
||||
|
||||
def test_post_broken_body():
|
||||
response = client.post("/items/", data={"name": "Foo", "price": 50.5})
|
||||
assert response.status_code == 400, response.text
|
||||
assert response.status_code == 422, response.text
|
||||
assert response.json() == {
|
||||
"detail": [
|
||||
{
|
||||
"ctx": {
|
||||
"colno": 1,
|
||||
"doc": "name=Foo&price=50.5",
|
||||
"lineno": 1,
|
||||
"msg": "Expecting value",
|
||||
"pos": 0,
|
||||
},
|
||||
"loc": ["body", 0],
|
||||
"msg": "Expecting value: line 1 column 1 (char 0)",
|
||||
"type": "value_error.jsondecode",
|
||||
}
|
||||
]
|
||||
}
|
||||
with patch("json.loads", side_effect=Exception):
|
||||
response = client.post("/items/", json={"test": "test2"})
|
||||
assert response.status_code == 400, response.text
|
||||
assert response.json() == {"detail": "There was an error parsing the body"}
|
||||
|
||||
@@ -95,7 +95,7 @@ def test_post_invalid_body():
|
||||
assert response.json() == {
|
||||
"detail": [
|
||||
{
|
||||
"loc": ["body", "weights", "__key__"],
|
||||
"loc": ["body", "__key__"],
|
||||
"msg": "value is not a valid integer",
|
||||
"type": "type_error.integer",
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ def test_exception_handler_body_access():
|
||||
"body": '{"numbers": [1, 2, 3]}',
|
||||
"errors": [
|
||||
{
|
||||
"loc": ["body", "numbers"],
|
||||
"loc": ["body"],
|
||||
"msg": "value is not a valid list",
|
||||
"type": "type_error.list",
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ def test_post_validation_error():
|
||||
assert response.json() == {
|
||||
"detail": [
|
||||
{
|
||||
"loc": ["body", "item", "size"],
|
||||
"loc": ["body", "size"],
|
||||
"msg": "value is not a valid integer",
|
||||
"type": "type_error.integer",
|
||||
}
|
||||
|
||||
65
tests/test_tutorial/test_metadata/test_tutorial004.py
Normal file
65
tests/test_tutorial/test_metadata/test_tutorial004.py
Normal file
@@ -0,0 +1,65 @@
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from metadata.tutorial004 import app
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
openapi_schema = {
|
||||
"openapi": "3.0.2",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/users/": {
|
||||
"get": {
|
||||
"tags": ["users"],
|
||||
"summary": "Get Users",
|
||||
"operationId": "get_users_users__get",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"/items/": {
|
||||
"get": {
|
||||
"tags": ["items"],
|
||||
"summary": "Get Items",
|
||||
"operationId": "get_items_items__get",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
"tags": [
|
||||
{
|
||||
"name": "users",
|
||||
"description": "Operations with users. The **login** logic is also here.",
|
||||
},
|
||||
{
|
||||
"name": "items",
|
||||
"description": "Manage items. So _fancy_ they have their own docs.",
|
||||
"externalDocs": {
|
||||
"description": "Items external docs",
|
||||
"url": "https://fastapi.tiangolo.com/",
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def test_openapi_schema():
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == openapi_schema
|
||||
|
||||
|
||||
def test_path_operations():
|
||||
response = client.get("/items/")
|
||||
assert response.status_code == 200, response.text
|
||||
response = client.get("/users/")
|
||||
assert response.status_code == 200, response.text
|
||||
@@ -15,69 +15,65 @@ def test_main():
|
||||
def test_websocket_with_cookie():
|
||||
with pytest.raises(WebSocketDisconnect):
|
||||
with client.websocket_connect(
|
||||
"/items/1/ws", cookies={"session": "fakesession"}
|
||||
"/items/foo/ws", cookies={"session": "fakesession"}
|
||||
) as websocket:
|
||||
message = "Message one"
|
||||
websocket.send_text(message)
|
||||
data = websocket.receive_text()
|
||||
assert data == "Session Cookie or X-Client Header value is: fakesession"
|
||||
assert data == "Session cookie or query token value is: fakesession"
|
||||
data = websocket.receive_text()
|
||||
assert data == f"Message text was: {message}, for item ID: 1"
|
||||
assert data == f"Message text was: {message}, for item ID: foo"
|
||||
message = "Message two"
|
||||
websocket.send_text(message)
|
||||
data = websocket.receive_text()
|
||||
assert data == "Session Cookie or X-Client Header value is: fakesession"
|
||||
assert data == "Session cookie or query token value is: fakesession"
|
||||
data = websocket.receive_text()
|
||||
assert data == f"Message text was: {message}, for item ID: 1"
|
||||
assert data == f"Message text was: {message}, for item ID: foo"
|
||||
|
||||
|
||||
def test_websocket_with_header():
|
||||
with pytest.raises(WebSocketDisconnect):
|
||||
with client.websocket_connect(
|
||||
"/items/2/ws", headers={"X-Client": "xmen"}
|
||||
) as websocket:
|
||||
with client.websocket_connect("/items/bar/ws?token=some-token") as websocket:
|
||||
message = "Message one"
|
||||
websocket.send_text(message)
|
||||
data = websocket.receive_text()
|
||||
assert data == "Session Cookie or X-Client Header value is: xmen"
|
||||
assert data == "Session cookie or query token value is: some-token"
|
||||
data = websocket.receive_text()
|
||||
assert data == f"Message text was: {message}, for item ID: 2"
|
||||
assert data == f"Message text was: {message}, for item ID: bar"
|
||||
message = "Message two"
|
||||
websocket.send_text(message)
|
||||
data = websocket.receive_text()
|
||||
assert data == "Session Cookie or X-Client Header value is: xmen"
|
||||
assert data == "Session cookie or query token value is: some-token"
|
||||
data = websocket.receive_text()
|
||||
assert data == f"Message text was: {message}, for item ID: 2"
|
||||
assert data == f"Message text was: {message}, for item ID: bar"
|
||||
|
||||
|
||||
def test_websocket_with_header_and_query():
|
||||
with pytest.raises(WebSocketDisconnect):
|
||||
with client.websocket_connect(
|
||||
"/items/2/ws?q=baz", headers={"X-Client": "xmen"}
|
||||
) as websocket:
|
||||
with client.websocket_connect("/items/2/ws?q=3&token=some-token") as websocket:
|
||||
message = "Message one"
|
||||
websocket.send_text(message)
|
||||
data = websocket.receive_text()
|
||||
assert data == "Session Cookie or X-Client Header value is: xmen"
|
||||
assert data == "Session cookie or query token value is: some-token"
|
||||
data = websocket.receive_text()
|
||||
assert data == "Query parameter q is: baz"
|
||||
assert data == "Query parameter q is: 3"
|
||||
data = websocket.receive_text()
|
||||
assert data == f"Message text was: {message}, for item ID: 2"
|
||||
message = "Message two"
|
||||
websocket.send_text(message)
|
||||
data = websocket.receive_text()
|
||||
assert data == "Session Cookie or X-Client Header value is: xmen"
|
||||
assert data == "Session cookie or query token value is: some-token"
|
||||
data = websocket.receive_text()
|
||||
assert data == "Query parameter q is: baz"
|
||||
assert data == "Query parameter q is: 3"
|
||||
data = websocket.receive_text()
|
||||
assert data == f"Message text was: {message}, for item ID: 2"
|
||||
|
||||
|
||||
def test_websocket_no_credentials():
|
||||
with pytest.raises(WebSocketDisconnect):
|
||||
client.websocket_connect("/items/2/ws")
|
||||
client.websocket_connect("/items/foo/ws")
|
||||
|
||||
|
||||
def test_websocket_invalid_data():
|
||||
with pytest.raises(WebSocketDisconnect):
|
||||
client.websocket_connect("/items/foo/ws", headers={"X-Client": "xmen"})
|
||||
client.websocket_connect("/items/foo/ws?q=bar&token=some-token")
|
||||
|
||||
Reference in New Issue
Block a user