mirror of
https://github.com/fastapi/fastapi.git
synced 2025-12-24 22:59:32 -05:00
Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d4ddf4e62a | ||
|
|
cb25dab986 | ||
|
|
7aa1628336 | ||
|
|
1dbd3d7aa7 | ||
|
|
24e73e01c7 | ||
|
|
e26f94018c | ||
|
|
1ce67887b9 | ||
|
|
8e0200607f | ||
|
|
bd407ca705 | ||
|
|
48b5ef1681 | ||
|
|
afad59dfbb | ||
|
|
0f9be1d2e7 | ||
|
|
48c2406495 | ||
|
|
9958d93120 | ||
|
|
b63512cd92 | ||
|
|
74c4d1c1db | ||
|
|
7ccd81f706 | ||
|
|
1da8d3f1e6 | ||
|
|
92016da962 | ||
|
|
9c3c9b6e78 | ||
|
|
687871e46a | ||
|
|
3c1803897f | ||
|
|
1c3289f115 | ||
|
|
35d41adc7b | ||
|
|
9a3dd91030 | ||
|
|
017eae63b1 | ||
|
|
8af4454251 | ||
|
|
adf252768c | ||
|
|
3705bdefd5 | ||
|
|
25e94d6344 | ||
|
|
26c345b766 | ||
|
|
9ce50b4684 | ||
|
|
66954c7ded | ||
|
|
e0c3519b94 |
29
.github/workflows/deploy-docs.yml
vendored
Normal file
29
.github/workflows/deploy-docs.yml
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
name: Build and Deploy to Netlify
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
types: [opened, synchronize]
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-18.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: "3.7"
|
||||
- name: Install Flit
|
||||
run: python3.7 -m pip install flit
|
||||
- name: Install docs extras
|
||||
run: python3.7 -m flit install --extras doc
|
||||
- name: Build MkDocs
|
||||
run: python3.7 -m mkdocs build
|
||||
- name: Deploy to Netlify
|
||||
uses: nwtgck/actions-netlify@v1.0.3
|
||||
with:
|
||||
publish-dir: './site'
|
||||
production-branch: master
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
env:
|
||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
|
||||
@@ -429,31 +429,31 @@ Repeat the same process with the 10 tabs. This time all of them will wait and yo
|
||||
|
||||
* `sql_app/database.py`:
|
||||
|
||||
```Python hl_lines=""
|
||||
```Python
|
||||
{!./src/sql_databases_peewee/sql_app/database.py!}
|
||||
```
|
||||
|
||||
* `sql_app/models.py`:
|
||||
|
||||
```Python hl_lines=""
|
||||
```Python
|
||||
{!./src/sql_databases_peewee/sql_app/models.py!}
|
||||
```
|
||||
|
||||
* `sql_app/schemas.py`:
|
||||
|
||||
```Python hl_lines=""
|
||||
```Python
|
||||
{!./src/sql_databases_peewee/sql_app/schemas.py!}
|
||||
```
|
||||
|
||||
* `sql_app/crud.py`:
|
||||
|
||||
```Python hl_lines=""
|
||||
```Python
|
||||
{!./src/sql_databases_peewee/sql_app/crud.py!}
|
||||
```
|
||||
|
||||
* `sql_app/main.py`:
|
||||
|
||||
```Python hl_lines=""
|
||||
```Python
|
||||
{!./src/sql_databases_peewee/sql_app/main.py!}
|
||||
```
|
||||
|
||||
|
||||
@@ -79,6 +79,10 @@ Here's an incomplete list of some of them.
|
||||
|
||||
* <a href="https://rightcode.co.jp/blog/information-technology/fastapi-tutorial-todo-apps-admin-page-improvement" class="external-link" target="_blank">【第4回】FastAPIチュートリアル: toDoアプリを作ってみよう【管理者ページ改良編】</a> by <a href="https://rightcode.co.jp/author/jun" class="external-link" target="_blank">ライトコードメディア編集部</a>
|
||||
|
||||
* <a href="https://qiita.com/bee2/items/0ad260ab9835a2087dae" class="external-link" target="_blank">PythonのWeb frameworkのパフォーマンス比較 (Django, Flask, responder, FastAPI, japronto)</a> by <a href="https://qiita.com/bee2" class="external-link" target="_blank">@bee2</a>.
|
||||
|
||||
* <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>.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Are you liking **FastAPI**?
|
||||
Do you like **FastAPI**?
|
||||
|
||||
Would you like to help FastAPI, other users, and the author?
|
||||
|
||||
|
||||
@@ -256,7 +256,7 @@ Taken from the official Pydantic docs:
|
||||
|
||||
**FastAPI** is all based on Pydantic.
|
||||
|
||||
You will see a lot more of all this in practice in the [Tutorial - User Guide](tutorial/){.internal-link target=_blank}.
|
||||
You will see a lot more of all this in practice in the [Tutorial - User Guide](tutorial/index.md){.internal-link target=_blank}.
|
||||
|
||||
## Type hints in **FastAPI**
|
||||
|
||||
@@ -276,7 +276,7 @@ With **FastAPI** you declare parameters with type hints and you get:
|
||||
* **Document** the API using OpenAPI:
|
||||
* which is then used by the automatic interactive documentation user interfaces.
|
||||
|
||||
This might all sound abstract. Don't worry. You'll see all this in action in the [Tutorial - User Guide](tutorial/){.internal-link target=_blank}.
|
||||
This might all sound abstract. Don't worry. You'll see all this in action in the [Tutorial - User Guide](tutorial/index.md){.internal-link target=_blank}.
|
||||
|
||||
The important thing is that by using standard Python types, in a single place (instead of adding more classes, decorators, etc), **FastAPI** will do a lot of the work for you.
|
||||
|
||||
|
||||
@@ -1,5 +1,33 @@
|
||||
## Latest changes
|
||||
|
||||
## 0.49.2
|
||||
|
||||
* Fix links in release notes. PR [#1052](https://github.com/tiangolo/fastapi/pull/1052) by [@sattosan](https://github.com/sattosan).
|
||||
* Fix typo in release notes. PR [#1051](https://github.com/tiangolo/fastapi/pull/1051) by [@sattosan](https://github.com/sattosan).
|
||||
* Refactor/clarify `serialize_response` parameter name to avoid confusion. PR [#1031](https://github.com/tiangolo/fastapi/pull/1031) by [@patrickmckenna](https://github.com/patrickmckenna).
|
||||
* Refactor calling each a path operation's handler function in an isolated function, to simplify profiling. PR [#1027](https://github.com/tiangolo/fastapi/pull/1027) by [@sm-Fifteen](https://github.com/sm-Fifteen).
|
||||
* Add missing dependencies for testing. PR [#1026](https://github.com/tiangolo/fastapi/pull/1026) by [@sm-Fifteen](https://github.com/sm-Fifteen).
|
||||
* Fix accepting valid types for response models, including Python types like `List[int]`. PR [#1017](https://github.com/tiangolo/fastapi/pull/1017) by [@patrickmckenna](https://github.com/patrickmckenna).
|
||||
* Fix format in SQL tutorial. PR [#1015](https://github.com/tiangolo/fastapi/pull/1015) by [@vegarsti](https://github.com/vegarsti).
|
||||
|
||||
## 0.49.1
|
||||
|
||||
* Fix path operation duplicated parameters when used in dependencies and the path operation function. PR [#994](https://github.com/tiangolo/fastapi/pull/994) by [@merowinger92](https://github.com/merowinger92).
|
||||
* Update Netlify previews deployment GitHub action as the fix is already merged and there's a new release. PR [#1047](https://github.com/tiangolo/fastapi/pull/1047).
|
||||
* Move mypy configurations to config file. PR [#987](https://github.com/tiangolo/fastapi/pull/987) by [@hukkinj1](https://github.com/hukkinj1).
|
||||
* Temporary fix to Netlify previews not deployable from PRs from forks. PR [#1046](https://github.com/tiangolo/fastapi/pull/1046) by [@mariacamilagl](https://github.com/mariacamilagl).
|
||||
|
||||
## 0.49.0
|
||||
|
||||
* Fix encoding of `pathlib` paths in `jsonable_encoder`. PR [#978](https://github.com/tiangolo/fastapi/pull/978) by [@patrickmckenna](https://github.com/patrickmckenna).
|
||||
* Add articles to [External Links](https://fastapi.tiangolo.com/external-links/): [PythonのWeb frameworkのパフォーマンス比較 (Django, Flask, responder, FastAPI, japronto)](https://qiita.com/bee2/items/0ad260ab9835a2087dae) and [[FastAPI] Python製のASGI Web フレームワーク FastAPIに入門する](https://qiita.com/bee2/items/75d9c0d7ba20e7a4a0e9). PR [#974](https://github.com/tiangolo/fastapi/pull/974) by [@tokusumi](https://github.com/tokusumi).
|
||||
* Fix broken links in docs. PR [#949](https://github.com/tiangolo/fastapi/pull/949) by [@tsotnikov](https://github.com/tsotnikov).
|
||||
* Fix small typos. PR [#941](https://github.com/tiangolo/fastapi/pull/941) by [@NikitaKolesov](https://github.com/NikitaKolesov).
|
||||
* Update and clarify docs for dependencies with `yield`. PR [#986](https://github.com/tiangolo/fastapi/pull/986).
|
||||
* Add Mermaid JS support for diagrams in docs. Add first diagrams to [Dependencies: First Steps](https://fastapi.tiangolo.com/tutorial/dependencies/) and [Dependencies with `yield` and `HTTPException`](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-with-yield/#dependencies-with-yield-and-httpexception). PR [#985](https://github.com/tiangolo/fastapi/pull/985).
|
||||
* Update CI to run docs deployment in GitHub actions. PR [#983](https://github.com/tiangolo/fastapi/pull/983).
|
||||
* Allow `callable`s in *path operation functions*, like functions modified with `functools.partial`. PR [#977](https://github.com/tiangolo/fastapi/pull/977).
|
||||
|
||||
## 0.48.0
|
||||
|
||||
* Run linters first in tests to error out faster. PR [#948](https://github.com/tiangolo/fastapi/pull/948).
|
||||
@@ -30,9 +58,9 @@
|
||||
* Fix handling form *path operation* parameters declared with pure classes like `list`, `tuple`, etc. PR [#856](https://github.com/tiangolo/fastapi/pull/856) by [@nsidnev](https://github.com/nsidnev).
|
||||
* Add request `body` to `RequestValidationError`, new docs: [Use the `RequestValidationError` body](https://fastapi.tiangolo.com/tutorial/handling-errors/#use-the-requestvalidationerror-body). Initial PR [#853](https://github.com/tiangolo/fastapi/pull/853) by [@aviramha](https://github.com/aviramha).
|
||||
* Update [External Links](https://fastapi.tiangolo.com/external-links/) with new links and dynamic GitHub projects with `fastapi` topic. PR [#850](https://github.com/tiangolo/fastapi/pull/850).
|
||||
* Fix Peewee `contextvars` handling in docs: [SQL (Relational) Databases with Peewee](https://fastapi.tiangolo.com/tutorial/sql-databases-peewee/). PR [#879](https://github.com/tiangolo/fastapi/pull/879).
|
||||
* Fix Peewee `contextvars` handling in docs: [SQL (Relational) Databases with Peewee](https://fastapi.tiangolo.com/advanced/sql-databases-peewee/). PR [#879](https://github.com/tiangolo/fastapi/pull/879).
|
||||
* Setup development environment with Python's Venv and Flit, instead of requiring the extra Pipenv duplicating dependencies. Updated docs: [Development - Contributing](https://fastapi.tiangolo.com/contributing/). PR [#877](https://github.com/tiangolo/fastapi/pull/877).
|
||||
* Update docs for [HTTP Basic Auth](https://fastapi.tiangolo.com/tutorial/security/http-basic-auth/) to improve security against timing attacks. Initial PR [#807](https://github.com/tiangolo/fastapi/pull/807) by [@zwass](https://github.com/zwass).
|
||||
* Update docs for [HTTP Basic Auth](https://fastapi.tiangolo.com/advanced/security/http-basic-auth/) to improve security against timing attacks. Initial PR [#807](https://github.com/tiangolo/fastapi/pull/807) by [@zwass](https://github.com/zwass).
|
||||
|
||||
## 0.46.0
|
||||
|
||||
@@ -46,12 +74,12 @@
|
||||
* Add support for subtypes of main types in `jsonable_encoder`, e.g. asyncpg's UUIDs. PR [#756](https://github.com/tiangolo/fastapi/pull/756) by [@RmStorm](https://github.com/RmStorm).
|
||||
* Fix usage of Pydantic's `HttpUrl` in docs. PR [#832](https://github.com/tiangolo/fastapi/pull/832) by [@Dustyposa](https://github.com/Dustyposa).
|
||||
* Fix Twitter links in docs. PR [#813](https://github.com/tiangolo/fastapi/pull/813) by [@justindujardin](https://github.com/justindujardin).
|
||||
* Add docs for correctly [using FastAPI with Peewee ORM](https://fastapi.tiangolo.com/tutorial/sql-databases-peewee/). Including how to overwrite parts of Peewee to correctly handle async threads. PR [#789](https://github.com/tiangolo/fastapi/pull/789).
|
||||
* Add docs for correctly [using FastAPI with Peewee ORM](https://fastapi.tiangolo.com/advanced/sql-databases-peewee/). Including how to overwrite parts of Peewee to correctly handle async threads. PR [#789](https://github.com/tiangolo/fastapi/pull/789).
|
||||
|
||||
## 0.45.0
|
||||
|
||||
* Add support for OpenAPI Callbacks:
|
||||
* New docs: [OpenAPI Callbacks](https://fastapi.tiangolo.com/tutorial/openapi-callbacks/).
|
||||
* New docs: [OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/).
|
||||
* Refactor generation of `operationId`s to be valid Python names (also valid variables in most languages).
|
||||
* Add `default_response_class` parameter to `APIRouter`.
|
||||
* Original PR [#722](https://github.com/tiangolo/fastapi/pull/722) by [@booooh](https://github.com/booooh).
|
||||
@@ -84,11 +112,11 @@
|
||||
## 0.43.0
|
||||
|
||||
* Update docs to reduce gender bias. PR [#645](https://github.com/tiangolo/fastapi/pull/645) by [@ticosax](https://github.com/ticosax).
|
||||
* Add docs about [overriding the `operationId` for all the *path operations*](https://fastapi.tiangolo.com/tutorial/path-operation-advanced-configuration/#using-the-path-operation-function-name-as-the-operationid) based on their function name. PR [#642](https://github.com/tiangolo/fastapi/pull/642) by [@SKalt](https://github.com/SKalt).
|
||||
* Add docs about [overriding the `operationId` for all the *path operations*](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/#using-the-path-operation-function-name-as-the-operationid) based on their function name. PR [#642](https://github.com/tiangolo/fastapi/pull/642) by [@SKalt](https://github.com/SKalt).
|
||||
* Fix validators in models generating an incorrect key order. PR [#637](https://github.com/tiangolo/fastapi/pull/637) by [@jaddison](https://github.com/jaddison).
|
||||
* Generate correct OpenAPI docs for responses with no content. PR [#621](https://github.com/tiangolo/fastapi/pull/621) by [@brotskydotcom](https://github.com/brotskydotcom).
|
||||
* Remove `$` from Bash code blocks in docs for consistency. PR [#613](https://github.com/tiangolo/fastapi/pull/613) by [@nstapelbroek](https://github.com/nstapelbroek).
|
||||
* Add docs for [self-serving docs' (Swagger UI) static assets](https://fastapi.tiangolo.com/tutorial/extending-openapi/#self-hosting-javascript-and-css-for-docs), e.g. to use the docs offline, or without Internet. Initial PR [#557](https://github.com/tiangolo/fastapi/pull/557) by [@svalouch](https://github.com/svalouch).
|
||||
* Add docs for [self-serving docs' (Swagger UI) static assets](https://fastapi.tiangolo.com/advanced/extending-openapi/#self-hosting-javascript-and-css-for-docs), e.g. to use the docs offline, or without Internet. Initial PR [#557](https://github.com/tiangolo/fastapi/pull/557) by [@svalouch](https://github.com/svalouch).
|
||||
* Fix `black` linting after upgrade. PR [#682](https://github.com/tiangolo/fastapi/pull/682) by [@frankie567](https://github.com/frankie567).
|
||||
|
||||
## 0.42.0
|
||||
@@ -117,7 +145,7 @@
|
||||
* Run middleware-like code only for a subset of *path operations*.
|
||||
* Process a request before passing it to a *path operation function*. E.g. decompressing, deserializing, etc.
|
||||
* Processing a response after being generated by *path operation functions* but before returning it. E.g. adding custom headers, logging, adding extra metadata.
|
||||
* New docs section: [Custom Request and APIRoute class](https://fastapi.tiangolo.com/tutorial/custom-request-and-route/).
|
||||
* New docs section: [Custom Request and APIRoute class](https://fastapi.tiangolo.com/advanced/custom-request-and-route/).
|
||||
* PR [#589](https://github.com/tiangolo/fastapi/pull/589) by [@dmontagu](https://github.com/dmontagu).
|
||||
* Fix preserving custom route class in routers when including other sub-routers. PR [#538](https://github.com/tiangolo/fastapi/pull/538) by [@dmontagu](https://github.com/dmontagu).
|
||||
|
||||
@@ -126,11 +154,11 @@
|
||||
* Add notes to docs about installing `python-multipart` when using forms. PR [#574](https://github.com/tiangolo/fastapi/pull/574) by [@sliptonic](https://github.com/sliptonic).
|
||||
* Generate OpenAPI schemas in alphabetical order. PR [#554](https://github.com/tiangolo/fastapi/pull/554) by [@dmontagu](https://github.com/dmontagu).
|
||||
* Add support for truncating docstrings from *path operation functions*.
|
||||
* New docs at [Advanced description from docstring](https://fastapi.tiangolo.com/tutorial/path-operation-advanced-configuration/#advanced-description-from-docstring).
|
||||
* New docs at [Advanced description from docstring](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/#advanced-description-from-docstring).
|
||||
* PR [#556](https://github.com/tiangolo/fastapi/pull/556) by [@svalouch](https://github.com/svalouch).
|
||||
* Fix `DOCTYPE` in HTML files generated for Swagger UI and ReDoc. PR [#537](https://github.com/tiangolo/fastapi/pull/537) by [@Trim21](https://github.com/Trim21).
|
||||
* Fix handling `4XX` responses overriding default `422` validation error responses. PR [#517](https://github.com/tiangolo/fastapi/pull/517) by [@tsouvarev](https://github.com/tsouvarev).
|
||||
* Fix typo in documentation for [Simple HTTP Basic Auth](https://fastapi.tiangolo.com/tutorial/security/http-basic-auth/#simple-http-basic-auth). PR [#514](https://github.com/tiangolo/fastapi/pull/514) by [@prostomarkeloff](https://github.com/prostomarkeloff).
|
||||
* Fix typo in documentation for [Simple HTTP Basic Auth](https://fastapi.tiangolo.com/advanced/security/http-basic-auth/#simple-http-basic-auth). PR [#514](https://github.com/tiangolo/fastapi/pull/514) by [@prostomarkeloff](https://github.com/prostomarkeloff).
|
||||
* Fix incorrect documentation example in [first steps](https://fastapi.tiangolo.com/tutorial/first-steps/). PR [#511](https://github.com/tiangolo/fastapi/pull/511) by [@IgnatovFedor](https://github.com/IgnatovFedor).
|
||||
* Add support for Swagger UI [initOauth](https://github.com/swagger-api/swagger-ui/blob/master/docs/usage/oauth2.md) settings with the parameter `swagger_ui_init_oauth`. PR [#499](https://github.com/tiangolo/fastapi/pull/499) by [@zamiramir](https://github.com/zamiramir).
|
||||
|
||||
@@ -201,7 +229,7 @@
|
||||
|
||||
* Fix source code `limit` for example in [Query Parameters](https://fastapi.tiangolo.com/tutorial/query-params/). PR [#366](https://github.com/tiangolo/fastapi/pull/366) by [@Smashman](https://github.com/Smashman).
|
||||
|
||||
* Update wording in docs about [OAuth2 scopes](https://fastapi.tiangolo.com/tutorial/security/oauth2-scopes/). PR [#371](https://github.com/tiangolo/fastapi/pull/371) by [@cjw296](https://github.com/cjw296).
|
||||
* Update wording in docs about [OAuth2 scopes](https://fastapi.tiangolo.com/advanced/security/oauth2-scopes/). PR [#371](https://github.com/tiangolo/fastapi/pull/371) by [@cjw296](https://github.com/cjw296).
|
||||
|
||||
* Update docs for `Enum`s to inherit from `str` and improve Swagger UI rendering. PR [#351](https://github.com/tiangolo/fastapi/pull/351).
|
||||
|
||||
@@ -267,7 +295,7 @@
|
||||
|
||||
* Fix handling an empty-body request with a required body param. PR [#311](https://github.com/tiangolo/fastapi/pull/311).
|
||||
|
||||
* Fix broken link in docs: [Return a Response directly](https://fastapi.tiangolo.com/tutorial/response-directly/). PR [#306](https://github.com/tiangolo/fastapi/pull/306) by [@dmontagu](https://github.com/dmontagu).
|
||||
* Fix broken link in docs: [Return a Response directly](https://fastapi.tiangolo.com/advanced/response-directly/). PR [#306](https://github.com/tiangolo/fastapi/pull/306) by [@dmontagu](https://github.com/dmontagu).
|
||||
|
||||
* Fix docs discrepancy in docs for [Response Model](https://fastapi.tiangolo.com/tutorial/response-model/). PR [#288](https://github.com/tiangolo/fastapi/pull/288) by [@awiddersheim](https://github.com/awiddersheim).
|
||||
|
||||
@@ -275,9 +303,9 @@
|
||||
|
||||
* Add support for declaring a `Response` parameter:
|
||||
* This allows declaring:
|
||||
* [Response Cookies](https://fastapi.tiangolo.com/tutorial/response-cookies/).
|
||||
* [Response Headers](https://fastapi.tiangolo.com/tutorial/response-headers/).
|
||||
* An HTTP Status Code different than the default: [Response - Change Status Code](https://fastapi.tiangolo.com/tutorial/response-change-status-code/).
|
||||
* [Response Cookies](https://fastapi.tiangolo.com/advanced/response-cookies/).
|
||||
* [Response Headers](https://fastapi.tiangolo.com/advanced/response-headers/).
|
||||
* An HTTP Status Code different than the default: [Response - Change Status Code](https://fastapi.tiangolo.com/advanced/response-change-status-code/).
|
||||
* All of this while still being able to return arbitrary objects (`dict`, DB model, etc).
|
||||
* Update attribution to Hug, for inspiring the `response` parameter pattern.
|
||||
* PR [#294](https://github.com/tiangolo/fastapi/pull/294).
|
||||
@@ -294,7 +322,7 @@
|
||||
|
||||
* Implement dependency overrides for testing.
|
||||
* This allows using overrides/mocks of dependencies during tests.
|
||||
* New docs: [Testing Dependencies with Overrides](https://fastapi.tiangolo.com/tutorial/testing-dependencies/).
|
||||
* New docs: [Testing Dependencies with Overrides](https://fastapi.tiangolo.com/advanced/testing-dependencies/).
|
||||
* PR [#291](https://github.com/tiangolo/fastapi/pull/291).
|
||||
|
||||
## 0.27.2
|
||||
@@ -366,7 +394,7 @@
|
||||
* `Path`
|
||||
* `Query`
|
||||
* ...as these are compatible with the WebSockets protocol (e.g. `Body` is not).
|
||||
* [Updated documentation for WebSockets](https://fastapi.tiangolo.com/tutorial/websockets/).
|
||||
* [Updated documentation for WebSockets](https://fastapi.tiangolo.com/advanced/websockets/).
|
||||
* PR [#178](https://github.com/tiangolo/fastapi/pull/178) by [@jekirl](https://github.com/jekirl).
|
||||
|
||||
* Upgrade the compatible version of Pydantic to `0.26.0`.
|
||||
@@ -433,16 +461,16 @@
|
||||
|
||||
* Upgrade OAuth2:
|
||||
* Upgrade Password flow using Bearer tokens to use the correct HTTP status code 401 `UNAUTHORIZED`, with `WWW-Authenticate` headers.
|
||||
* Update, simplify, and improve all the [security docs](https://fastapi.tiangolo.com/tutorial/security/intro/).
|
||||
* Add new `scope_str` to `SecurityScopes` and update docs: [OAuth2 scopes](https://fastapi.tiangolo.com/tutorial/security/oauth2-scopes/).
|
||||
* Update, simplify, and improve all the [security docs](https://fastapi.tiangolo.com/advanced/security/).
|
||||
* Add new `scope_str` to `SecurityScopes` and update docs: [OAuth2 scopes](https://fastapi.tiangolo.com/advanced/security/oauth2-scopes/).
|
||||
* Update docs, images, tests.
|
||||
* PR [#188](https://github.com/tiangolo/fastapi/pull/188).
|
||||
|
||||
* Include [Hypercorn](https://gitlab.com/pgjones/hypercorn) as an alternative ASGI server in the docs. PR [#187](https://github.com/tiangolo/fastapi/pull/187).
|
||||
|
||||
* Add docs for [Static Files](https://fastapi.tiangolo.com/tutorial/static-files/) and [Templates](https://fastapi.tiangolo.com/tutorial/templates/). PR [#186](https://github.com/tiangolo/fastapi/pull/186).
|
||||
* Add docs for [Static Files](https://fastapi.tiangolo.com/tutorial/static-files/) and [Templates](https://fastapi.tiangolo.com/advanced/templates/). PR [#186](https://github.com/tiangolo/fastapi/pull/186).
|
||||
|
||||
* Add docs for handling [Response Cookies](https://fastapi.tiangolo.com/tutorial/response-cookies/) and [Response Headers](https://fastapi.tiangolo.com/tutorial/response-headers/). PR [#185](https://github.com/tiangolo/fastapi/pull/185).
|
||||
* Add docs for handling [Response Cookies](https://fastapi.tiangolo.com/advanced/response-cookies/) and [Response Headers](https://fastapi.tiangolo.com/advanced/response-headers/). PR [#185](https://github.com/tiangolo/fastapi/pull/185).
|
||||
|
||||
* Fix typos in docs. PR [#176](https://github.com/tiangolo/fastapi/pull/176) by [@chdsbd](https://github.com/chdsbd).
|
||||
|
||||
@@ -452,13 +480,13 @@
|
||||
|
||||
* Add docs:
|
||||
* How to use the `jsonable_encoder` in [JSON compatible encoder](https://fastapi.tiangolo.com/tutorial/encoder/).
|
||||
* How to [Return a Response directly](https://fastapi.tiangolo.com/tutorial/response-directly/).
|
||||
* Update how to use a [Custom Response Class](https://fastapi.tiangolo.com/tutorial/custom-response/).
|
||||
* How to [Return a Response directly](https://fastapi.tiangolo.com/advanced/response-directly/).
|
||||
* Update how to use a [Custom Response Class](https://fastapi.tiangolo.com/advanced/custom-response/).
|
||||
* PR [#184](https://github.com/tiangolo/fastapi/pull/184).
|
||||
|
||||
## 0.18.0
|
||||
|
||||
* Add docs for [HTTP Basic Auth](https://fastapi.tiangolo.com/tutorial/custom-response/). PR [#177](https://github.com/tiangolo/fastapi/pull/177).
|
||||
* Add docs for [HTTP Basic Auth](https://fastapi.tiangolo.com/advanced/custom-response/). PR [#177](https://github.com/tiangolo/fastapi/pull/177).
|
||||
|
||||
* Upgrade HTTP Basic Auth handling with automatic headers (automatic browser login prompt). PR [#175](https://github.com/tiangolo/fastapi/pull/175).
|
||||
|
||||
@@ -476,7 +504,7 @@
|
||||
|
||||
## 0.16.0
|
||||
|
||||
* Upgrade *path operation* `doctsring` parsing to support proper Markdown descriptions. New documentation at [Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#description-from-docstring). PR [#163](https://github.com/tiangolo/fastapi/pull/163).
|
||||
* Upgrade *path operation* `docstring` parsing to support proper Markdown descriptions. New documentation at [Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#description-from-docstring). PR [#163](https://github.com/tiangolo/fastapi/pull/163).
|
||||
|
||||
* Refactor internal usage of Pydantic to use correct data types. PR [#164](https://github.com/tiangolo/fastapi/pull/164).
|
||||
|
||||
@@ -490,7 +518,7 @@
|
||||
|
||||
* Add support for multiple file uploads (as a single form field). New docs at: [Multiple file uploads](https://fastapi.tiangolo.com/tutorial/request-files/#multiple-file-uploads). PR [#158](https://github.com/tiangolo/fastapi/pull/158).
|
||||
|
||||
* Add docs for: [Additional Status Codes](https://fastapi.tiangolo.com/tutorial/additional-status-codes/). PR [#156](https://github.com/tiangolo/fastapi/pull/156).
|
||||
* Add docs for: [Additional Status Codes](https://fastapi.tiangolo.com/advanced/additional-status-codes/). PR [#156](https://github.com/tiangolo/fastapi/pull/156).
|
||||
|
||||
## 0.14.0
|
||||
|
||||
@@ -511,7 +539,7 @@
|
||||
* Improve `Security` handling, merging scopes when declaring `SecurityScopes`.
|
||||
* Allow using `SecurityBase` (like `OAuth2`) classes with `Depends` and still document them. `Security` now is needed only to declare `scopes`.
|
||||
* Updated docs about: [OAuth2 with Password (and hashing), Bearer with JWT tokens](https://fastapi.tiangolo.com/tutorial/security/oauth2-jwt/).
|
||||
* New docs about: [OAuth2 scopes](https://fastapi.tiangolo.com/tutorial/security/oauth2-scopes/).
|
||||
* New docs about: [OAuth2 scopes](https://fastapi.tiangolo.com/advanced/security/oauth2-scopes/).
|
||||
* PR [#141](https://github.com/tiangolo/fastapi/pull/141).
|
||||
|
||||
## 0.12.1
|
||||
@@ -526,7 +554,7 @@
|
||||
|
||||
* Add additional `responses` parameter to *path operation decorators* to extend responses in OpenAPI (and API docs).
|
||||
* It also allows extending existing responses generated from `response_model`, declare other media types (like images), etc.
|
||||
* The new documentation is here: [Additional Responses](https://fastapi.tiangolo.com/tutorial/additional-responses/).
|
||||
* The new documentation is here: [Additional Responses](https://fastapi.tiangolo.com/advanced/additional-responses/).
|
||||
* `responses` can also be added to `.include_router()`, the updated docs are here: [Bigger Applications](https://fastapi.tiangolo.com/tutorial/bigger-applications/#add-some-custom-tags-and-responses).
|
||||
* PR [#97](https://github.com/tiangolo/fastapi/pull/97) originally initiated by [@barsi](https://github.com/barsi).
|
||||
* Update `scripts/test-cov-html.sh` to allow passing extra parameters like `-vv`, for development.
|
||||
@@ -543,7 +571,7 @@
|
||||
|
||||
* Add Gitter chat, badge, links, etc. [https://gitter.im/tiangolo/fastapi](https://gitter.im/tiangolo/fastapi) . PR [#117](https://github.com/tiangolo/fastapi/pull/117).
|
||||
|
||||
* Add docs about [Extending OpenAPI](https://fastapi.tiangolo.com/tutorial/extending-openapi/). PR [#126](https://github.com/tiangolo/fastapi/pull/126).
|
||||
* Add docs about [Extending OpenAPI](https://fastapi.tiangolo.com/advanced/extending-openapi/). PR [#126](https://github.com/tiangolo/fastapi/pull/126).
|
||||
|
||||
* Make Travis run Ubuntu Xenial (newer version) and Python 3.7 instead of Python 3.7-dev. PR [#92](https://github.com/tiangolo/fastapi/pull/92) by [@blueyed](https://github.com/blueyed).
|
||||
|
||||
@@ -563,7 +591,7 @@
|
||||
|
||||
## 0.10.1
|
||||
|
||||
* Add docs and tests for [encode/databases](https://github.com/encode/databases). New docs at: [Async SQL (Relational) Databases](https://fastapi.tiangolo.com/tutorial/async-sql-databases/). PR [#107](https://github.com/tiangolo/fastapi/pull/107).
|
||||
* Add docs and tests for [encode/databases](https://github.com/encode/databases). New docs at: [Async SQL (Relational) Databases](https://fastapi.tiangolo.com/advanced/async-sql-databases/). PR [#107](https://github.com/tiangolo/fastapi/pull/107).
|
||||
|
||||
## 0.10.0
|
||||
|
||||
@@ -571,7 +599,7 @@
|
||||
|
||||
* Add support for `.websocket_route()` in `APIRouter`. PR [#100](https://github.com/tiangolo/fastapi/pull/100) by [@euri10](https://github.com/euri10).
|
||||
|
||||
* New docs section about [Events: startup - shutdown](https://fastapi.tiangolo.com/tutorial/events/). PR [#99](https://github.com/tiangolo/fastapi/pull/99).
|
||||
* New docs section about [Events: startup - shutdown](https://fastapi.tiangolo.com/advanced/events/). PR [#99](https://github.com/tiangolo/fastapi/pull/99).
|
||||
|
||||
## 0.9.1
|
||||
|
||||
@@ -625,7 +653,7 @@
|
||||
|
||||
* Add section about [History, Design and Future](https://fastapi.tiangolo.com/history-design-future/).
|
||||
|
||||
* Add docs for using [WebSockets with **FastAPI**](https://fastapi.tiangolo.com/tutorial/websockets/). PR [#62](https://github.com/tiangolo/fastapi/pull/62).
|
||||
* Add docs for using [WebSockets with **FastAPI**](https://fastapi.tiangolo.com/advanced/websockets/). PR [#62](https://github.com/tiangolo/fastapi/pull/62).
|
||||
|
||||
## 0.6.3
|
||||
|
||||
@@ -641,7 +669,7 @@
|
||||
|
||||
## 0.6.1
|
||||
|
||||
* Add docs for GraphQL: [https://fastapi.tiangolo.com/tutorial/graphql/](https://fastapi.tiangolo.com/tutorial/graphql/). PR [#48](https://github.com/tiangolo/fastapi/pull/48).
|
||||
* Add docs for GraphQL: [https://fastapi.tiangolo.com/advanced/graphql/](https://fastapi.tiangolo.com/advanced/graphql/). PR [#48](https://github.com/tiangolo/fastapi/pull/48).
|
||||
|
||||
## 0.6.0
|
||||
|
||||
@@ -663,7 +691,7 @@
|
||||
|
||||
* Add new `HTTPException` with support for custom headers. With new documentation for handling errors at: [https://fastapi.tiangolo.com/tutorial/handling-errors/](https://fastapi.tiangolo.com/tutorial/handling-errors/). PR [#35](https://github.com/tiangolo/fastapi/pull/35).
|
||||
|
||||
* Add [documentation to use Starlette `Request` object](https://fastapi.tiangolo.com/tutorial/using-request-directly/) directly. Check [#25](https://github.com/tiangolo/fastapi/pull/25) by [@euri10](https://github.com/euri10).
|
||||
* Add [documentation to use Starlette `Request` object](https://fastapi.tiangolo.com/advanced/using-request-directly/) directly. Check [#25](https://github.com/tiangolo/fastapi/pull/25) by [@euri10](https://github.com/euri10).
|
||||
|
||||
* Add issue templates to simplify reporting bugs, getting help, etc: [#34](https://github.com/tiangolo/fastapi/pull/34).
|
||||
|
||||
@@ -671,7 +699,7 @@
|
||||
|
||||
## 0.4.0
|
||||
|
||||
* Add `openapi_prefix`, support for reverse proxy and mounting sub-applications. See the docs at [https://fastapi.tiangolo.com/tutorial/sub-applications-proxy/](https://fastapi.tiangolo.com/tutorial/sub-applications-proxy/): [#26](https://github.com/tiangolo/fastapi/pull/26) by [@kabirkhan](https://github.com/kabirkhan).
|
||||
* Add `openapi_prefix`, support for reverse proxy and mounting sub-applications. See the docs at [https://fastapi.tiangolo.com/advanced/sub-applications-proxy/](https://fastapi.tiangolo.com/advanced/sub-applications-proxy/): [#26](https://github.com/tiangolo/fastapi/pull/26) by [@kabirkhan](https://github.com/kabirkhan).
|
||||
|
||||
* Update [docs/tutorial for SQLAlchemy](https://fastapi.tiangolo.com/tutorial/sql-databases/) including note about _DB Browser for SQLite_.
|
||||
|
||||
|
||||
@@ -102,6 +102,79 @@ You can have any combinations of dependencies that you want.
|
||||
|
||||
**FastAPI** uses them internally to achieve this.
|
||||
|
||||
## Dependencies with `yield` and `HTTPException`
|
||||
|
||||
You saw that you can use dependencies with `yield` and have `try` blocks that catch exceptions.
|
||||
|
||||
It might be tempting to raise an `HTTPException` or similar in the exit code, after the `yield`. But **it won't work**.
|
||||
|
||||
The exit code in dependencies with `yield` is executed *after* [Exception Handlers](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}. There's nothing catching exceptions thrown by your dependencies in the exit code (after the `yield`).
|
||||
|
||||
So, if you raise an `HTTPException` after the `yield`, the default (or any custom) exception handler that catches `HTTPException`s and returns an HTTP 400 response won't be there to catch that exception anymore.
|
||||
|
||||
This is what allows anything set in the dependency (e.g. a DB session) to, for example, be used by background tasks.
|
||||
|
||||
Background tasks are run *after* the response has been sent. So there's no way to raise an `HTTPException` because there's not even a way to change the response that is *already sent*.
|
||||
|
||||
But if a background task creates a DB error, at least you can rollback or cleanly close the session in the dependency with `yield`, and maybe log the error or report it to a remote tracking system.
|
||||
|
||||
If you have some code that you know could raise an exception, do the most normal/"Pythonic" thing and add a `try` block in that section of the code.
|
||||
|
||||
If you have custom exceptions that you would like to handle *before* returning the response and possibly modifying the response, maybe even raising an `HTTPException`, create a [Custom Exception Handler](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}.
|
||||
|
||||
!!! tip
|
||||
You can still raise exceptions including `HTTPException` *before* the `yield`. But not after.
|
||||
|
||||
The sequence of execution is more or less like this diagram. Time flows from top to bottom. And each column is one of the parts interacting or executing code.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
|
||||
participant client as Client
|
||||
participant handler as Exception handler
|
||||
participant dep as Dep with yield
|
||||
participant operation as Path Operation
|
||||
participant tasks as Background tasks
|
||||
|
||||
Note over client,tasks: Can raise exception for dependency, handled after response is sent
|
||||
Note over client,operation: Can raise HTTPException and can change the response
|
||||
client ->> dep: Start request
|
||||
Note over dep: Run code up to yield
|
||||
opt raise
|
||||
dep -->> handler: Raise HTTPException
|
||||
handler -->> client: HTTP error response
|
||||
dep -->> dep: Raise other exception
|
||||
end
|
||||
dep ->> operation: Run dependency, e.g. DB session
|
||||
opt raise
|
||||
operation -->> handler: Raise HTTPException
|
||||
handler -->> client: HTTP error response
|
||||
operation -->> dep: Raise other exception
|
||||
end
|
||||
operation ->> client: Return response to client
|
||||
Note over client,operation: Response is already sent, can't change it anymore
|
||||
opt Tasks
|
||||
operation -->> tasks: Send background tasks
|
||||
end
|
||||
opt Raise other exception
|
||||
tasks -->> dep: Raise other exception
|
||||
end
|
||||
Note over dep: After yield
|
||||
opt Handle other exception
|
||||
dep -->> dep: Handle exception, can't change response. E.g. close DB session.
|
||||
end
|
||||
```
|
||||
|
||||
!!! info
|
||||
Only **one response** will be sent to the client. It might be one of the error responses or it will be the response from the *path operation*.
|
||||
|
||||
After one of those responses is sent, no other response can be sent.
|
||||
|
||||
!!! tip
|
||||
This diagram shows `HTTPException`, but you could also raise any other exception for which you create a [Custom Exception Handler](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}. And that exception would be handled by that custom exception handler instead of the dependency exit code.
|
||||
|
||||
But if you raise an exception that is not handled by the exception handlers, it will be handled by the exit code of the dependency.
|
||||
|
||||
## Context Managers
|
||||
|
||||
### What are "Context Managers"
|
||||
|
||||
@@ -82,6 +82,19 @@ Whenever a new request arrives, **FastAPI** will take care of:
|
||||
* Get the result from your function.
|
||||
* Assign that result to the parameter in your *path operation function*.
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
|
||||
common_parameters(["common_parameters"])
|
||||
read_items["/items/"]
|
||||
read_users["/users/"]
|
||||
|
||||
common_parameters --> read_items
|
||||
common_parameters --> read_users
|
||||
```
|
||||
|
||||
This way you write shared code once and **FastAPI** takes care of calling it for your *path operations*.
|
||||
|
||||
!!! check
|
||||
Notice that you don't have to create a special class and pass it somewhere to **FastAPI** to "register" it or anything similar.
|
||||
|
||||
@@ -154,7 +167,39 @@ Although the hierarchical dependency injection system is very simple to define a
|
||||
|
||||
You can define dependencies that in turn can define dependencies themselves.
|
||||
|
||||
In the end, a hierarchical tree of dependencies is built, and the **Dependency Injection** system takes care of solving all these dependencies for you (and your dependencies) and providing (injecting) the results at each step.
|
||||
In the end, a hierarchical tree of dependencies is built, and the **Dependency Injection** system takes care of solving all these dependencies for you (and their sub-dependencies) and providing (injecting) the results at each step.
|
||||
|
||||
For example, let's say you have 4 API endpoints (*path operations*):
|
||||
|
||||
* `/items/public/`
|
||||
* `/items/private/`
|
||||
* `/users/{user_id}/activate`
|
||||
* `/items/pro/`
|
||||
|
||||
then you could add different permission requirements for each of them just with dependencies and sub-dependencies:
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
|
||||
current_user(["current_user"])
|
||||
active_user(["active_user"])
|
||||
admin_user(["admin_user"])
|
||||
paying_user(["paying_user"])
|
||||
|
||||
public["/items/public/"]
|
||||
private["/items/private/"]
|
||||
activate_user["/users/{user_id}/activate"]
|
||||
pro_items["/items/pro/"]
|
||||
|
||||
current_user --> active_user
|
||||
active_user --> admin_user
|
||||
active_user --> paying_user
|
||||
|
||||
current_user --> public
|
||||
active_user --> private
|
||||
admin_user --> activate_user
|
||||
paying_user --> pro_items
|
||||
```
|
||||
|
||||
## Integrated with **OpenAPI**
|
||||
|
||||
|
||||
@@ -44,6 +44,17 @@ Then we can use the dependency with:
|
||||
|
||||
But **FastAPI** will know that it has to solve `query_extractor` first, to pass the results of that to `query_or_cookie_extractor` while calling it.
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
|
||||
query_extractor(["query_extractor"])
|
||||
query_or_cookie_extractor(["query_or_cookie_extractor"])
|
||||
|
||||
read_query["/items/"]
|
||||
|
||||
query_extractor --> query_or_cookie_extractor --> read_query
|
||||
```
|
||||
|
||||
## Using the same dependency multiple times
|
||||
|
||||
If one of your dependencies is declared multiple times for the same *path operation*, for example, multiple dependencies have a common sub-dependency, **FastAPI** will know to call that sub-dependency only once per request.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
There are many situations in where you need to notify an error to the client that is using your API.
|
||||
There are many situations in where you need to notify an error to a client that is using your API.
|
||||
|
||||
This client could be a browser with a frontend, the code from someone else, an IoT device, etc.
|
||||
This client could be a browser with a frontend, a code from someone else, an IoT device, etc.
|
||||
|
||||
You could need to tell that client that:
|
||||
You could need to tell the client that:
|
||||
|
||||
* The client doesn't have enough privileges for that operation.
|
||||
* The client doesn't have access to that resource.
|
||||
|
||||
@@ -469,6 +469,8 @@ Our dependency will create a new SQLAlchemy `SessionLocal` that will be used in
|
||||
|
||||
This way we make sure the database session is always closed after the request. Even if there was an exception while processing the request.
|
||||
|
||||
But you can't raise another exception from the exit code (after `yield`). See more in [Dependencies with `yield` and `HTTPException`](./dependencies/dependencies-with-yield.md#dependencies-with-yield-and-httpexception){.internal-link target=_blank}
|
||||
|
||||
And then, when using the dependency in a *path operation function*, we declare it with the type `Session` we imported directly from SQLAlchemy.
|
||||
|
||||
This will then give us better editor support inside the *path operation function*, because the editor will know that the `db` parameter is of type `Session`:
|
||||
@@ -556,31 +558,31 @@ For example, in a background task worker with <a href="http://www.celeryproject.
|
||||
|
||||
* `sql_app/database.py`:
|
||||
|
||||
```Python hl_lines=""
|
||||
```Python
|
||||
{!./src/sql_databases/sql_app/database.py!}
|
||||
```
|
||||
|
||||
* `sql_app/models.py`:
|
||||
|
||||
```Python hl_lines=""
|
||||
```Python
|
||||
{!./src/sql_databases/sql_app/models.py!}
|
||||
```
|
||||
|
||||
* `sql_app/schemas.py`:
|
||||
|
||||
```Python hl_lines=""
|
||||
```Python
|
||||
{!./src/sql_databases/sql_app/schemas.py!}
|
||||
```
|
||||
|
||||
* `sql_app/crud.py`:
|
||||
|
||||
```Python hl_lines=""
|
||||
```Python
|
||||
{!./src/sql_databases/sql_app/crud.py!}
|
||||
```
|
||||
|
||||
* `sql_app/main.py`:
|
||||
|
||||
```Python hl_lines=""
|
||||
```Python
|
||||
{!./src/sql_databases/sql_app/main.py!}
|
||||
```
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
|
||||
|
||||
__version__ = "0.48.0"
|
||||
__version__ = "0.49.2"
|
||||
|
||||
from starlette.background import BackgroundTasks
|
||||
|
||||
|
||||
@@ -27,7 +27,12 @@ from fastapi.dependencies.models import Dependant, SecurityRequirement
|
||||
from fastapi.security.base import SecurityBase
|
||||
from fastapi.security.oauth2 import OAuth2, SecurityScopes
|
||||
from fastapi.security.open_id_connect_url import OpenIdConnect
|
||||
from fastapi.utils import PYDANTIC_1, get_field_info, get_path_param_names
|
||||
from fastapi.utils import (
|
||||
PYDANTIC_1,
|
||||
create_response_field,
|
||||
get_field_info,
|
||||
get_path_param_names,
|
||||
)
|
||||
from pydantic import BaseConfig, BaseModel, create_model
|
||||
from pydantic.error_wrappers import ErrorWrapper
|
||||
from pydantic.errors import MissingError
|
||||
@@ -362,31 +367,15 @@ def get_param_field(
|
||||
alias = param.name.replace("_", "-")
|
||||
else:
|
||||
alias = field_info.alias or param.name
|
||||
if PYDANTIC_1:
|
||||
field = ModelField(
|
||||
name=param.name,
|
||||
type_=annotation,
|
||||
default=None if required else default_value,
|
||||
alias=alias,
|
||||
required=required,
|
||||
model_config=BaseConfig,
|
||||
class_validators={},
|
||||
field_info=field_info,
|
||||
)
|
||||
# TODO: remove when removing support for Pydantic < 1.2.0
|
||||
field.required = required
|
||||
else: # pragma: nocover
|
||||
field = ModelField( # type: ignore
|
||||
name=param.name,
|
||||
type_=annotation,
|
||||
default=None if required else default_value,
|
||||
alias=alias,
|
||||
required=required,
|
||||
model_config=BaseConfig,
|
||||
class_validators={},
|
||||
schema=field_info,
|
||||
)
|
||||
field.required = required
|
||||
field = create_response_field(
|
||||
name=param.name,
|
||||
type_=annotation,
|
||||
default=None if required else default_value,
|
||||
alias=alias,
|
||||
required=required,
|
||||
field_info=field_info,
|
||||
)
|
||||
field.required = required
|
||||
if not had_schema and not is_scalar_field(field=field):
|
||||
if PYDANTIC_1:
|
||||
field.field_info = params.Body(field_info.default)
|
||||
@@ -694,28 +683,16 @@ def get_schema_compatible_field(*, field: ModelField) -> ModelField:
|
||||
use_type: type = bytes
|
||||
if field.shape in sequence_shapes:
|
||||
use_type = List[bytes]
|
||||
if PYDANTIC_1:
|
||||
out_field = ModelField(
|
||||
name=field.name,
|
||||
type_=use_type,
|
||||
class_validators=field.class_validators,
|
||||
model_config=field.model_config,
|
||||
default=field.default,
|
||||
required=field.required,
|
||||
alias=field.alias,
|
||||
field_info=field.field_info,
|
||||
)
|
||||
else: # pragma: nocover
|
||||
out_field = ModelField( # type: ignore
|
||||
name=field.name,
|
||||
type_=use_type,
|
||||
class_validators=field.class_validators,
|
||||
model_config=field.model_config,
|
||||
default=field.default,
|
||||
required=field.required,
|
||||
alias=field.alias,
|
||||
schema=field.schema, # type: ignore
|
||||
)
|
||||
out_field = create_response_field(
|
||||
name=field.name,
|
||||
type_=use_type,
|
||||
class_validators=field.class_validators,
|
||||
model_config=field.model_config,
|
||||
default=field.default,
|
||||
required=field.required,
|
||||
alias=field.alias,
|
||||
field_info=field.field_info if PYDANTIC_1 else field.schema, # type: ignore
|
||||
)
|
||||
|
||||
return out_field
|
||||
|
||||
@@ -754,26 +731,10 @@ def get_body_field(*, dependant: Dependant, name: str) -> Optional[ModelField]:
|
||||
]
|
||||
if len(set(body_param_media_types)) == 1:
|
||||
BodyFieldInfo_kwargs["media_type"] = body_param_media_types[0]
|
||||
if PYDANTIC_1:
|
||||
field = ModelField(
|
||||
name="body",
|
||||
type_=BodyModel,
|
||||
default=None,
|
||||
required=required,
|
||||
model_config=BaseConfig,
|
||||
class_validators={},
|
||||
alias="body",
|
||||
field_info=BodyFieldInfo(**BodyFieldInfo_kwargs),
|
||||
)
|
||||
else: # pragma: nocover
|
||||
field = ModelField( # type: ignore
|
||||
name="body",
|
||||
type_=BodyModel,
|
||||
default=None,
|
||||
required=required,
|
||||
model_config=BaseConfig,
|
||||
class_validators={},
|
||||
alias="body",
|
||||
schema=BodyFieldInfo(**BodyFieldInfo_kwargs),
|
||||
)
|
||||
return field
|
||||
return create_response_field(
|
||||
name="body",
|
||||
type_=BodyModel,
|
||||
required=required,
|
||||
alias="body",
|
||||
field_info=BodyFieldInfo(**BodyFieldInfo_kwargs),
|
||||
)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from enum import Enum
|
||||
from pathlib import PurePath
|
||||
from types import GeneratorType
|
||||
from typing import Any, Callable, Dict, List, Set, Tuple, Union
|
||||
|
||||
@@ -73,6 +74,8 @@ def jsonable_encoder(
|
||||
)
|
||||
if isinstance(obj, Enum):
|
||||
return obj.value
|
||||
if isinstance(obj, PurePath):
|
||||
return str(obj)
|
||||
if isinstance(obj, (str, int, float, type(None))):
|
||||
return obj
|
||||
if isinstance(obj, dict):
|
||||
|
||||
@@ -20,6 +20,12 @@ RequestErrorModel = create_model("Request")
|
||||
WebSocketErrorModel = create_model("WebSocket")
|
||||
|
||||
|
||||
class FastAPIError(RuntimeError):
|
||||
"""
|
||||
A generic, FastAPI-specific error.
|
||||
"""
|
||||
|
||||
|
||||
class RequestValidationError(ValidationError):
|
||||
def __init__(self, errors: Sequence[ErrorList], *, body: Any = None) -> None:
|
||||
self.body = body
|
||||
|
||||
@@ -180,7 +180,9 @@ def get_openapi_path(
|
||||
operation_parameters = get_openapi_operation_parameters(all_route_params)
|
||||
parameters.extend(operation_parameters)
|
||||
if parameters:
|
||||
operation["parameters"] = parameters
|
||||
operation["parameters"] = list(
|
||||
{param["name"]: param for param in parameters}.values()
|
||||
)
|
||||
if method in METHODS_WITH_BODY:
|
||||
request_body_oai = get_openapi_operation_request_body(
|
||||
body_field=route.body_field, model_name_map=model_name_map
|
||||
|
||||
@@ -17,13 +17,13 @@ from fastapi.openapi.constants import STATUS_CODES_WITH_NO_BODY
|
||||
from fastapi.utils import (
|
||||
PYDANTIC_1,
|
||||
create_cloned_field,
|
||||
create_response_field,
|
||||
generate_operation_id_for_path,
|
||||
get_field_info,
|
||||
warning_response_model_skip_defaults_deprecated,
|
||||
)
|
||||
from pydantic import BaseConfig, BaseModel
|
||||
from pydantic import BaseModel
|
||||
from pydantic.error_wrappers import ErrorWrapper, ValidationError
|
||||
from pydantic.utils import lenient_issubclass
|
||||
from starlette import routing
|
||||
from starlette.concurrency import run_in_threadpool
|
||||
from starlette.exceptions import HTTPException
|
||||
@@ -50,7 +50,7 @@ except ImportError: # pragma: nocover
|
||||
async def serialize_response(
|
||||
*,
|
||||
field: ModelField = None,
|
||||
response: Response,
|
||||
response_content: Any,
|
||||
include: Union[SetIntStr, DictIntStrAny] = None,
|
||||
exclude: Union[SetIntStr, DictIntStrAny] = set(),
|
||||
by_alias: bool = True,
|
||||
@@ -59,16 +59,18 @@ async def serialize_response(
|
||||
) -> Any:
|
||||
if field:
|
||||
errors = []
|
||||
if exclude_unset and isinstance(response, BaseModel):
|
||||
if exclude_unset and isinstance(response_content, BaseModel):
|
||||
if PYDANTIC_1:
|
||||
response = response.dict(exclude_unset=exclude_unset)
|
||||
response_content = response_content.dict(exclude_unset=exclude_unset)
|
||||
else:
|
||||
response = response.dict(skip_defaults=exclude_unset) # pragma: nocover
|
||||
response_content = response_content.dict(
|
||||
skip_defaults=exclude_unset
|
||||
) # pragma: nocover
|
||||
if is_coroutine:
|
||||
value, errors_ = field.validate(response, {}, loc=("response",))
|
||||
value, errors_ = field.validate(response_content, {}, loc=("response",))
|
||||
else:
|
||||
value, errors_ = await run_in_threadpool(
|
||||
field.validate, response, {}, loc=("response",)
|
||||
field.validate, response_content, {}, loc=("response",)
|
||||
)
|
||||
if isinstance(errors_, ErrorWrapper):
|
||||
errors.append(errors_)
|
||||
@@ -84,7 +86,20 @@ async def serialize_response(
|
||||
exclude_unset=exclude_unset,
|
||||
)
|
||||
else:
|
||||
return jsonable_encoder(response)
|
||||
return jsonable_encoder(response_content)
|
||||
|
||||
|
||||
async def run_endpoint_function(
|
||||
*, dependant: Dependant, values: Dict[str, Any], is_coroutine: bool
|
||||
) -> Any:
|
||||
# Only called by get_request_handler. Has been split into its own function to
|
||||
# facilitate profiling endpoints, since inner functions are harder to profile.
|
||||
assert dependant.call is not None, "dependant.call must be a function"
|
||||
|
||||
if is_coroutine:
|
||||
return await dependant.call(**values)
|
||||
else:
|
||||
return await run_in_threadpool(dependant.call, **values)
|
||||
|
||||
|
||||
def get_request_handler(
|
||||
@@ -128,18 +143,17 @@ def get_request_handler(
|
||||
if errors:
|
||||
raise RequestValidationError(errors, body=body)
|
||||
else:
|
||||
assert dependant.call is not None, "dependant.call must be a function"
|
||||
if is_coroutine:
|
||||
raw_response = await dependant.call(**values)
|
||||
else:
|
||||
raw_response = await run_in_threadpool(dependant.call, **values)
|
||||
raw_response = await run_endpoint_function(
|
||||
dependant=dependant, values=values, is_coroutine=is_coroutine
|
||||
)
|
||||
|
||||
if isinstance(raw_response, Response):
|
||||
if raw_response.background is None:
|
||||
raw_response.background = background_tasks
|
||||
return raw_response
|
||||
response_data = await serialize_response(
|
||||
field=response_field,
|
||||
response=raw_response,
|
||||
response_content=raw_response,
|
||||
include=response_model_include,
|
||||
exclude=response_model_exclude,
|
||||
by_alias=response_model_by_alias,
|
||||
@@ -243,26 +257,9 @@ class APIRoute(routing.Route):
|
||||
status_code not in STATUS_CODES_WITH_NO_BODY
|
||||
), f"Status code {status_code} must not have a response body"
|
||||
response_name = "Response_" + self.unique_id
|
||||
if PYDANTIC_1:
|
||||
self.response_field: Optional[ModelField] = ModelField(
|
||||
name=response_name,
|
||||
type_=self.response_model,
|
||||
class_validators={},
|
||||
default=None,
|
||||
required=False,
|
||||
model_config=BaseConfig,
|
||||
field_info=FieldInfo(None),
|
||||
)
|
||||
else:
|
||||
self.response_field: Optional[ModelField] = ModelField( # type: ignore # pragma: nocover
|
||||
name=response_name,
|
||||
type_=self.response_model,
|
||||
class_validators={},
|
||||
default=None,
|
||||
required=False,
|
||||
model_config=BaseConfig,
|
||||
schema=FieldInfo(None),
|
||||
)
|
||||
self.response_field = create_response_field(
|
||||
name=response_name, type_=self.response_model
|
||||
)
|
||||
# Create a clone of the field, so that a Pydantic submodel is not returned
|
||||
# as is just because it's an instance of a subclass of a more limited class
|
||||
# e.g. UserInDB (containing hashed_password) could be a subclass of User
|
||||
@@ -274,7 +271,7 @@ class APIRoute(routing.Route):
|
||||
ModelField
|
||||
] = create_cloned_field(self.response_field)
|
||||
else:
|
||||
self.response_field = None
|
||||
self.response_field = None # type: ignore
|
||||
self.secure_cloned_response_field = None
|
||||
self.status_code = status_code
|
||||
self.tags = tags or []
|
||||
@@ -297,30 +294,8 @@ class APIRoute(routing.Route):
|
||||
assert (
|
||||
additional_status_code not in STATUS_CODES_WITH_NO_BODY
|
||||
), f"Status code {additional_status_code} must not have a response body"
|
||||
assert lenient_issubclass(
|
||||
model, BaseModel
|
||||
), "A response model must be a Pydantic model"
|
||||
response_name = f"Response_{additional_status_code}_{self.unique_id}"
|
||||
if PYDANTIC_1:
|
||||
response_field = ModelField(
|
||||
name=response_name,
|
||||
type_=model,
|
||||
class_validators=None,
|
||||
default=None,
|
||||
required=False,
|
||||
model_config=BaseConfig,
|
||||
field_info=FieldInfo(None),
|
||||
)
|
||||
else:
|
||||
response_field = ModelField( # type: ignore # pragma: nocover
|
||||
name=response_name,
|
||||
type_=model,
|
||||
class_validators=None,
|
||||
default=None,
|
||||
required=False,
|
||||
model_config=BaseConfig,
|
||||
schema=FieldInfo(None),
|
||||
)
|
||||
response_field = create_response_field(name=response_name, type_=model)
|
||||
response_fields[additional_status_code] = response_field
|
||||
if response_fields:
|
||||
self.response_fields: Dict[Union[int, str], ModelField] = response_fields
|
||||
@@ -335,9 +310,7 @@ class APIRoute(routing.Route):
|
||||
self.include_in_schema = include_in_schema
|
||||
self.response_class = response_class
|
||||
|
||||
assert inspect.isfunction(endpoint) or inspect.ismethod(
|
||||
endpoint
|
||||
), f"An endpoint must be a function or method"
|
||||
assert callable(endpoint), f"An endpoint must be a callable"
|
||||
self.dependant = get_dependant(path=self.path_format, call=self.endpoint)
|
||||
for depends in self.dependencies[::-1]:
|
||||
self.dependant.dependencies.insert(
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
import functools
|
||||
import re
|
||||
from dataclasses import is_dataclass
|
||||
from typing import Any, Dict, List, Sequence, Set, Type, cast
|
||||
from typing import Any, Dict, List, Optional, Sequence, Set, Type, Union, cast
|
||||
|
||||
import fastapi
|
||||
from fastapi import routing
|
||||
from fastapi.logger import logger
|
||||
from fastapi.openapi.constants import REF_PREFIX
|
||||
from pydantic import BaseConfig, BaseModel, create_model
|
||||
from pydantic.class_validators import Validator
|
||||
from pydantic.schema import get_flat_models_from_fields, model_process_schema
|
||||
from pydantic.utils import lenient_issubclass
|
||||
from starlette.routing import BaseRoute
|
||||
|
||||
try:
|
||||
from pydantic.fields import FieldInfo, ModelField
|
||||
from pydantic.fields import FieldInfo, ModelField, UndefinedType
|
||||
|
||||
PYDANTIC_1 = True
|
||||
except ImportError: # pragma: nocover
|
||||
@@ -19,6 +22,10 @@ except ImportError: # pragma: nocover
|
||||
from pydantic.fields import Field as ModelField # type: ignore
|
||||
from pydantic import Schema as FieldInfo # type: ignore
|
||||
|
||||
class UndefinedType: # type: ignore
|
||||
def __repr__(self) -> str:
|
||||
return "PydanticUndefined"
|
||||
|
||||
logger.warning(
|
||||
"Pydantic versions < 1.0.0 are deprecated in FastAPI and support will be "
|
||||
"removed soon."
|
||||
@@ -86,6 +93,44 @@ def get_path_param_names(path: str) -> Set[str]:
|
||||
return {item.strip("{}") for item in re.findall("{[^}]*}", path)}
|
||||
|
||||
|
||||
def create_response_field(
|
||||
name: str,
|
||||
type_: Type[Any],
|
||||
class_validators: Optional[Dict[str, Validator]] = None,
|
||||
default: Optional[Any] = None,
|
||||
required: Union[bool, UndefinedType] = False,
|
||||
model_config: Type[BaseConfig] = BaseConfig,
|
||||
field_info: Optional[FieldInfo] = None,
|
||||
alias: Optional[str] = None,
|
||||
) -> ModelField:
|
||||
"""
|
||||
Create a new response field. Raises if type_ is invalid.
|
||||
"""
|
||||
class_validators = class_validators or {}
|
||||
field_info = field_info or FieldInfo(None)
|
||||
|
||||
response_field = functools.partial(
|
||||
ModelField,
|
||||
name=name,
|
||||
type_=type_,
|
||||
class_validators=class_validators,
|
||||
default=default,
|
||||
required=required,
|
||||
model_config=model_config,
|
||||
alias=alias,
|
||||
)
|
||||
|
||||
try:
|
||||
if PYDANTIC_1:
|
||||
return response_field(field_info=field_info)
|
||||
else: # pragma: nocover
|
||||
return response_field(schema=field_info)
|
||||
except RuntimeError:
|
||||
raise fastapi.exceptions.FastAPIError(
|
||||
f"Invalid args for response field! Hint: check that {type_} is a valid pydantic field type"
|
||||
)
|
||||
|
||||
|
||||
def create_cloned_field(field: ModelField) -> ModelField:
|
||||
original_type = field.type_
|
||||
if is_dataclass(original_type) and hasattr(original_type, "__pydantic_model__"):
|
||||
@@ -96,26 +141,8 @@ def create_cloned_field(field: ModelField) -> ModelField:
|
||||
use_type = create_model(original_type.__name__, __base__=original_type)
|
||||
for f in original_type.__fields__.values():
|
||||
use_type.__fields__[f.name] = create_cloned_field(f)
|
||||
if PYDANTIC_1:
|
||||
new_field = ModelField(
|
||||
name=field.name,
|
||||
type_=use_type,
|
||||
class_validators={},
|
||||
default=None,
|
||||
required=False,
|
||||
model_config=BaseConfig,
|
||||
field_info=FieldInfo(None),
|
||||
)
|
||||
else: # pragma: nocover
|
||||
new_field = ModelField( # type: ignore
|
||||
name=field.name,
|
||||
type_=use_type,
|
||||
class_validators={},
|
||||
default=None,
|
||||
required=False,
|
||||
model_config=BaseConfig,
|
||||
schema=FieldInfo(None),
|
||||
)
|
||||
|
||||
new_field = create_response_field(name=field.name, type_=use_type)
|
||||
new_field.has_alias = field.has_alias
|
||||
new_field.alias = field.alias
|
||||
new_field.class_validators = field.class_validators
|
||||
|
||||
@@ -117,6 +117,11 @@ markdown_extensions:
|
||||
- admonition
|
||||
- codehilite
|
||||
- extra
|
||||
- pymdownx.superfences:
|
||||
custom_fences:
|
||||
- name: mermaid
|
||||
class: mermaid
|
||||
format: !!python/name:pymdownx.superfences.fence_div_format
|
||||
|
||||
extra:
|
||||
social:
|
||||
@@ -137,4 +142,5 @@ extra_css:
|
||||
- 'css/custom.css'
|
||||
|
||||
extra_javascript:
|
||||
- 'https://unpkg.com/mermaid@8.4.6/dist/mermaid.min.js'
|
||||
- 'js/custom.js'
|
||||
|
||||
1
mypy.ini
1
mypy.ini
@@ -1,2 +1,3 @@
|
||||
[mypy]
|
||||
disallow_untyped_defs = True
|
||||
ignore_missing_imports = True
|
||||
|
||||
@@ -55,7 +55,10 @@ test = [
|
||||
"databases[sqlite]",
|
||||
"orjson",
|
||||
"async_exit_stack",
|
||||
"async_generator"
|
||||
"async_generator",
|
||||
"python-multipart",
|
||||
"aiofiles",
|
||||
"ujson"
|
||||
]
|
||||
doc = [
|
||||
"mkdocs",
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
set -e
|
||||
set -x
|
||||
|
||||
mypy fastapi --disallow-untyped-defs
|
||||
mypy fastapi
|
||||
black fastapi tests --check
|
||||
isort --multi-line=3 --trailing-comma --force-grid-wrap=0 --combine-as --line-width 88 --recursive --check-only --thirdparty fastapi fastapi tests
|
||||
|
||||
24
tests/test_callable_endpoint.py
Normal file
24
tests/test_callable_endpoint.py
Normal file
@@ -0,0 +1,24 @@
|
||||
from functools import partial
|
||||
|
||||
from fastapi import FastAPI
|
||||
from starlette.testclient import TestClient
|
||||
|
||||
|
||||
def main(some_arg, q: str = None):
|
||||
return {"some_arg": some_arg, "q": q}
|
||||
|
||||
|
||||
endpoint = partial(main, "foo")
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
app.get("/")(endpoint)
|
||||
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
def test_partial():
|
||||
response = client.get("/?q=bar")
|
||||
data = response.json()
|
||||
assert data == {"some_arg": "foo", "q": "bar"}
|
||||
@@ -1,9 +1,10 @@
|
||||
from datetime import datetime, timezone
|
||||
from enum import Enum
|
||||
from pathlib import PurePath, PurePosixPath, PureWindowsPath
|
||||
|
||||
import pytest
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from pydantic import BaseModel, ValidationError
|
||||
from pydantic import BaseModel, ValidationError, create_model
|
||||
|
||||
try:
|
||||
from pydantic import Field
|
||||
@@ -69,6 +70,19 @@ class ModelWithAlias(BaseModel):
|
||||
foo: str = Field(..., alias="Foo")
|
||||
|
||||
|
||||
@pytest.fixture(
|
||||
name="model_with_path", params=[PurePath, PurePosixPath, PureWindowsPath]
|
||||
)
|
||||
def fixture_model_with_path(request):
|
||||
class Config:
|
||||
arbitrary_types_allowed = True
|
||||
|
||||
ModelWithPath = create_model(
|
||||
"ModelWithPath", path=(request.param, ...), __config__=Config
|
||||
)
|
||||
return ModelWithPath(path=request.param("/foo", "bar"))
|
||||
|
||||
|
||||
def test_encode_class():
|
||||
person = Person(name="Foo")
|
||||
pet = Pet(owner=person, name="Firulais")
|
||||
@@ -120,3 +134,11 @@ def test_custom_encoders():
|
||||
instance, custom_encoder={safe_datetime: lambda o: o.isoformat()}
|
||||
)
|
||||
assert encoded_instance["dt_field"] == instance.dt_field.isoformat()
|
||||
|
||||
|
||||
def test_encode_model_with_path(model_with_path):
|
||||
if isinstance(model_with_path.path, PureWindowsPath):
|
||||
expected = "\\foo\\bar"
|
||||
else:
|
||||
expected = "/foo/bar"
|
||||
assert jsonable_encoder(model_with_path) == {"path": expected}
|
||||
|
||||
93
tests/test_param_in_path_and_dependency.py
Normal file
93
tests/test_param_in_path_and_dependency.py
Normal file
@@ -0,0 +1,93 @@
|
||||
from fastapi import Depends, FastAPI
|
||||
from starlette.testclient import TestClient
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
async def user_exists(user_id: int):
|
||||
return True
|
||||
|
||||
|
||||
@app.get("/users/{user_id}", dependencies=[Depends(user_exists)])
|
||||
async def read_users(user_id: int):
|
||||
pass
|
||||
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
openapi_schema = {
|
||||
"openapi": "3.0.2",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/users/{user_id}": {
|
||||
"get": {
|
||||
"summary": "Read Users",
|
||||
"operationId": "read_users_users__user_id__get",
|
||||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "User Id", "type": "integer"},
|
||||
"name": "user_id",
|
||||
"in": "path",
|
||||
},
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
"ValidationError": {
|
||||
"title": "ValidationError",
|
||||
"required": ["loc", "msg", "type"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"loc": {
|
||||
"title": "Location",
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
},
|
||||
"msg": {"title": "Message", "type": "string"},
|
||||
"type": {"title": "Error Type", "type": "string"},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def test_reused_param():
|
||||
response = client.get("/openapi.json")
|
||||
data = response.json()
|
||||
assert data == openapi_schema
|
||||
|
||||
|
||||
def test_read_users():
|
||||
response = client.get("/users/42")
|
||||
assert response.status_code == 200
|
||||
45
tests/test_response_model_invalid.py
Normal file
45
tests/test_response_model_invalid.py
Normal file
@@ -0,0 +1,45 @@
|
||||
from typing import List
|
||||
|
||||
import pytest
|
||||
from fastapi import FastAPI
|
||||
from fastapi.exceptions import FastAPIError
|
||||
|
||||
|
||||
class NonPydanticModel:
|
||||
pass
|
||||
|
||||
|
||||
def test_invalid_response_model_raises():
|
||||
with pytest.raises(FastAPIError):
|
||||
app = FastAPI()
|
||||
|
||||
@app.get("/", response_model=NonPydanticModel)
|
||||
def read_root():
|
||||
pass # pragma: nocover
|
||||
|
||||
|
||||
def test_invalid_response_model_sub_type_raises():
|
||||
with pytest.raises(FastAPIError):
|
||||
app = FastAPI()
|
||||
|
||||
@app.get("/", response_model=List[NonPydanticModel])
|
||||
def read_root():
|
||||
pass # pragma: nocover
|
||||
|
||||
|
||||
def test_invalid_response_model_in_responses_raises():
|
||||
with pytest.raises(FastAPIError):
|
||||
app = FastAPI()
|
||||
|
||||
@app.get("/", responses={"500": {"model": NonPydanticModel}})
|
||||
def read_root():
|
||||
pass # pragma: nocover
|
||||
|
||||
|
||||
def test_invalid_response_model_sub_type_in_responses_raises():
|
||||
with pytest.raises(FastAPIError):
|
||||
app = FastAPI()
|
||||
|
||||
@app.get("/", responses={"500": {"model": List[NonPydanticModel]}})
|
||||
def read_root():
|
||||
pass # pragma: nocover
|
||||
160
tests/test_response_model_sub_types.py
Normal file
160
tests/test_response_model_sub_types.py
Normal file
@@ -0,0 +1,160 @@
|
||||
from typing import List
|
||||
|
||||
from fastapi import FastAPI
|
||||
from pydantic import BaseModel
|
||||
from starlette.testclient import TestClient
|
||||
|
||||
|
||||
class Model(BaseModel):
|
||||
name: str
|
||||
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.get("/valid1", responses={"500": {"model": int}})
|
||||
def valid1():
|
||||
pass
|
||||
|
||||
|
||||
@app.get("/valid2", responses={"500": {"model": List[int]}})
|
||||
def valid2():
|
||||
pass
|
||||
|
||||
|
||||
@app.get("/valid3", responses={"500": {"model": Model}})
|
||||
def valid3():
|
||||
pass
|
||||
|
||||
|
||||
@app.get("/valid4", responses={"500": {"model": List[Model]}})
|
||||
def valid4():
|
||||
pass
|
||||
|
||||
|
||||
openapi_schema = {
|
||||
"openapi": "3.0.2",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/valid1": {
|
||||
"get": {
|
||||
"summary": "Valid1",
|
||||
"operationId": "valid1_valid1_get",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"title": "Response 500 Valid1 Valid1 Get",
|
||||
"type": "integer",
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
"/valid2": {
|
||||
"get": {
|
||||
"summary": "Valid2",
|
||||
"operationId": "valid2_valid2_get",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"title": "Response 500 Valid2 Valid2 Get",
|
||||
"type": "array",
|
||||
"items": {"type": "integer"},
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
"/valid3": {
|
||||
"get": {
|
||||
"summary": "Valid3",
|
||||
"operationId": "valid3_valid3_get",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {"$ref": "#/components/schemas/Model"}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
"/valid4": {
|
||||
"get": {
|
||||
"summary": "Valid4",
|
||||
"operationId": "valid4_valid4_get",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"title": "Response 500 Valid4 Valid4 Get",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/Model"},
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"Model": {
|
||||
"title": "Model",
|
||||
"required": ["name"],
|
||||
"type": "object",
|
||||
"properties": {"name": {"title": "Name", "type": "string"}},
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
def test_openapi_schema():
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == openapi_schema
|
||||
|
||||
|
||||
def test_path_operations():
|
||||
response = client.get("/valid1")
|
||||
assert response.status_code == 200
|
||||
response = client.get("/valid2")
|
||||
assert response.status_code == 200
|
||||
response = client.get("/valid3")
|
||||
assert response.status_code == 200
|
||||
response = client.get("/valid4")
|
||||
assert response.status_code == 200
|
||||
Reference in New Issue
Block a user