mirror of
https://github.com/fastapi/fastapi.git
synced 2025-12-24 06:39:31 -05:00
Compare commits
91 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0bb8920ae1 | ||
|
|
95c182a42c | ||
|
|
165d57db24 | ||
|
|
ae5280e501 | ||
|
|
d4b2ef8137 | ||
|
|
fd2080aece | ||
|
|
3c8d8c38a7 | ||
|
|
fa92dbf557 | ||
|
|
cd8854c4c7 | ||
|
|
b63398f4b5 | ||
|
|
728b961d35 | ||
|
|
5e34cb1868 | ||
|
|
bbd34c55d5 | ||
|
|
8a72d363c8 | ||
|
|
ee93ad952c | ||
|
|
d26674616c | ||
|
|
40a19fe261 | ||
|
|
8b4546352f | ||
|
|
b3f2f08251 | ||
|
|
3d3a7dbe7e | ||
|
|
d69b5f39b4 | ||
|
|
70213467a6 | ||
|
|
8d1d1703f4 | ||
|
|
1285ba4b2e | ||
|
|
6bbfd27c19 | ||
|
|
4c0b9b831c | ||
|
|
9e6dcba766 | ||
|
|
e1825589ba | ||
|
|
acc59df815 | ||
|
|
d86b54e79b | ||
|
|
6b77179e3b | ||
|
|
8dab25df71 | ||
|
|
22bed0008c | ||
|
|
de6ccd7754 | ||
|
|
26c68c6e0d | ||
|
|
dc10b81d05 | ||
|
|
1f2070af20 | ||
|
|
a95212565a | ||
|
|
d80b065b5e | ||
|
|
dde140d8e3 | ||
|
|
96c3f44a0e | ||
|
|
0539dd9cd3 | ||
|
|
aaf5a380df | ||
|
|
e3c055ba8f | ||
|
|
afe44f0b25 | ||
|
|
8cd8aa4b67 | ||
|
|
26097421d3 | ||
|
|
3df68694c0 | ||
|
|
f3e9dcd891 | ||
|
|
00bdf533ef | ||
|
|
ae56590c51 | ||
|
|
923e0ac0c1 | ||
|
|
a64387c3fc | ||
|
|
c8124496fc | ||
|
|
75792eb82b | ||
|
|
bb53d0b0ea | ||
|
|
f928f3390c | ||
|
|
d5c84594cb | ||
|
|
0968329ed7 | ||
|
|
880c8b37cf | ||
|
|
92181ef182 | ||
|
|
ca2fae0588 | ||
|
|
af3a6ef78b | ||
|
|
eb3ab337ab | ||
|
|
5215b39d50 | ||
|
|
9359a8d65f | ||
|
|
afaa0391a5 | ||
|
|
438656395a | ||
|
|
39319a7ede | ||
|
|
8c6ad35615 | ||
|
|
f90465f5b4 | ||
|
|
1835b3f76d | ||
|
|
2201f29be3 | ||
|
|
68f25120c5 | ||
|
|
7d6e70791d | ||
|
|
18819f9850 | ||
|
|
ad65e72d90 | ||
|
|
ec072d75fe | ||
|
|
09acce4b2c | ||
|
|
f6808e76dc | ||
|
|
f8f5281ef5 | ||
|
|
b993b4af28 | ||
|
|
982911f08f | ||
|
|
634cf22584 | ||
|
|
12f60cac7a | ||
|
|
e7b1b96a54 | ||
|
|
a6a39f3009 | ||
|
|
1ff8df6e7f | ||
|
|
7953353a35 | ||
|
|
6c16d6a4f9 | ||
|
|
eb80b79555 |
2
.github/workflows/preview-docs.yml
vendored
2
.github/workflows/preview-docs.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Download Artifact Docs
|
||||
uses: dawidd6/action-download-artifact@v2.21.1
|
||||
uses: dawidd6/action-download-artifact@v2.22.0
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
workflow: build-docs.yml
|
||||
|
||||
@@ -12,7 +12,7 @@ repos:
|
||||
- id: end-of-file-fixer
|
||||
- id: trailing-whitespace
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v2.37.1
|
||||
rev: v2.37.3
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args:
|
||||
|
||||
@@ -32,7 +32,6 @@ FastAPI is a modern, fast (high-performance), web framework for building APIs wi
|
||||
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.
|
||||
|
||||
@@ -97,7 +97,7 @@ Hierdurch werden Sie nie wieder einen falschen Schlüsselnamen benutzen und spar
|
||||
|
||||
### Kompakt
|
||||
|
||||
FastAPI nutzt für alles sinnvolle **Standard-Einstellungen**, welche optional überall konfiguriert werden können. Alle Parameter können ganz genau an Ihre Bedürfnisse angepasst werden, sodass sie genau die API definieren können, die sie brauchen.
|
||||
FastAPI nutzt für alles sensible **Standard-Einstellungen**, welche optional überall konfiguriert werden können. Alle Parameter können ganz genau an Ihre Bedürfnisse angepasst werden, sodass sie genau die API definieren können, die sie brauchen.
|
||||
|
||||
Aber standardmäßig, **"funktioniert einfach"** alles.
|
||||
|
||||
@@ -119,9 +119,9 @@ Die gesamte Validierung übernimmt das etablierte und robuste **Pydantic**.
|
||||
|
||||
### Sicherheit und Authentifizierung
|
||||
|
||||
Sicherheit und Authentifizierung integriert. Ohne einen Kompromiss aufgrund einer Datenbank oder den Datenentitäten.
|
||||
Integrierte Sicherheit und Authentifizierung. Ohne Kompromisse bei Datenbanken oder Datenmodellen.
|
||||
|
||||
Unterstützt alle von OpenAPI definierten Sicherheitsschemata, hierzu gehören:
|
||||
Unterstützt werden alle von OpenAPI definierten Sicherheitsschemata, hierzu gehören:
|
||||
|
||||
* HTTP Basis Authentifizierung.
|
||||
* **OAuth2** (auch mit **JWT Zugriffstokens**). Schauen Sie sich hierzu dieses Tutorial an: [OAuth2 mit JWT](tutorial/security/oauth2-jwt.md){.internal-link target=_blank}.
|
||||
@@ -142,8 +142,8 @@ FastAPI enthält ein extrem einfaches, aber extrem mächtiges <abbr title='oft v
|
||||
* **Automatische Umsetzung** durch FastAPI.
|
||||
* Alle abhängigen Komponenten könnten Daten von Anfragen, **Erweiterungen der Pfadoperations-**Einschränkungen und der automatisierten Dokumentation benötigen.
|
||||
* **Automatische Validierung** selbst für *Pfadoperationen*-Parameter, die in den Abhängigkeiten definiert wurden.
|
||||
* Unterstützt komplexe Benutzerauthentifizierungssysteme, mit **Datenbankverbindungen**, usw.
|
||||
* **Keine Kompromisse** bei Datenbanken, Eingabemasken, usw., sondern einfache Integration von allen.
|
||||
* Unterstützt komplexe Benutzerauthentifizierungssysteme, **Datenbankverbindungen**, usw.
|
||||
* **Keine Kompromisse** bei Datenbanken, Eingabemasken, usw. Sondern einfache Integration von allen.
|
||||
|
||||
### Unbegrenzte Erweiterungen
|
||||
|
||||
@@ -165,7 +165,7 @@ Jede Integration wurde so entworfen, dass sie einfach zu nutzen ist (mit Abhäng
|
||||
|
||||
Mit **FastAPI** bekommen Sie viele von **Starlette**'s Funktionen (da FastAPI nur Starlette auf Steroiden ist):
|
||||
|
||||
* Stark beeindruckende Performanz. Es ist <a href="https://github.com/encode/starlette#performance" class="external-link" target="_blank">eines der schnellsten Python frameworks, auf Augenhöhe mit **NodeJS** und **Go**</a>.
|
||||
* Stark beeindruckende Performanz. Es ist <a href="https://github.com/encode/starlette#performance" class="external-link" target="_blank">eines der schnellsten Python Frameworks, auf Augenhöhe mit **NodeJS** und **Go**</a>.
|
||||
* **WebSocket**-Unterstützung.
|
||||
* Hintergrundaufgaben im selben Prozess.
|
||||
* Ereignisse für das Starten und Herunterfahren.
|
||||
@@ -196,8 +196,8 @@ Mit **FastAPI** bekommen Sie alle Funktionen von **Pydantic** (da FastAPI für d
|
||||
* In <a href="https://pydantic-docs.helpmanual.io/benchmarks/" class="external-link" target="_blank">Vergleichen</a> ist Pydantic schneller als jede andere getestete Bibliothek.
|
||||
* Validierung von **komplexen Strukturen**:
|
||||
* Benutzung von hierachischen Pydantic Schemata, Python `typing`’s `List` und `Dict`, etc.
|
||||
* Validierungen erlauben klare und einfache Datenschemadefinition, überprüft und dokumentiert als JSON Schema.
|
||||
* Validierungen erlauben eine klare und einfache Datenschemadefinition, überprüft und dokumentiert als JSON Schema.
|
||||
* Sie können stark **verschachtelte JSON** Objekte haben und diese sind trotzdem validiert und annotiert.
|
||||
* **Erweiterbar**:
|
||||
* Pydantic erlaubt die Definition von eigenen Datentypen oder Sie können die Validierung mit einer `validator` dekorierten Methode erweitern..
|
||||
* Pydantic erlaubt die Definition von eigenen Datentypen oder sie können die Validierung mit einer `validator` dekorierten Methode erweitern.
|
||||
* 100% Testabdeckung.
|
||||
|
||||
@@ -212,6 +212,10 @@ articles:
|
||||
author_link: https://twitter.com/MantoshMukul
|
||||
link: https://www.jetbrains.com/pycharm/guide/tutorials/fastapi-aws-kubernetes/
|
||||
title: Developing FastAPI Application using K8s & AWS
|
||||
- author: KrishNa
|
||||
author_link: https://medium.com/@krishnardt365
|
||||
link: https://medium.com/@krishnardt365/fastapi-docker-and-postgres-91943e71be92
|
||||
title: Fastapi, Docker(Docker compose) and Postgres
|
||||
german:
|
||||
- author: Nico Axtmann
|
||||
author_link: https://twitter.com/_nicoax
|
||||
|
||||
@@ -23,7 +23,7 @@ Each of those response `dict`s can have a key `model`, containing a Pydantic mod
|
||||
|
||||
For example, to declare another response with a status code `404` and a Pydantic model `Message`, you can write:
|
||||
|
||||
```Python hl_lines="18 23"
|
||||
```Python hl_lines="18 22"
|
||||
{!../../../docs_src/additional_responses/tutorial001.py!}
|
||||
```
|
||||
|
||||
|
||||
@@ -26,13 +26,23 @@ The important difference for us is that with HTTPX we are not limited to synchro
|
||||
|
||||
## Example
|
||||
|
||||
For a simple example, let's consider the following `main.py` module:
|
||||
For a simple example, let's consider a file structure similar to the one described in [Bigger Applications](../tutorial/bigger-applications.md){.internal-link target=_blank} and [Testing](../tutorial/testing.md){.internal-link target=_blank}:
|
||||
|
||||
```
|
||||
.
|
||||
├── app
|
||||
│ ├── __init__.py
|
||||
│ ├── main.py
|
||||
│ └── test_main.py
|
||||
```
|
||||
|
||||
The file `main.py` would have:
|
||||
|
||||
```Python
|
||||
{!../../../docs_src/async_tests/main.py!}
|
||||
```
|
||||
|
||||
The `test_main.py` module that contains the tests for `main.py` could look like this now:
|
||||
The file `test_main.py` would have the tests for `main.py`, it could look like this now:
|
||||
|
||||
```Python
|
||||
{!../../../docs_src/async_tests/test_main.py!}
|
||||
|
||||
@@ -31,7 +31,7 @@ It will have a *path operation* that will receive an `Invoice` body, and a query
|
||||
|
||||
This part is pretty normal, most of the code is probably already familiar to you:
|
||||
|
||||
```Python hl_lines="10-14 37-54"
|
||||
```Python hl_lines="9-13 36-53"
|
||||
{!../../../docs_src/openapi_callbacks/tutorial001.py!}
|
||||
```
|
||||
|
||||
@@ -83,7 +83,7 @@ So we are going to use that same knowledge to document how the *external API* sh
|
||||
|
||||
First create a new `APIRouter` that will contain one or more callbacks.
|
||||
|
||||
```Python hl_lines="5 26"
|
||||
```Python hl_lines="3 25"
|
||||
{!../../../docs_src/openapi_callbacks/tutorial001.py!}
|
||||
```
|
||||
|
||||
@@ -96,7 +96,7 @@ It should look just like a normal FastAPI *path operation*:
|
||||
* It should probably have a declaration of the body it should receive, e.g. `body: InvoiceEvent`.
|
||||
* And it could also have a declaration of the response it should return, e.g. `response_model=InvoiceEventReceived`.
|
||||
|
||||
```Python hl_lines="17-19 22-23 29-33"
|
||||
```Python hl_lines="16-18 21-22 28-32"
|
||||
{!../../../docs_src/openapi_callbacks/tutorial001.py!}
|
||||
```
|
||||
|
||||
@@ -163,7 +163,7 @@ At this point you have the *callback path operation(s)* needed (the one(s) that
|
||||
|
||||
Now use the parameter `callbacks` in *your API's path operation decorator* to pass the attribute `.routes` (that's actually just a `list` of routes/*path operations*) from that callback router:
|
||||
|
||||
```Python hl_lines="36"
|
||||
```Python hl_lines="35"
|
||||
{!../../../docs_src/openapi_callbacks/tutorial001.py!}
|
||||
```
|
||||
|
||||
|
||||
@@ -34,13 +34,19 @@ Here's a more complete example.
|
||||
|
||||
Use a dependency to check if the username and password are correct.
|
||||
|
||||
For this, use the Python standard module <a href="https://docs.python.org/3/library/secrets.html" class="external-link" target="_blank">`secrets`</a> to check the username and password:
|
||||
For this, use the Python standard module <a href="https://docs.python.org/3/library/secrets.html" class="external-link" target="_blank">`secrets`</a> to check the username and password.
|
||||
|
||||
```Python hl_lines="1 11-13"
|
||||
`secrets.compare_digest()` needs to take `bytes` or a `str` that only contains ASCII characters (the ones in English), this means it wouldn't work with characters like `á`, as in `Sebastián`.
|
||||
|
||||
To handle that, we first convert the `username` and `password` to `bytes` encoding them with UTF-8.
|
||||
|
||||
Then we can use `secrets.compare_digest()` to ensure that `credentials.username` is `"stanleyjobson"`, and that `credentials.password` is `"swordfish"`.
|
||||
|
||||
```Python hl_lines="1 11-21"
|
||||
{!../../../docs_src/security/tutorial007.py!}
|
||||
```
|
||||
|
||||
This will ensure that `credentials.username` is `"stanleyjobson"`, and that `credentials.password` is `"swordfish"`. This would be similar to:
|
||||
This would be similar to:
|
||||
|
||||
```Python
|
||||
if not (credentials.username == "stanleyjobson") or not (credentials.password == "swordfish"):
|
||||
@@ -102,6 +108,6 @@ That way, using `secrets.compare_digest()` in your application code, it will be
|
||||
|
||||
After detecting that the credentials are incorrect, return an `HTTPException` with a status code 401 (the same returned when no credentials are provided) and add the header `WWW-Authenticate` to make the browser show the login prompt again:
|
||||
|
||||
```Python hl_lines="15-19"
|
||||
```Python hl_lines="23-27"
|
||||
{!../../../docs_src/security/tutorial007.py!}
|
||||
```
|
||||
|
||||
@@ -26,7 +26,7 @@ async def read_results():
|
||||
|
||||
---
|
||||
|
||||
If you are using a third party library that communicates with something (a database, an API, the file system, etc) and doesn't have support for using `await`, (this is currently the case for most database libraries), then declare your *path operation functions* as normally, with just `def`, like:
|
||||
If you are using a third party library that communicates with something (a database, an API, the file system, etc.) and doesn't have support for using `await`, (this is currently the case for most database libraries), then declare your *path operation functions* as normally, with just `def`, like:
|
||||
|
||||
```Python hl_lines="2"
|
||||
@app.get('/')
|
||||
@@ -45,7 +45,7 @@ If you just don't know, use normal `def`.
|
||||
|
||||
---
|
||||
|
||||
**Note**: you can mix `def` and `async def` in your *path operation functions* as much as you need and define each one using the best option for you. FastAPI will do the right thing with them.
|
||||
**Note**: You can mix `def` and `async def` in your *path operation functions* as much as you need and define each one using the best option for you. FastAPI will do the right thing with them.
|
||||
|
||||
Anyway, in any of the cases above, FastAPI will still work asynchronously and be extremely fast.
|
||||
|
||||
@@ -397,7 +397,7 @@ All that is what powers FastAPI (through Starlette) and what makes it have such
|
||||
|
||||
These are very technical details of how **FastAPI** works underneath.
|
||||
|
||||
If you have quite some technical knowledge (co-routines, threads, blocking, etc) and are curious about how FastAPI handles `async def` vs normal `def`, go ahead.
|
||||
If you have quite some technical knowledge (co-routines, threads, blocking, etc.) and are curious about how FastAPI handles `async def` vs normal `def`, go ahead.
|
||||
|
||||
### Path operation functions
|
||||
|
||||
|
||||
@@ -84,7 +84,17 @@ To check it worked, use:
|
||||
|
||||
If it shows the `pip` binary at `env/bin/pip` then it worked. 🎉
|
||||
|
||||
Make sure you have the latest pip version on your virtual environment to avoid errors on the next steps:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ python -m pip install --upgrade pip
|
||||
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
!!! tip
|
||||
Every time you install a new package with `pip` under that environment, activate the environment again.
|
||||
|
||||
@@ -195,6 +195,6 @@ With **FastAPI** you get all of **Pydantic**'s features (as FastAPI is based on
|
||||
* Use of hierarchical Pydantic models, Python `typing`’s `List` and `Dict`, etc.
|
||||
* And validators allow complex data schemas to be clearly and easily defined, checked and documented as JSON Schema.
|
||||
* You can have deeply **nested JSON** objects and have them all validated and annotated.
|
||||
* **Extendible**:
|
||||
* **Extensible**:
|
||||
* Pydantic allows custom data types to be defined or you can extend validation with methods on a model decorated with the validator decorator.
|
||||
* 100% test coverage.
|
||||
|
||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 107 KiB After Width: | Height: | Size: 107 KiB |
@@ -32,7 +32,6 @@ FastAPI is a modern, fast (high-performance), web framework for building APIs wi
|
||||
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.
|
||||
|
||||
@@ -158,7 +158,7 @@ The syntax using `typing` is **compatible** with all versions, from Python 3.6 t
|
||||
|
||||
As Python advances, **newer versions** come with improved support for these type annotations and in many cases you won't even need to import and use the `typing` module to declare the type annotations.
|
||||
|
||||
If you can chose a more recent version of Python for your project, you will be able to take advantage of that extra simplicity. See some examples below.
|
||||
If you can choose a more recent version of Python for your project, you will be able to take advantage of that extra simplicity. See some examples below.
|
||||
|
||||
#### List
|
||||
|
||||
@@ -372,7 +372,7 @@ These types that take type parameters in square brackets are called **Generic ty
|
||||
|
||||
=== "Python 3.9 and above"
|
||||
|
||||
You can use the same builtin types as generics (with square brakets and types inside):
|
||||
You can use the same builtin types as generics (with square brackets and types inside):
|
||||
|
||||
* `list`
|
||||
* `tuple`
|
||||
@@ -387,7 +387,7 @@ These types that take type parameters in square brackets are called **Generic ty
|
||||
|
||||
=== "Python 3.10 and above"
|
||||
|
||||
You can use the same builtin types as generics (with square brakets and types inside):
|
||||
You can use the same builtin types as generics (with square brackets and types inside):
|
||||
|
||||
* `list`
|
||||
* `tuple`
|
||||
|
||||
@@ -3,6 +3,142 @@
|
||||
## Latest Changes
|
||||
|
||||
|
||||
## 0.81.0
|
||||
|
||||
### Features
|
||||
|
||||
* ✨ Add ReDoc `<noscript>` warning when JS is disabled. PR [#5074](https://github.com/tiangolo/fastapi/pull/5074) by [@evroon](https://github.com/evroon).
|
||||
* ✨ Add support for `FrozenSet` in parameters (e.g. query). PR [#2938](https://github.com/tiangolo/fastapi/pull/2938) by [@juntatalor](https://github.com/juntatalor).
|
||||
* ✨ Allow custom middlewares to raise `HTTPException`s and propagate them. PR [#2036](https://github.com/tiangolo/fastapi/pull/2036) by [@ghandic](https://github.com/ghandic).
|
||||
* ✨ Preserve `json.JSONDecodeError` information when handling invalid JSON in request body, to support custom exception handlers that use its information. PR [#4057](https://github.com/tiangolo/fastapi/pull/4057) by [@UKnowWhoIm](https://github.com/UKnowWhoIm).
|
||||
|
||||
### Fixes
|
||||
|
||||
* 🐛 Fix `jsonable_encoder` for dataclasses with pydantic-compatible fields. PR [#3607](https://github.com/tiangolo/fastapi/pull/3607) by [@himbeles](https://github.com/himbeles).
|
||||
* 🐛 Fix support for extending `openapi_extras` with parameter lists. PR [#4267](https://github.com/tiangolo/fastapi/pull/4267) by [@orilevari](https://github.com/orilevari).
|
||||
|
||||
### Docs
|
||||
|
||||
* ✏ Fix a simple typo in `docs/en/docs/python-types.md`. PR [#5193](https://github.com/tiangolo/fastapi/pull/5193) by [@GlitchingCore](https://github.com/GlitchingCore).
|
||||
* ✏ Fix typos in `tests/test_schema_extra_examples.py`. PR [#5126](https://github.com/tiangolo/fastapi/pull/5126) by [@supraaxdd](https://github.com/supraaxdd).
|
||||
* ✏ Fix typos in `docs/en/docs/tutorial/path-params-numeric-validations.md`. PR [#5142](https://github.com/tiangolo/fastapi/pull/5142) by [@invisibleroads](https://github.com/invisibleroads).
|
||||
* 📝 Add step about upgrading pip in the venv to avoid errors when installing dependencies `docs/en/docs/contributing.md`. PR [#5181](https://github.com/tiangolo/fastapi/pull/5181) by [@edisnake](https://github.com/edisnake).
|
||||
* ✏ Reword and clarify text in tutorial `docs/en/docs/tutorial/body-nested-models.md`. PR [#5169](https://github.com/tiangolo/fastapi/pull/5169) by [@papb](https://github.com/papb).
|
||||
* ✏ Fix minor typo in `docs/en/docs/features.md`. PR [#5206](https://github.com/tiangolo/fastapi/pull/5206) by [@OtherBarry](https://github.com/OtherBarry).
|
||||
* ✏ Fix minor typos in `docs/en/docs/async.md`. PR [#5125](https://github.com/tiangolo/fastapi/pull/5125) by [@Ksenofanex](https://github.com/Ksenofanex).
|
||||
* 📝 Add external link to docs: "Fastapi, Docker(Docker compose) and Postgres". PR [#5033](https://github.com/tiangolo/fastapi/pull/5033) by [@krishnardt](https://github.com/krishnardt).
|
||||
* 📝 Simplify example for docs for Additional Responses, remove unnecessary `else`. PR [#4693](https://github.com/tiangolo/fastapi/pull/4693) by [@adriangb](https://github.com/adriangb).
|
||||
* 📝 Update docs, compare enums with identity instead of equality. PR [#4905](https://github.com/tiangolo/fastapi/pull/4905) by [@MicaelJarniac](https://github.com/MicaelJarniac).
|
||||
* ✏ Fix typo in `docs/en/docs/python-types.md`. PR [#4886](https://github.com/tiangolo/fastapi/pull/4886) by [@MicaelJarniac](https://github.com/MicaelJarniac).
|
||||
* 🎨 Fix syntax highlighting in docs for OpenAPI Callbacks. PR [#4368](https://github.com/tiangolo/fastapi/pull/4368) by [@xncbf](https://github.com/xncbf).
|
||||
* ✏ Reword confusing sentence in docs file `typo-fix-path-params-numeric-validations.md`. PR [#3219](https://github.com/tiangolo/fastapi/pull/3219) by [@ccrenfroe](https://github.com/ccrenfroe).
|
||||
* 📝 Update docs for handling HTTP Basic Auth with `secrets.compare_digest()` to account for non-ASCII characters. PR [#3536](https://github.com/tiangolo/fastapi/pull/3536) by [@lewoudar](https://github.com/lewoudar).
|
||||
* 📝 Update docs for testing, fix examples with relative imports. PR [#5302](https://github.com/tiangolo/fastapi/pull/5302) by [@tiangolo](https://github.com/tiangolo).
|
||||
|
||||
### Translations
|
||||
|
||||
* 🌐 Add Russian translation for `docs/ru/docs/index.md`. PR [#5289](https://github.com/tiangolo/fastapi/pull/5289) by [@impocode](https://github.com/impocode).
|
||||
* 🌐 Add Russian translation for `docs/ru/docs/deployment/versions.md`. PR [#4985](https://github.com/tiangolo/fastapi/pull/4985) by [@emp7yhead](https://github.com/emp7yhead).
|
||||
* 🌐 Add Portuguese translation for `docs/pt/docs/tutorial/header-params.md`. PR [#4921](https://github.com/tiangolo/fastapi/pull/4921) by [@batlopes](https://github.com/batlopes).
|
||||
* 🌐 Update `ko/mkdocs.yml` for a missing link. PR [#5020](https://github.com/tiangolo/fastapi/pull/5020) by [@dalinaum](https://github.com/dalinaum).
|
||||
|
||||
### Internal
|
||||
|
||||
* ⬆ Bump dawidd6/action-download-artifact from 2.21.1 to 2.22.0. PR [#5258](https://github.com/tiangolo/fastapi/pull/5258) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||
* ⬆ [pre-commit.ci] pre-commit autoupdate. PR [#5196](https://github.com/tiangolo/fastapi/pull/5196) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci).
|
||||
* 🔥 Delete duplicated tests in `tests/test_tutorial/test_sql_databases/test_sql_databases.py`. PR [#5040](https://github.com/tiangolo/fastapi/pull/5040) by [@raccoonyy](https://github.com/raccoonyy).
|
||||
* ♻ Simplify internal RegEx in `fastapi/utils.py`. PR [#5057](https://github.com/tiangolo/fastapi/pull/5057) by [@pylounge](https://github.com/pylounge).
|
||||
* 🔧 Fix Type hint of `auto_error` which does not need to be `Optional[bool]`. PR [#4933](https://github.com/tiangolo/fastapi/pull/4933) by [@DavidKimDY](https://github.com/DavidKimDY).
|
||||
* 🔧 Update mypy config, use `strict = true` instead of manual configs. PR [#4605](https://github.com/tiangolo/fastapi/pull/4605) by [@michaeloliverx](https://github.com/michaeloliverx).
|
||||
* ♻ Change a `dict()` for `{}` in `fastapi/utils.py`. PR [#3138](https://github.com/tiangolo/fastapi/pull/3138) by [@ShahriyarR](https://github.com/ShahriyarR).
|
||||
* ♻ Move internal variable for errors in `jsonable_encoder` to put related code closer. PR [#4560](https://github.com/tiangolo/fastapi/pull/4560) by [@GuilleQP](https://github.com/GuilleQP).
|
||||
* ♻ Simplify conditional assignment in `fastapi/dependencies/utils.py`. PR [#4597](https://github.com/tiangolo/fastapi/pull/4597) by [@cikay](https://github.com/cikay).
|
||||
* ⬆ Upgrade version pin accepted for Flake8, for internal code, to `flake8 >=3.8.3,<6.0.0`. PR [#4097](https://github.com/tiangolo/fastapi/pull/4097) by [@jamescurtin](https://github.com/jamescurtin).
|
||||
* 🍱 Update Jina banner, fix typo. PR [#5301](https://github.com/tiangolo/fastapi/pull/5301) by [@tiangolo](https://github.com/tiangolo).
|
||||
|
||||
## 0.80.0
|
||||
|
||||
### Breaking Changes - Fixes
|
||||
|
||||
* 🐛 Fix `response_model` not invalidating `None`. PR [#2725](https://github.com/tiangolo/fastapi/pull/2725) by [@hukkin](https://github.com/hukkin).
|
||||
|
||||
If you are using `response_model` with some type that doesn't include `None` but the function is returning `None`, it will now raise an internal server error, because you are returning invalid data that violates the contract in `response_model`. Before this release it would allow breaking that contract returning `None`.
|
||||
|
||||
For example, if you have an app like this:
|
||||
|
||||
```Python
|
||||
from fastapi import FastAPI
|
||||
from pydantic import BaseModel
|
||||
|
||||
class Item(BaseModel):
|
||||
name: str
|
||||
price: Optional[float] = None
|
||||
owner_ids: Optional[List[int]] = None
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
@app.get("/items/invalidnone", response_model=Item)
|
||||
def get_invalid_none():
|
||||
return None
|
||||
```
|
||||
|
||||
...calling the path `/items/invalidnone` will raise an error, because `None` is not a valid type for the `response_model` declared with `Item`.
|
||||
|
||||
You could also be implicitly returning `None` without realizing, for example:
|
||||
|
||||
```Python
|
||||
from fastapi import FastAPI
|
||||
from pydantic import BaseModel
|
||||
|
||||
class Item(BaseModel):
|
||||
name: str
|
||||
price: Optional[float] = None
|
||||
owner_ids: Optional[List[int]] = None
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
@app.get("/items/invalidnone", response_model=Item)
|
||||
def get_invalid_none():
|
||||
if flag:
|
||||
return {"name": "foo"}
|
||||
# if flag is False, at this point the function will implicitly return None
|
||||
```
|
||||
|
||||
If you have *path operations* using `response_model` that need to be allowed to return `None`, make it explicit in `response_model` using `Union[Something, None]`:
|
||||
|
||||
```Python
|
||||
from typing import Union
|
||||
|
||||
from fastapi import FastAPI
|
||||
from pydantic import BaseModel
|
||||
|
||||
class Item(BaseModel):
|
||||
name: str
|
||||
price: Optional[float] = None
|
||||
owner_ids: Optional[List[int]] = None
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
@app.get("/items/invalidnone", response_model=Union[Item, None])
|
||||
def get_invalid_none():
|
||||
return None
|
||||
```
|
||||
|
||||
This way the data will be correctly validated, you won't have an internal server error, and the documentation will also reflect that this *path operation* could return `None` (or `null` in JSON).
|
||||
|
||||
### Fixes
|
||||
|
||||
* ⬆ Upgrade Swagger UI copy of `oauth2-redirect.html` to include fixes for flavors of authorization code flows in Swagger UI. PR [#3439](https://github.com/tiangolo/fastapi/pull/3439) initial PR by [@koonpeng](https://github.com/koonpeng).
|
||||
* ♻ Strip empty whitespace from description extracted from docstrings. PR [#2821](https://github.com/tiangolo/fastapi/pull/2821) by [@and-semakin](https://github.com/and-semakin).
|
||||
* 🐛 Fix cached dependencies when using a dependency in `Security()` and other places (e.g. `Depends()`) with different OAuth2 scopes. PR [#2945](https://github.com/tiangolo/fastapi/pull/2945) by [@laggardkernel](https://github.com/laggardkernel).
|
||||
* 🎨 Update type annotations for `response_model`, allow things like `Union[str, None]`. PR [#5294](https://github.com/tiangolo/fastapi/pull/5294) by [@tiangolo](https://github.com/tiangolo).
|
||||
|
||||
### Translations
|
||||
|
||||
* 🌐 Fix typos in German translation for `docs/de/docs/features.md`. PR [#4533](https://github.com/tiangolo/fastapi/pull/4533) by [@0xflotus](https://github.com/0xflotus).
|
||||
* 🌐 Add missing navigator for `encoder.md` in Korean translation. PR [#5238](https://github.com/tiangolo/fastapi/pull/5238) by [@joonas-yoon](https://github.com/joonas-yoon).
|
||||
* (Empty PR merge by accident) [#4913](https://github.com/tiangolo/fastapi/pull/4913).
|
||||
|
||||
## 0.79.1
|
||||
|
||||
### Fixes
|
||||
|
||||
@@ -18,7 +18,7 @@ You can define an attribute to be a subtype. For example, a Python `list`:
|
||||
{!> ../../../docs_src/body_nested_models/tutorial001_py310.py!}
|
||||
```
|
||||
|
||||
This will make `tags` be a list of items. Although it doesn't declare the type of each of the items.
|
||||
This will make `tags` be a list, although it doesn't declare the type of the elements of the list.
|
||||
|
||||
## List fields with type parameter
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Path Parameters and Numeric Validations
|
||||
|
||||
The same way you can declare more validations and metadata for query parameters with `Query`, you can declare the same type of validations and metadata for path parameters with `Path`.
|
||||
In the same way that you can declare more validations and metadata for query parameters with `Query`, you can declare the same type of validations and metadata for path parameters with `Path`.
|
||||
|
||||
## Import Path
|
||||
|
||||
@@ -77,7 +77,7 @@ Python won't do anything with that `*`, but it will know that all the following
|
||||
|
||||
## Number validations: greater than or equal
|
||||
|
||||
With `Query` and `Path` (and other's you'll see later) you can declare string constraints, but also number constraints.
|
||||
With `Query` and `Path` (and others you'll see later) you can declare number constraints.
|
||||
|
||||
Here, with `ge=1`, `item_id` will need to be an integer number "`g`reater than or `e`qual" to `1`.
|
||||
|
||||
@@ -122,9 +122,9 @@ And you can also declare numeric validations:
|
||||
* `le`: `l`ess than or `e`qual
|
||||
|
||||
!!! info
|
||||
`Query`, `Path`, and others you will see later are subclasses of a common `Param` class (that you don't need to use).
|
||||
`Query`, `Path`, and other classes you will see later are subclasses of a common `Param` class.
|
||||
|
||||
And all of them share the same all these same parameters of additional validation and metadata you have seen.
|
||||
All of them share the same parameters for additional validation and metadata you have seen.
|
||||
|
||||
!!! note "Technical Details"
|
||||
When you import `Query`, `Path` and others from `fastapi`, they are actually functions.
|
||||
|
||||
@@ -50,7 +50,17 @@ And your **FastAPI** application might also be composed of several files/modules
|
||||
|
||||
### **FastAPI** app file
|
||||
|
||||
Let's say you have a file `main.py` with your **FastAPI** app:
|
||||
Let's say you have a file structure as described in [Bigger Applications](./bigger-applications.md){.internal-link target=_blank}:
|
||||
|
||||
```
|
||||
.
|
||||
├── app
|
||||
│ ├── __init__.py
|
||||
│ └── main.py
|
||||
```
|
||||
|
||||
In the file `main.py` you have your **FastAPI** app:
|
||||
|
||||
|
||||
```Python
|
||||
{!../../../docs_src/app_testing/main.py!}
|
||||
@@ -58,18 +68,40 @@ Let's say you have a file `main.py` with your **FastAPI** app:
|
||||
|
||||
### Testing file
|
||||
|
||||
Then you could have a file `test_main.py` with your tests, and import your `app` from the `main` module (`main.py`):
|
||||
Then you could have a file `test_main.py` with your tests. It could live on the same Python package (the same directory with a `__init__.py` file):
|
||||
|
||||
```Python
|
||||
``` hl_lines="5"
|
||||
.
|
||||
├── app
|
||||
│ ├── __init__.py
|
||||
│ ├── main.py
|
||||
│ └── test_main.py
|
||||
```
|
||||
|
||||
Because this file is in the same package, you can use relative imports to import the object `app` from the `main` module (`main.py`):
|
||||
|
||||
```Python hl_lines="3"
|
||||
{!../../../docs_src/app_testing/test_main.py!}
|
||||
```
|
||||
|
||||
...and have the code for the tests just like before.
|
||||
|
||||
## Testing: extended example
|
||||
|
||||
Now let's extend this example and add more details to see how to test different parts.
|
||||
|
||||
### Extended **FastAPI** app file
|
||||
|
||||
Let's continue with the same file structure as before:
|
||||
|
||||
```
|
||||
.
|
||||
├── app
|
||||
│ ├── __init__.py
|
||||
│ ├── main.py
|
||||
│ └── test_main.py
|
||||
```
|
||||
|
||||
Let's say that now the file `main.py` with your **FastAPI** app has some other **path operations**.
|
||||
|
||||
It has a `GET` operation that could return an error.
|
||||
|
||||
@@ -68,6 +68,7 @@ nav:
|
||||
- tutorial/response-status-code.md
|
||||
- tutorial/request-files.md
|
||||
- tutorial/request-forms-and-files.md
|
||||
- tutorial/encoder.md
|
||||
markdown_extensions:
|
||||
- toc:
|
||||
permalink: true
|
||||
|
||||
128
docs/pt/docs/tutorial/header-params.md
Normal file
128
docs/pt/docs/tutorial/header-params.md
Normal file
@@ -0,0 +1,128 @@
|
||||
# Parâmetros de Cabeçalho
|
||||
|
||||
Você pode definir parâmetros de Cabeçalho da mesma maneira que define paramêtros com `Query`, `Path` e `Cookie`.
|
||||
|
||||
## importe `Header`
|
||||
|
||||
Primeiro importe `Header`:
|
||||
|
||||
=== "Python 3.6 and above"
|
||||
|
||||
```Python hl_lines="3"
|
||||
{!> ../../../docs_src/header_params/tutorial001.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.10 and above"
|
||||
|
||||
```Python hl_lines="1"
|
||||
{!> ../../../docs_src/header_params/tutorial001_py310.py!}
|
||||
```
|
||||
|
||||
## Declare parâmetros de `Header`
|
||||
|
||||
Então declare os paramêtros de cabeçalho usando a mesma estrutura que em `Path`, `Query` e `Cookie`.
|
||||
|
||||
O primeiro valor é o valor padrão, você pode passar todas as validações adicionais ou parâmetros de anotação:
|
||||
|
||||
=== "Python 3.6 and above"
|
||||
|
||||
```Python hl_lines="9"
|
||||
{!> ../../../docs_src/header_params/tutorial001.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.10 and above"
|
||||
|
||||
```Python hl_lines="7"
|
||||
{!> ../../../docs_src/header_params/tutorial001_py310.py!}
|
||||
```
|
||||
|
||||
!!! note "Detalhes Técnicos"
|
||||
`Header` é uma classe "irmã" de `Path`, `Query` e `Cookie`. Ela também herda da mesma classe em comum `Param`.
|
||||
|
||||
Mas lembre-se que quando você importa `Query`, `Path`, `Header`, e outras de `fastapi`, elas são na verdade funções que retornam classes especiais.
|
||||
|
||||
!!! info
|
||||
Para declarar headers, você precisa usar `Header`, caso contrário, os parâmetros seriam interpretados como parâmetros de consulta.
|
||||
|
||||
## Conversão automática
|
||||
|
||||
`Header` tem algumas funcionalidades a mais em relação a `Path`, `Query` e `Cookie`.
|
||||
|
||||
A maioria dos cabeçalhos padrão são separados pelo caractere "hífen", também conhecido como "sinal de menos" (`-`).
|
||||
|
||||
Mas uma variável como `user-agent` é inválida em Python.
|
||||
|
||||
Portanto, por padrão, `Header` converterá os caracteres de nomes de parâmetros de sublinhado (`_`) para hífen (`-`) para extrair e documentar os cabeçalhos.
|
||||
|
||||
Além disso, os cabeçalhos HTTP não diferenciam maiúsculas de minúsculas, portanto, você pode declará-los com o estilo padrão do Python (também conhecido como "snake_case").
|
||||
|
||||
Portanto, você pode usar `user_agent` como faria normalmente no código Python, em vez de precisar colocar as primeiras letras em maiúsculas como `User_Agent` ou algo semelhante.
|
||||
|
||||
Se por algum motivo você precisar desabilitar a conversão automática de sublinhados para hífens, defina o parâmetro `convert_underscores` de `Header` para `False`:
|
||||
|
||||
=== "Python 3.6 and above"
|
||||
|
||||
```Python hl_lines="10"
|
||||
{!> ../../../docs_src/header_params/tutorial002.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.10 and above"
|
||||
|
||||
```Python hl_lines="8"
|
||||
{!> ../../../docs_src/header_params/tutorial002_py310.py!}
|
||||
```
|
||||
|
||||
!!! warning "Aviso"
|
||||
Antes de definir `convert_underscores` como `False`, lembre-se de que alguns proxies e servidores HTTP não permitem o uso de cabeçalhos com sublinhados.
|
||||
|
||||
## Cabeçalhos duplicados
|
||||
|
||||
É possível receber cabeçalhos duplicados. Isso significa, o mesmo cabeçalho com vários valores.
|
||||
|
||||
Você pode definir esses casos usando uma lista na declaração de tipo.
|
||||
|
||||
Você receberá todos os valores do cabeçalho duplicado como uma `list` Python.
|
||||
|
||||
Por exemplo, para declarar um cabeçalho de `X-Token` que pode aparecer mais de uma vez, você pode escrever:
|
||||
|
||||
=== "Python 3.6 and above"
|
||||
|
||||
```Python hl_lines="9"
|
||||
{!> ../../../docs_src/header_params/tutorial003.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.9 and above"
|
||||
|
||||
```Python hl_lines="9"
|
||||
{!> ../../../docs_src/header_params/tutorial003_py39.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.10 and above"
|
||||
|
||||
```Python hl_lines="7"
|
||||
{!> ../../../docs_src/header_params/tutorial003_py310.py!}
|
||||
```
|
||||
|
||||
Se você se comunicar com essa *operação de caminho* enviando dois cabeçalhos HTTP como:
|
||||
|
||||
```
|
||||
X-Token: foo
|
||||
X-Token: bar
|
||||
```
|
||||
|
||||
A resposta seria como:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"X-Token values": [
|
||||
"bar",
|
||||
"foo"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Recapitulando
|
||||
|
||||
Declare cabeçalhos com `Header`, usando o mesmo padrão comum que utiliza-se em `Query`, `Path` e `Cookie`.
|
||||
|
||||
E não se preocupe com sublinhados em suas variáveis, FastAPI cuidará da conversão deles.
|
||||
@@ -70,6 +70,7 @@ nav:
|
||||
- tutorial/extra-data-types.md
|
||||
- tutorial/query-params-str-validations.md
|
||||
- tutorial/cookie-params.md
|
||||
- tutorial/header-params.md
|
||||
- tutorial/handling-errors.md
|
||||
- Segurança:
|
||||
- tutorial/security/index.md
|
||||
|
||||
87
docs/ru/docs/deployment/versions.md
Normal file
87
docs/ru/docs/deployment/versions.md
Normal file
@@ -0,0 +1,87 @@
|
||||
# О версиях FastAPI
|
||||
|
||||
**FastAPI** уже используется в продакшене во многих приложениях и системах. Покрытие тестами поддерживается на уровне 100%. Однако его разработка все еще продолжается.
|
||||
|
||||
Часто добавляются новые функции, регулярно исправляются баги, код продолжает постоянно совершенствоваться.
|
||||
|
||||
По указанным причинам текущие версии до сих пор `0.x.x`. Это говорит о том, что каждая версия может содержать обратно несовместимые изменения, следуя <a href="https://semver.org/" class="external-link" target="_blank">соглашению о Семантическом Версионировании</a>.
|
||||
|
||||
Уже сейчас вы можете создавать приложения в продакшене, используя **FastAPI** (и скорее всего так и делаете), главное убедиться в том, что вы используете версию, которая корректно работает с вашим кодом.
|
||||
|
||||
## Закрепите вашу версию `fastapi`
|
||||
|
||||
Первым делом вам следует "закрепить" конкретную последнюю используемую версию **FastAPI**, которая корректно работает с вашим приложением.
|
||||
|
||||
Например, в своём приложении вы используете версию `0.45.0`.
|
||||
|
||||
Если вы используете файл `requirements.txt`, вы можете указать версию следующим способом:
|
||||
|
||||
```txt
|
||||
fastapi==0.45.0
|
||||
```
|
||||
|
||||
это означает, что вы будете использовать именно версию `0.45.0`.
|
||||
|
||||
Или вы можете закрепить версию следующим способом:
|
||||
|
||||
```txt
|
||||
fastapi>=0.45.0,<0.46.0
|
||||
```
|
||||
|
||||
это значит, что вы используете версии `0.45.0` или выше, но меньше чем `0.46.0`. Например, версия `0.45.2` все еще будет подходить.
|
||||
|
||||
Если вы используете любой другой инструмент для управления зависимостями, например Poetry, Pipenv или др., у них у всех имеется способ определения специфической версии для ваших пакетов.
|
||||
|
||||
## Доступные версии
|
||||
|
||||
Вы можете посмотреть доступные версии (например, проверить последнюю на данный момент) в [примечаниях к выпуску](../release-notes.md){.internal-link target=_blank}.
|
||||
|
||||
## О версиях
|
||||
|
||||
Следуя соглашению о Семантическом Версионировании, любые версии ниже `1.0.0` потенциально могут добавить обратно несовместимые изменения.
|
||||
|
||||
FastAPI следует соглашению в том, что любые изменения "ПАТЧ"-версии предназначены для исправления багов и внесения обратно совместимых изменений.
|
||||
|
||||
!!! Подсказка
|
||||
"ПАТЧ" - это последнее число. Например, в `0.2.3`, ПАТЧ-версия - это `3`.
|
||||
|
||||
Итак, вы можете закрепить версию следующим образом:
|
||||
|
||||
```txt
|
||||
fastapi>=0.45.0,<0.46.0
|
||||
```
|
||||
|
||||
Обратно несовместимые изменения и новые функции добавляются в "МИНОРНЫЕ" версии.
|
||||
|
||||
!!! Подсказка
|
||||
"МИНОРНАЯ" версия - это число в середине. Например, в `0.2.3` МИНОРНАЯ версия - это `2`.
|
||||
|
||||
## Обновление версий FastAPI
|
||||
|
||||
Вам следует добавить тесты для вашего приложения.
|
||||
|
||||
С помощью **FastAPI** это очень просто (благодаря Starlette), см. документацию: [Тестирование](../tutorial/testing.md){.internal-link target=_blank}
|
||||
|
||||
После создания тестов вы можете обновить свою версию **FastAPI** до более новой. После этого следует убедиться, что ваш код работает корректно, запустив тесты.
|
||||
|
||||
Если все работает корректно, или после внесения необходимых изменений все ваши тесты проходят, только тогда вы можете закрепить вашу новую версию `fastapi`.
|
||||
|
||||
## О Starlette
|
||||
|
||||
Не следует закреплять версию `starlette`.
|
||||
|
||||
Разные версии **FastAPI** будут использовать более новые версии Starlette.
|
||||
|
||||
Так что решение об используемой версии Starlette, вы можете оставить **FastAPI**.
|
||||
|
||||
## О Pydantic
|
||||
|
||||
Pydantic включает свои собственные тесты для **FastAPI**, так что новые версии Pydantic (выше `1.0.0`) всегда совместимы с FastAPI.
|
||||
|
||||
Вы можете закрепить любую версию Pydantic, которая вам подходит, выше `1.0.0` и ниже `2.0.0`.
|
||||
|
||||
Например:
|
||||
|
||||
```txt
|
||||
pydantic>=1.2.0,<2.0.0
|
||||
```
|
||||
@@ -1,50 +1,48 @@
|
||||
|
||||
{!../../../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>
|
||||
<em>Готовый к внедрению высокопроизводительный фреймворк, простой в изучении и разработке.</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 href="https://github.com/tiangolo/fastapi/actions?query=workflow%3ATest+event%3Apush+branch%3Amaster" target="_blank">
|
||||
<img src="https://github.com/tiangolo/fastapi/workflows/Test/badge.svg?event=push&branch=master" alt="Test">
|
||||
</a>
|
||||
<a href="https://codecov.io/gh/tiangolo/fastapi" target="_blank">
|
||||
<img src="https://img.shields.io/codecov/c/github/tiangolo/fastapi" alt="Coverage">
|
||||
<img src="https://img.shields.io/codecov/c/github/tiangolo/fastapi?color=%2334D058" alt="Coverage">
|
||||
</a>
|
||||
<a href="https://pypi.org/project/fastapi" target="_blank">
|
||||
<img src="https://badge.fury.io/py/fastapi.svg" alt="Package version">
|
||||
<img src="https://img.shields.io/pypi/v/fastapi?color=%2334D058&label=pypi%20package" alt="Package version">
|
||||
</a>
|
||||
<a href="https://pypi.org/project/fastapi" target="_blank">
|
||||
<img src="https://img.shields.io/pypi/pyversions/fastapi.svg?color=%2334D058" alt="Supported Python versions">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
**Documentation**: <a href="https://fastapi.tiangolo.com" target="_blank">https://fastapi.tiangolo.com</a>
|
||||
**Документация**: <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>
|
||||
**Исходный код**: <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.
|
||||
FastAPI — это современный, быстрый (высокопроизводительный) веб-фреймворк для создания API используя Python 3.6+, в основе которого лежит стандартная аннотация типов Python.
|
||||
|
||||
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).
|
||||
* **Скорость**: Очень высокая производительность, на уровне **NodeJS** и **Go** (благодаря Starlette и Pydantic). [Один из самых быстрых фреймворков Python](#_10).
|
||||
* **Быстрота разработки**: Увеличьте скорость разработки примерно на 200–300%. *
|
||||
* **Меньше ошибок**: Сократите примерно на 40% количество ошибок, вызванных человеком (разработчиком). *
|
||||
* **Интуитивно понятный**: Отличная поддержка редактора. <abbr title="также известное как автозаполнение, автодополнение, IntelliSense">Автозавершение</abbr> везде. Меньше времени на отладку.
|
||||
* **Лёгкость**: Разработан так, чтобы его было легко использовать и осваивать. Меньше времени на чтение документации.
|
||||
* **Краткость**: Сведите к минимуму дублирование кода. Каждый объявленный параметр - определяет несколько функций. Меньше ошибок.
|
||||
* **Надежность**: Получите готовый к работе код. С автоматической интерактивной документацией.
|
||||
* **На основе стандартов**: Основан на открытых стандартах API и полностью совместим с ними: <a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank">OpenAPI</a> (ранее известном как Swagger) и <a href="https://json-schema.org/" class="external-link" target="_blank">JSON Schema</a>.
|
||||
|
||||
* **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="https://json-schema.org/" class="external-link" target="_blank">JSON Schema</a>.
|
||||
<small>* оценка на основе тестов внутренней команды разработчиков, создающих производственные приложения.</small>
|
||||
|
||||
<small>* estimation based on tests on an internal development team, building production applications.</small>
|
||||
|
||||
## Sponsors
|
||||
## Спонсоры
|
||||
|
||||
<!-- sponsors -->
|
||||
|
||||
@@ -59,66 +57,66 @@ The key features are:
|
||||
|
||||
<!-- /sponsors -->
|
||||
|
||||
<a href="https://fastapi.tiangolo.com/fastapi-people/#sponsors" class="external-link" target="_blank">Other sponsors</a>
|
||||
<a href="https://fastapi.tiangolo.com/fastapi-people/#sponsors" class="external-link" target="_blank">Другие спонсоры</a>
|
||||
|
||||
## 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._"
|
||||
"_В последнее время я много где использую **FastAPI**. [...] На самом деле я планирую использовать его для всех **сервисов машинного обучения моей команды в Microsoft**. Некоторые из них интегрируются в основной продукт **Windows**, а некоторые — в продукты **Office**._"
|
||||
|
||||
<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]_"
|
||||
"_Мы использовали библиотеку **FastAPI** для создания сервера **REST**, к которому можно делать запросы для получения **прогнозов**. [для 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**]_"
|
||||
"_**Netflix** рада объявить о выпуске опенсорсного фреймворка для оркестровки **антикризисного управления**: **Dispatch**! [создана с помощью **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!_"
|
||||
"_Я в полном восторге от **FastAPI**. Это так весело!_"
|
||||
|
||||
<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._"
|
||||
"_Честно говоря, то, что вы создали, выглядит очень солидно и отполировано. Во многих смыслах я хотел, чтобы **Hug** был именно таким — это действительно вдохновляет, когда кто-то создаёт такое._"
|
||||
|
||||
<div style="text-align: right; margin-right: 10%;">Timothy Crosley - <strong><a href="https://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 [...]_"
|
||||
"_Если вы хотите изучить какой-нибудь **современный фреймворк** для создания REST API, ознакомьтесь с **FastAPI** [...] Он быстрый, лёгкий и простой в изучении [...]_"
|
||||
|
||||
"_We've switched over to **FastAPI** for our **APIs** [...] I think you'll like it [...]_"
|
||||
"_Мы перешли на **FastAPI** для наших **API** [...] Я думаю, вам тоже понравится [...]_"
|
||||
|
||||
<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
|
||||
## **Typer**, интерфейс командной строки для FastAPI
|
||||
|
||||
<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>.
|
||||
Если вы создаете приложение <abbr title="Интерфейс командной строки">CLI</abbr> для использования в терминале вместо веб-API, ознакомьтесь с <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**. ⌨️ 🚀
|
||||
**Typer** — младший брат FastAPI. И он предназначен для использования в качестве **интерфейса командной строки для FastAPI**. ⌨️ 🚀
|
||||
|
||||
## Requirements
|
||||
## Зависимости
|
||||
|
||||
Python 3.6+
|
||||
|
||||
FastAPI stands on the shoulders of giants:
|
||||
FastAPI стоит на плечах гигантов:
|
||||
|
||||
* <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.
|
||||
* <a href="https://www.starlette.io/" class="external-link" target="_blank">Starlette</a> для части связанной с вебом.
|
||||
* <a href="https://pydantic-docs.helpmanual.io/" class="external-link" target="_blank">Pydantic</a> для части связанной с данными.
|
||||
|
||||
## Installation
|
||||
## Установка
|
||||
|
||||
<div class="termy">
|
||||
|
||||
@@ -130,7 +128,7 @@ $ pip install fastapi
|
||||
|
||||
</div>
|
||||
|
||||
You will also need an ASGI server, for production such as <a href="https://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>.
|
||||
Вам также понадобится сервер ASGI для производства, такой как <a href="https://www.uvicorn.org" class="external-link" target="_blank">Uvicorn</a> или <a href="https://gitlab.com/pgjones/hypercorn" class="external-link" target="_blank">Hypercorn</a>.
|
||||
|
||||
<div class="termy">
|
||||
|
||||
@@ -142,11 +140,11 @@ $ pip install "uvicorn[standard]"
|
||||
|
||||
</div>
|
||||
|
||||
## Example
|
||||
## Пример
|
||||
|
||||
### Create it
|
||||
### Создание
|
||||
|
||||
* Create a file `main.py` with:
|
||||
* Создайте файл `main.py` со следующим содержимым:
|
||||
|
||||
```Python
|
||||
from typing import Union
|
||||
@@ -167,9 +165,9 @@ def read_item(item_id: int, q: Union[str, None] = None):
|
||||
```
|
||||
|
||||
<details markdown="1">
|
||||
<summary>Or use <code>async def</code>...</summary>
|
||||
<summary>Или используйте <code>async def</code>...</summary>
|
||||
|
||||
If your code uses `async` / `await`, use `async def`:
|
||||
Если ваш код использует `async` / `await`, используйте `async def`:
|
||||
|
||||
```Python hl_lines="9 14"
|
||||
from typing import Union
|
||||
@@ -189,15 +187,15 @@ async def read_item(item_id: int, q: Union[str, None] = 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>.
|
||||
Если вы не знаете, проверьте раздел _"Торопитесь?"_ <a href="https://fastapi.tiangolo.com/async/#in-a-hurry" target="_blank">в документации об `async` и `await`</a>.
|
||||
|
||||
</details>
|
||||
|
||||
### Run it
|
||||
### Запуск
|
||||
|
||||
Run the server with:
|
||||
Запустите сервер с помощью:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
@@ -214,54 +212,54 @@ INFO: Application startup complete.
|
||||
</div>
|
||||
|
||||
<details markdown="1">
|
||||
<summary>About the command <code>uvicorn main:app --reload</code>...</summary>
|
||||
<summary>О команде <code>uvicorn main:app --reload</code>...</summary>
|
||||
|
||||
The command `uvicorn main:app` refers to:
|
||||
Команда `uvicorn main:app` относится к:
|
||||
|
||||
* `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.
|
||||
* `main`: файл `main.py` (модуль Python).
|
||||
* `app`: объект, созданный внутри `main.py` с помощью строки `app = FastAPI()`.
|
||||
* `--reload`: перезапуск сервера после изменения кода. Делайте это только во время разработки.
|
||||
|
||||
</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>.
|
||||
Откройте браузер на <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 ответ:
|
||||
|
||||
```JSON
|
||||
{"item_id": 5, "q": "somequery"}
|
||||
```
|
||||
|
||||
You already created an API that:
|
||||
Вы уже создали API, который:
|
||||
|
||||
* 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`.
|
||||
* Получает HTTP-запросы по _путям_ `/` и `/items/{item_id}`.
|
||||
* И первый и второй _путь_ используют `GET` <em>операции</em> (также известные как HTTP _методы_).
|
||||
* _путь_ `/items/{item_id}` имеет _параметр пути_ `item_id`, который должен быть `int`.
|
||||
* _путь_ `/items/{item_id}` имеет необязательный `str` _параметр запроса_ `q`.
|
||||
|
||||
### Interactive API docs
|
||||
### Интерактивная документация по API
|
||||
|
||||
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>.
|
||||
Перейдите на <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>):
|
||||
Вы увидите автоматическую интерактивную документацию API (предоставленную <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank">Swagger UI</a>):
|
||||
|
||||

|
||||
|
||||
### Alternative API docs
|
||||
### Альтернативная документация по API
|
||||
|
||||
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>.
|
||||
А теперь перейдите на <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>):
|
||||
Вы увидите альтернативную автоматическую документацию (предоставленную <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.
|
||||
Теперь измените файл `main.py`, чтобы получить тело ответа из `PUT` запроса.
|
||||
|
||||
Declare the body using standard Python types, thanks to Pydantic.
|
||||
Объявите тело, используя стандартную типизацию Python, спасибо Pydantic.
|
||||
|
||||
```Python hl_lines="4 9-12 25-27"
|
||||
from typing import Union
|
||||
@@ -293,174 +291,173 @@ 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).
|
||||
Сервер должен перезагрузиться автоматически (потому что вы добавили `--reload` к команде `uvicorn` выше).
|
||||
|
||||
### Interactive API docs upgrade
|
||||
### Интерактивное обновление документации API
|
||||
|
||||
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>.
|
||||
Перейдите на <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:
|
||||
* Интерактивная документация API будет автоматически обновляться, включая новое тело:
|
||||
|
||||

|
||||
|
||||
* Click on the button "Try it out", it allows you to fill the parameters and directly interact with the API:
|
||||
* Нажмите на кнопку "Try it out", это позволит вам заполнить параметры и напрямую взаимодействовать с 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:
|
||||
* Затем нажмите кнопку "Execute", пользовательский интерфейс свяжется с вашим API, отправит параметры, получит результаты и отобразит их на экране:
|
||||
|
||||

|
||||
|
||||
### Alternative API docs upgrade
|
||||
### Альтернативное обновление документации API
|
||||
|
||||
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>.
|
||||
А теперь перейдите на <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.
|
||||
Вы делаете это испльзуя стандартную современную типизацию Python.
|
||||
|
||||
You don't have to learn a new syntax, the methods or classes of a specific library, etc.
|
||||
Вам не нужно изучать новый синтаксис, методы или классы конкретной библиотеки и т. д.
|
||||
|
||||
Just standard **Python 3.6+**.
|
||||
Только стандартный **Python 3.6+**.
|
||||
|
||||
For example, for an `int`:
|
||||
Например, для `int`:
|
||||
|
||||
```Python
|
||||
item_id: int
|
||||
```
|
||||
|
||||
or for a more complex `Item` model:
|
||||
или для более сложной модели `Item`:
|
||||
|
||||
```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.
|
||||
* <abbr title="также известный как: сериализация, синтаксический анализ, маршалинг">Преобразование</abbr> входных данных: поступающие из сети в объекты Python с соблюдением типов. Чтение из:
|
||||
* 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:
|
||||
* Заголовков.
|
||||
* Форм.
|
||||
* Файлов.
|
||||
* <abbr title="также известный как: сериализация, синтаксический анализ, маршалинг">Преобразование</abbr> выходных данных: преобразование объектов Python в данные передаваемые по сети интернет (такие как JSON):
|
||||
* Преобразование типов Python (`str`, `int`, `float`, `bool`, `list`, и т.д.).
|
||||
* Объекты `datetime`.
|
||||
* Объекты `UUID`.
|
||||
* Модели баз данных.
|
||||
* ...и многое другое.
|
||||
* Автоматическая интерактивная документация по API, включая 2 альтернативных пользовательских интерфейса:
|
||||
* Swagger UI.
|
||||
* ReDoc.
|
||||
|
||||
---
|
||||
|
||||
Coming back to the previous code example, **FastAPI** will:
|
||||
Возвращаясь к предыдущему примеру кода, **FastAPI** будет:
|
||||
|
||||
* 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.
|
||||
* Проверять наличие `item_id` в пути для запросов `GET` и `PUT`.
|
||||
* Проверять, что `item_id` имеет тип `int` для запросов `GET` и `PUT`.
|
||||
* Если это не так, клиент увидит полезную чёткую ошибку.
|
||||
* Проверять, есть ли необязательный параметр запроса с именем `q` (например, `http://127.0.0.1:8000/items/foo?q=somequery`) для `GET` запросов.
|
||||
* Поскольку параметр `q` объявлен с `= None`, он является необязательным.
|
||||
* Без `None` он был бы необходим (как тело в случае с `PUT`).
|
||||
* Для `PUT` запросов к `/items/{item_id}` читать тело как JSON:
|
||||
* Проверять, что у него есть обязательный атрибут `name`, который должен быть `str`.
|
||||
* Проверять, что у него есть обязательный атрибут `price`, который должен быть `float`.
|
||||
* Проверять, что у него есть необязательный атрибут `is_offer`, который должен быть `bool`, если он присутствует.
|
||||
* Все это также будет работать для глубоко вложенных объектов JSON.
|
||||
* Преобразовывать из и в JSON автоматически.
|
||||
* Документировать с помощью OpenAPI все, что может быть использовано:
|
||||
* Системы интерактивной документации.
|
||||
* Системы автоматической генерации клиентского кода для многих языков.
|
||||
* Обеспечит 2 интерактивных веб-интерфейса документации напрямую.
|
||||
|
||||
---
|
||||
|
||||
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>.
|
||||
Более полный пример с дополнительными функциями см. в <a href="https://fastapi.tiangolo.com/tutorial/">Учебное руководство - Руководство пользователя</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`
|
||||
* Объявление **параметров** из других мест, таких как: **заголовки**, **cookies**, **поля формы** и **файлы**.
|
||||
* Как установить **ограничительные проверки** такие как `maximum_length` или `regex`.
|
||||
* Очень мощная и простая в использовании система **<abbr title="также известная как компоненты, ресурсы, провайдеры, сервисы, инъекции">внедрения зависимостей</abbr>**.
|
||||
* Безопасность и аутентификация, включая поддержку **OAuth2** с **токенами JWT** и **HTTP Basic** аутентификацию.
|
||||
* Более продвинутые (но столь же простые) методы объявления **глубоко вложенных моделей JSON** (спасибо Pydantic).
|
||||
* **GraphQL** интеграция с <a href="https://strawberry.rocks" class="external-link" target="_blank">Strawberry</a> и другими библиотеками.
|
||||
* Множество дополнительных функций (благодаря Starlette), таких как:
|
||||
* **Веб-сокеты**
|
||||
* очень простые тесты на основе `requests` и `pytest`
|
||||
* **CORS**
|
||||
* **Cookie Sessions**
|
||||
* ...and more.
|
||||
* **Cookie сеансы(сессии)**
|
||||
* ...и многое другое.
|
||||
|
||||
## 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). (*)
|
||||
Независимые тесты TechEmpower показывают приложения **FastAPI**, работающие под управлением Uvicorn, как <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">один из самых быстрых доступных фреймворков Python</a>, уступающий только самим Starlette и Uvicorn (используемых внутри FastAPI). (*)
|
||||
|
||||
To understand more about it, see the section <a href="https://fastapi.tiangolo.com/benchmarks/" class="internal-link" target="_blank">Benchmarks</a>.
|
||||
Чтобы узнать больше об этом, см. раздел <a href="https://fastapi.tiangolo.com/benchmarks/" class="internal-link" target="_blank">Тесты производительности</a>.
|
||||
|
||||
## Optional Dependencies
|
||||
## Необязательные зависимости
|
||||
|
||||
Used by Pydantic:
|
||||
Используется 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.
|
||||
* <a href="https://github.com/esnme/ultrajson" target="_blank"><code>ujson</code></a> - для более быстрого JSON <abbr title="преобразования строки, полученной из HTTP-запроса, в данные Python">"парсинга"</abbr>.
|
||||
* <a href="https://github.com/JoshData/python-email-validator" target="_blank"><code>email_validator</code></a> - для проверки электронной почты.
|
||||
|
||||
Used by Starlette:
|
||||
Используется Starlette:
|
||||
|
||||
* <a href="https://requests.readthedocs.io" target="_blank"><code>requests</code></a> - Required if you want to use the `TestClient`.
|
||||
* <a href="https://jinja.palletsprojects.com" 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`.
|
||||
* <a href="https://requests.readthedocs.io" target="_blank"><code>requests</code></a> - Обязательно, если вы хотите использовать `TestClient`.
|
||||
* <a href="https://jinja.palletsprojects.com" target="_blank"><code>jinja2</code></a> - Обязательно, если вы хотите использовать конфигурацию шаблона по умолчанию.
|
||||
* <a href="https://andrew-d.github.io/python-multipart/" target="_blank"><code>python-multipart</code></a> - Обязательно, если вы хотите поддерживать форму <abbr title="преобразование строки, полученной из HTTP-запроса, в данные Python">"парсинга"</abbr> с помощью `request.form()`.
|
||||
* <a href="https://pythonhosted.org/itsdangerous/" target="_blank"><code>itsdangerous</code></a> - Обязательно, для поддержки `SessionMiddleware`.
|
||||
* <a href="https://pyyaml.org/wiki/PyYAMLDocumentation" target="_blank"><code>pyyaml</code></a> - Обязательно, для поддержки `SchemaGenerator` Starlette (возможно, вам это не нужно с FastAPI).
|
||||
* <a href="https://github.com/esnme/ultrajson" target="_blank"><code>ujson</code></a> - Обязательно, если вы хотите использовать `UJSONResponse`.
|
||||
|
||||
Used by FastAPI / Starlette:
|
||||
Используется FastAPI / Starlette:
|
||||
|
||||
* <a href="https://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`.
|
||||
* <a href="https://www.uvicorn.org" target="_blank"><code>uvicorn</code></a> - сервер, который загружает и обслуживает ваше приложение.
|
||||
* <a href="https://github.com/ijl/orjson" target="_blank"><code>orjson</code></a> - Обязательно, если вы хотите использовать `ORJSONResponse`.
|
||||
|
||||
You can install all of these with `pip install fastapi[all]`.
|
||||
Вы можете установить все это с помощью `pip install "fastapi[all]"`.
|
||||
|
||||
## License
|
||||
## Лицензия
|
||||
|
||||
This project is licensed under the terms of the MIT license.
|
||||
Этот проект распространяется на условиях лицензии MIT.
|
||||
|
||||
@@ -63,6 +63,8 @@ nav:
|
||||
- tutorial/background-tasks.md
|
||||
- external-links.md
|
||||
- async.md
|
||||
- Развёртывание:
|
||||
- deployment/versions.md
|
||||
markdown_extensions:
|
||||
- toc:
|
||||
permalink: true
|
||||
|
||||
@@ -19,5 +19,4 @@ app = FastAPI()
|
||||
async def read_item(item_id: str):
|
||||
if item_id == "foo":
|
||||
return {"id": "foo", "value": "there goes my hero"}
|
||||
else:
|
||||
return JSONResponse(status_code=404, content={"message": "Item not found"})
|
||||
return JSONResponse(status_code=404, content={"message": "Item not found"})
|
||||
|
||||
@@ -14,7 +14,7 @@ app = FastAPI()
|
||||
|
||||
@app.get("/models/{model_name}")
|
||||
async def get_model(model_name: ModelName):
|
||||
if model_name == ModelName.alexnet:
|
||||
if model_name is ModelName.alexnet:
|
||||
return {"model_name": model_name, "message": "Deep Learning FTW!"}
|
||||
|
||||
if model_name.value == "lenet":
|
||||
|
||||
@@ -9,9 +9,17 @@ security = HTTPBasic()
|
||||
|
||||
|
||||
def get_current_username(credentials: HTTPBasicCredentials = Depends(security)):
|
||||
correct_username = secrets.compare_digest(credentials.username, "stanleyjobson")
|
||||
correct_password = secrets.compare_digest(credentials.password, "swordfish")
|
||||
if not (correct_username and correct_password):
|
||||
current_username_bytes = credentials.username.encode("utf8")
|
||||
correct_username_bytes = b"stanleyjobson"
|
||||
is_correct_username = secrets.compare_digest(
|
||||
current_username_bytes, correct_username_bytes
|
||||
)
|
||||
current_password_bytes = credentials.password.encode("utf8")
|
||||
correct_password_bytes = b"swordfish"
|
||||
is_correct_password = secrets.compare_digest(
|
||||
current_password_bytes, correct_password_bytes
|
||||
)
|
||||
if not (is_correct_username and is_correct_password):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Incorrect email or password",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
|
||||
|
||||
__version__ = "0.79.1"
|
||||
__version__ = "0.81.0"
|
||||
|
||||
from starlette import status as status
|
||||
|
||||
|
||||
@@ -273,7 +273,7 @@ class FastAPI(Starlette):
|
||||
path: str,
|
||||
endpoint: Callable[..., Coroutine[Any, Any, Response]],
|
||||
*,
|
||||
response_model: Optional[Type[Any]] = None,
|
||||
response_model: Any = None,
|
||||
status_code: Optional[int] = None,
|
||||
tags: Optional[List[Union[str, Enum]]] = None,
|
||||
dependencies: Optional[Sequence[Depends]] = None,
|
||||
@@ -331,7 +331,7 @@ class FastAPI(Starlette):
|
||||
self,
|
||||
path: str,
|
||||
*,
|
||||
response_model: Optional[Type[Any]] = None,
|
||||
response_model: Any = None,
|
||||
status_code: Optional[int] = None,
|
||||
tags: Optional[List[Union[str, Enum]]] = None,
|
||||
dependencies: Optional[Sequence[Depends]] = None,
|
||||
@@ -434,7 +434,7 @@ class FastAPI(Starlette):
|
||||
self,
|
||||
path: str,
|
||||
*,
|
||||
response_model: Optional[Type[Any]] = None,
|
||||
response_model: Any = None,
|
||||
status_code: Optional[int] = None,
|
||||
tags: Optional[List[Union[str, Enum]]] = None,
|
||||
dependencies: Optional[Sequence[Depends]] = None,
|
||||
@@ -489,7 +489,7 @@ class FastAPI(Starlette):
|
||||
self,
|
||||
path: str,
|
||||
*,
|
||||
response_model: Optional[Type[Any]] = None,
|
||||
response_model: Any = None,
|
||||
status_code: Optional[int] = None,
|
||||
tags: Optional[List[Union[str, Enum]]] = None,
|
||||
dependencies: Optional[Sequence[Depends]] = None,
|
||||
@@ -544,7 +544,7 @@ class FastAPI(Starlette):
|
||||
self,
|
||||
path: str,
|
||||
*,
|
||||
response_model: Optional[Type[Any]] = None,
|
||||
response_model: Any = None,
|
||||
status_code: Optional[int] = None,
|
||||
tags: Optional[List[Union[str, Enum]]] = None,
|
||||
dependencies: Optional[Sequence[Depends]] = None,
|
||||
@@ -599,7 +599,7 @@ class FastAPI(Starlette):
|
||||
self,
|
||||
path: str,
|
||||
*,
|
||||
response_model: Optional[Type[Any]] = None,
|
||||
response_model: Any = None,
|
||||
status_code: Optional[int] = None,
|
||||
tags: Optional[List[Union[str, Enum]]] = None,
|
||||
dependencies: Optional[Sequence[Depends]] = None,
|
||||
@@ -654,7 +654,7 @@ class FastAPI(Starlette):
|
||||
self,
|
||||
path: str,
|
||||
*,
|
||||
response_model: Optional[Type[Any]] = None,
|
||||
response_model: Any = None,
|
||||
status_code: Optional[int] = None,
|
||||
tags: Optional[List[Union[str, Enum]]] = None,
|
||||
dependencies: Optional[Sequence[Depends]] = None,
|
||||
@@ -709,7 +709,7 @@ class FastAPI(Starlette):
|
||||
self,
|
||||
path: str,
|
||||
*,
|
||||
response_model: Optional[Type[Any]] = None,
|
||||
response_model: Any = None,
|
||||
status_code: Optional[int] = None,
|
||||
tags: Optional[List[Union[str, Enum]]] = None,
|
||||
dependencies: Optional[Sequence[Depends]] = None,
|
||||
@@ -764,7 +764,7 @@ class FastAPI(Starlette):
|
||||
self,
|
||||
path: str,
|
||||
*,
|
||||
response_model: Optional[Type[Any]] = None,
|
||||
response_model: Any = None,
|
||||
status_code: Optional[int] = None,
|
||||
tags: Optional[List[Union[str, Enum]]] = None,
|
||||
dependencies: Optional[Sequence[Depends]] = None,
|
||||
@@ -819,7 +819,7 @@ class FastAPI(Starlette):
|
||||
self,
|
||||
path: str,
|
||||
*,
|
||||
response_model: Optional[Type[Any]] = None,
|
||||
response_model: Any = None,
|
||||
status_code: Optional[int] = None,
|
||||
tags: Optional[List[Union[str, Enum]]] = None,
|
||||
dependencies: Optional[Sequence[Depends]] = None,
|
||||
|
||||
@@ -34,6 +34,7 @@ from pydantic import BaseModel, create_model
|
||||
from pydantic.error_wrappers import ErrorWrapper
|
||||
from pydantic.errors import MissingError
|
||||
from pydantic.fields import (
|
||||
SHAPE_FROZENSET,
|
||||
SHAPE_LIST,
|
||||
SHAPE_SEQUENCE,
|
||||
SHAPE_SET,
|
||||
@@ -58,6 +59,7 @@ from starlette.websockets import WebSocket
|
||||
sequence_shapes = {
|
||||
SHAPE_LIST,
|
||||
SHAPE_SET,
|
||||
SHAPE_FROZENSET,
|
||||
SHAPE_TUPLE,
|
||||
SHAPE_SEQUENCE,
|
||||
SHAPE_TUPLE_ELLIPSIS,
|
||||
@@ -161,7 +163,6 @@ def get_sub_dependant(
|
||||
)
|
||||
if security_requirement:
|
||||
sub_dependant.security_requirements.append(security_requirement)
|
||||
sub_dependant.security_scopes = security_scopes
|
||||
return sub_dependant
|
||||
|
||||
|
||||
@@ -278,7 +279,13 @@ def get_dependant(
|
||||
path_param_names = get_path_param_names(path)
|
||||
endpoint_signature = get_typed_signature(call)
|
||||
signature_params = endpoint_signature.parameters
|
||||
dependant = Dependant(call=call, name=name, path=path, use_cache=use_cache)
|
||||
dependant = Dependant(
|
||||
call=call,
|
||||
name=name,
|
||||
path=path,
|
||||
security_scopes=security_scopes,
|
||||
use_cache=use_cache,
|
||||
)
|
||||
for param_name, param in signature_params.items():
|
||||
if isinstance(param.default, params.Depends):
|
||||
sub_dependant = get_param_sub_dependant(
|
||||
@@ -295,10 +302,7 @@ def get_dependant(
|
||||
assert is_scalar_field(
|
||||
field=param_field
|
||||
), "Path params must be of one of the supported types"
|
||||
if isinstance(param.default, params.Path):
|
||||
ignore_default = False
|
||||
else:
|
||||
ignore_default = True
|
||||
ignore_default = not isinstance(param.default, params.Path)
|
||||
param_field = get_param_field(
|
||||
param=param,
|
||||
param_name=param_name,
|
||||
@@ -495,7 +499,6 @@ 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,
|
||||
|
||||
@@ -71,7 +71,14 @@ def jsonable_encoder(
|
||||
sqlalchemy_safe=sqlalchemy_safe,
|
||||
)
|
||||
if dataclasses.is_dataclass(obj):
|
||||
return dataclasses.asdict(obj)
|
||||
obj_dict = dataclasses.asdict(obj)
|
||||
return jsonable_encoder(
|
||||
obj_dict,
|
||||
exclude_none=exclude_none,
|
||||
exclude_defaults=exclude_defaults,
|
||||
custom_encoder=custom_encoder,
|
||||
sqlalchemy_safe=sqlalchemy_safe,
|
||||
)
|
||||
if isinstance(obj, Enum):
|
||||
return obj.value
|
||||
if isinstance(obj, PurePath):
|
||||
@@ -137,10 +144,10 @@ def jsonable_encoder(
|
||||
if isinstance(obj, classes_tuple):
|
||||
return encoder(obj)
|
||||
|
||||
errors: List[Exception] = []
|
||||
try:
|
||||
data = dict(obj)
|
||||
except Exception as e:
|
||||
errors: List[Exception] = []
|
||||
errors.append(e)
|
||||
try:
|
||||
data = vars(obj)
|
||||
|
||||
@@ -106,6 +106,9 @@ def get_redoc_html(
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
ReDoc requires Javascript to function. Please enable it to browse the documentation.
|
||||
</noscript>
|
||||
<redoc spec-url="{openapi_url}"></redoc>
|
||||
<script src="{redoc_js_url}"> </script>
|
||||
</body>
|
||||
@@ -115,12 +118,14 @@ def get_redoc_html(
|
||||
|
||||
|
||||
def get_swagger_ui_oauth2_redirect_html() -> HTMLResponse:
|
||||
# copied from https://github.com/swagger-api/swagger-ui/blob/v4.14.0/dist/oauth2-redirect.html
|
||||
html = """
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en-US">
|
||||
<body onload="run()">
|
||||
</body>
|
||||
</html>
|
||||
<head>
|
||||
<title>Swagger UI: OAuth2 Redirect</title>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
'use strict';
|
||||
function run () {
|
||||
@@ -130,31 +135,32 @@ def get_swagger_ui_oauth2_redirect_html() -> HTMLResponse:
|
||||
var isValid, qp, arr;
|
||||
|
||||
if (/code|token|error/.test(window.location.hash)) {
|
||||
qp = window.location.hash.substring(1);
|
||||
qp = window.location.hash.substring(1).replace('?', '&');
|
||||
} else {
|
||||
qp = location.search.substring(1);
|
||||
}
|
||||
|
||||
arr = qp.split("&")
|
||||
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';})
|
||||
arr = qp.split("&");
|
||||
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';});
|
||||
qp = qp ? JSON.parse('{' + arr.join() + '}',
|
||||
function (key, value) {
|
||||
return key === "" ? value : decodeURIComponent(value)
|
||||
return key === "" ? value : decodeURIComponent(value);
|
||||
}
|
||||
) : {}
|
||||
) : {};
|
||||
|
||||
isValid = qp.state === sentState
|
||||
isValid = qp.state === sentState;
|
||||
|
||||
if ((
|
||||
oauth2.auth.schema.get("flow") === "accessCode"||
|
||||
oauth2.auth.schema.get("flow") === "authorizationCode"
|
||||
oauth2.auth.schema.get("flow") === "accessCode" ||
|
||||
oauth2.auth.schema.get("flow") === "authorizationCode" ||
|
||||
oauth2.auth.schema.get("flow") === "authorization_code"
|
||||
) && !oauth2.auth.code) {
|
||||
if (!isValid) {
|
||||
oauth2.errCb({
|
||||
authId: oauth2.auth.name,
|
||||
source: "auth",
|
||||
level: "warning",
|
||||
message: "Authorization may be unsafe, passed state was changed in server Passed state wasn't returned from auth server"
|
||||
message: "Authorization may be unsafe, passed state was changed in server. The passed state wasn't returned from auth server."
|
||||
});
|
||||
}
|
||||
|
||||
@@ -163,7 +169,7 @@ def get_swagger_ui_oauth2_redirect_html() -> HTMLResponse:
|
||||
oauth2.auth.code = qp.code;
|
||||
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
|
||||
} else {
|
||||
let oauthErrorMsg
|
||||
let oauthErrorMsg;
|
||||
if (qp.error) {
|
||||
oauthErrorMsg = "["+qp.error+"]: " +
|
||||
(qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
|
||||
@@ -174,7 +180,7 @@ def get_swagger_ui_oauth2_redirect_html() -> HTMLResponse:
|
||||
authId: oauth2.auth.name,
|
||||
source: "auth",
|
||||
level: "error",
|
||||
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server"
|
||||
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server."
|
||||
});
|
||||
}
|
||||
} else {
|
||||
@@ -182,6 +188,16 @@ def get_swagger_ui_oauth2_redirect_html() -> HTMLResponse:
|
||||
}
|
||||
window.close();
|
||||
}
|
||||
|
||||
if (document.readyState !== 'loading') {
|
||||
run();
|
||||
} else {
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
run();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
return HTMLResponse(content=html)
|
||||
|
||||
@@ -209,7 +209,11 @@ def get_request_handler(
|
||||
else:
|
||||
body = body_bytes
|
||||
except json.JSONDecodeError as e:
|
||||
raise RequestValidationError([ErrorWrapper(e, ("body", e.pos))], body=e.doc)
|
||||
raise RequestValidationError(
|
||||
[ErrorWrapper(e, ("body", e.pos))], body=e.doc
|
||||
) from e
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=400, detail="There was an error parsing the body"
|
||||
@@ -315,7 +319,7 @@ class APIRoute(routing.Route):
|
||||
path: str,
|
||||
endpoint: Callable[..., Any],
|
||||
*,
|
||||
response_model: Optional[Type[Any]] = None,
|
||||
response_model: Any = None,
|
||||
status_code: Optional[int] = None,
|
||||
tags: Optional[List[Union[str, Enum]]] = None,
|
||||
dependencies: Optional[Sequence[params.Depends]] = None,
|
||||
@@ -409,7 +413,7 @@ class APIRoute(routing.Route):
|
||||
self.description = description or inspect.cleandoc(self.endpoint.__doc__ or "")
|
||||
# if a "form feed" character (page break) is found in the description text,
|
||||
# truncate description text to the content preceding the first "form feed"
|
||||
self.description = self.description.split("\f")[0]
|
||||
self.description = self.description.split("\f")[0].strip()
|
||||
response_fields = {}
|
||||
for additional_status_code, response in self.responses.items():
|
||||
assert isinstance(response, dict), "An additional response must be a dict"
|
||||
@@ -511,7 +515,7 @@ class APIRouter(routing.Router):
|
||||
path: str,
|
||||
endpoint: Callable[..., Any],
|
||||
*,
|
||||
response_model: Optional[Type[Any]] = None,
|
||||
response_model: Any = None,
|
||||
status_code: Optional[int] = None,
|
||||
tags: Optional[List[Union[str, Enum]]] = None,
|
||||
dependencies: Optional[Sequence[params.Depends]] = None,
|
||||
@@ -592,7 +596,7 @@ class APIRouter(routing.Router):
|
||||
self,
|
||||
path: str,
|
||||
*,
|
||||
response_model: Optional[Type[Any]] = None,
|
||||
response_model: Any = None,
|
||||
status_code: Optional[int] = None,
|
||||
tags: Optional[List[Union[str, Enum]]] = None,
|
||||
dependencies: Optional[Sequence[params.Depends]] = None,
|
||||
@@ -787,7 +791,7 @@ class APIRouter(routing.Router):
|
||||
self,
|
||||
path: str,
|
||||
*,
|
||||
response_model: Optional[Type[Any]] = None,
|
||||
response_model: Any = None,
|
||||
status_code: Optional[int] = None,
|
||||
tags: Optional[List[Union[str, Enum]]] = None,
|
||||
dependencies: Optional[Sequence[params.Depends]] = None,
|
||||
@@ -843,7 +847,7 @@ class APIRouter(routing.Router):
|
||||
self,
|
||||
path: str,
|
||||
*,
|
||||
response_model: Optional[Type[Any]] = None,
|
||||
response_model: Any = None,
|
||||
status_code: Optional[int] = None,
|
||||
tags: Optional[List[Union[str, Enum]]] = None,
|
||||
dependencies: Optional[Sequence[params.Depends]] = None,
|
||||
@@ -899,7 +903,7 @@ class APIRouter(routing.Router):
|
||||
self,
|
||||
path: str,
|
||||
*,
|
||||
response_model: Optional[Type[Any]] = None,
|
||||
response_model: Any = None,
|
||||
status_code: Optional[int] = None,
|
||||
tags: Optional[List[Union[str, Enum]]] = None,
|
||||
dependencies: Optional[Sequence[params.Depends]] = None,
|
||||
@@ -955,7 +959,7 @@ class APIRouter(routing.Router):
|
||||
self,
|
||||
path: str,
|
||||
*,
|
||||
response_model: Optional[Type[Any]] = None,
|
||||
response_model: Any = None,
|
||||
status_code: Optional[int] = None,
|
||||
tags: Optional[List[Union[str, Enum]]] = None,
|
||||
dependencies: Optional[Sequence[params.Depends]] = None,
|
||||
@@ -1011,7 +1015,7 @@ class APIRouter(routing.Router):
|
||||
self,
|
||||
path: str,
|
||||
*,
|
||||
response_model: Optional[Type[Any]] = None,
|
||||
response_model: Any = None,
|
||||
status_code: Optional[int] = None,
|
||||
tags: Optional[List[Union[str, Enum]]] = None,
|
||||
dependencies: Optional[Sequence[params.Depends]] = None,
|
||||
@@ -1067,7 +1071,7 @@ class APIRouter(routing.Router):
|
||||
self,
|
||||
path: str,
|
||||
*,
|
||||
response_model: Optional[Type[Any]] = None,
|
||||
response_model: Any = None,
|
||||
status_code: Optional[int] = None,
|
||||
tags: Optional[List[Union[str, Enum]]] = None,
|
||||
dependencies: Optional[Sequence[params.Depends]] = None,
|
||||
@@ -1123,7 +1127,7 @@ class APIRouter(routing.Router):
|
||||
self,
|
||||
path: str,
|
||||
*,
|
||||
response_model: Optional[Type[Any]] = None,
|
||||
response_model: Any = None,
|
||||
status_code: Optional[int] = None,
|
||||
tags: Optional[List[Union[str, Enum]]] = None,
|
||||
dependencies: Optional[Sequence[params.Depends]] = None,
|
||||
@@ -1179,7 +1183,7 @@ class APIRouter(routing.Router):
|
||||
self,
|
||||
path: str,
|
||||
*,
|
||||
response_model: Optional[Type[Any]] = None,
|
||||
response_model: Any = None,
|
||||
status_code: Optional[int] = None,
|
||||
tags: Optional[List[Union[str, Enum]]] = None,
|
||||
dependencies: Optional[Sequence[params.Depends]] = None,
|
||||
|
||||
@@ -119,7 +119,7 @@ class OAuth2(SecurityBase):
|
||||
flows: Union[OAuthFlowsModel, Dict[str, Dict[str, Any]]] = OAuthFlowsModel(),
|
||||
scheme_name: Optional[str] = None,
|
||||
description: Optional[str] = None,
|
||||
auto_error: Optional[bool] = True
|
||||
auto_error: bool = True
|
||||
):
|
||||
self.model = OAuth2Model(flows=flows, description=description)
|
||||
self.scheme_name = scheme_name or self.__class__.__name__
|
||||
|
||||
@@ -50,7 +50,7 @@ def create_response_field(
|
||||
type_: Type[Any],
|
||||
class_validators: Optional[Dict[str, Validator]] = None,
|
||||
default: Optional[Any] = None,
|
||||
required: Union[bool, UndefinedType] = False,
|
||||
required: Union[bool, UndefinedType] = True,
|
||||
model_config: Type[BaseConfig] = BaseConfig,
|
||||
field_info: Optional[FieldInfo] = None,
|
||||
alias: Optional[str] = None,
|
||||
@@ -87,7 +87,7 @@ def create_cloned_field(
|
||||
) -> ModelField:
|
||||
# _cloned_types has already cloned types, to support recursive models
|
||||
if cloned_types is None:
|
||||
cloned_types = dict()
|
||||
cloned_types = {}
|
||||
original_type = field.type_
|
||||
if is_dataclass(original_type) and hasattr(original_type, "__pydantic_model__"):
|
||||
original_type = original_type.__pydantic_model__
|
||||
@@ -140,14 +140,14 @@ def generate_operation_id_for_path(
|
||||
stacklevel=2,
|
||||
)
|
||||
operation_id = name + path
|
||||
operation_id = re.sub("[^0-9a-zA-Z_]", "_", operation_id)
|
||||
operation_id = re.sub(r"\W", "_", operation_id)
|
||||
operation_id = operation_id + "_" + method.lower()
|
||||
return operation_id
|
||||
|
||||
|
||||
def generate_unique_id(route: "APIRoute") -> str:
|
||||
operation_id = route.name + route.path_format
|
||||
operation_id = re.sub("[^0-9a-zA-Z_]", "_", operation_id)
|
||||
operation_id = re.sub(r"\W", "_", operation_id)
|
||||
assert route.methods
|
||||
operation_id = operation_id + "_" + list(route.methods)[0].lower()
|
||||
return operation_id
|
||||
@@ -161,6 +161,12 @@ def deep_dict_update(main_dict: Dict[Any, Any], update_dict: Dict[Any, Any]) ->
|
||||
and isinstance(value, dict)
|
||||
):
|
||||
deep_dict_update(main_dict[key], value)
|
||||
elif (
|
||||
key in main_dict
|
||||
and isinstance(main_dict[key], list)
|
||||
and isinstance(update_dict[key], list)
|
||||
):
|
||||
main_dict[key] = main_dict[key] + update_dict[key]
|
||||
else:
|
||||
main_dict[key] = value
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ test = [
|
||||
"pytest >=6.2.4,<7.0.0",
|
||||
"pytest-cov >=2.12.0,<4.0.0",
|
||||
"mypy ==0.910",
|
||||
"flake8 >=3.8.3,<4.0.0",
|
||||
"flake8 >=3.8.3,<6.0.0",
|
||||
"black == 22.3.0",
|
||||
"isort >=5.0.6,<6.0.0",
|
||||
"requests >=2.24.0,<3.0.0",
|
||||
@@ -83,7 +83,7 @@ dev = [
|
||||
"python-jose[cryptography] >=3.3.0,<4.0.0",
|
||||
"passlib[bcrypt] >=1.7.2,<2.0.0",
|
||||
"autoflake >=1.4.0,<2.0.0",
|
||||
"flake8 >=3.8.3,<4.0.0",
|
||||
"flake8 >=3.8.3,<6.0.0",
|
||||
"uvicorn[standard] >=0.12.0,<0.18.0",
|
||||
"pre-commit >=2.17.0,<3.0.0",
|
||||
]
|
||||
@@ -104,21 +104,7 @@ profile = "black"
|
||||
known_third_party = ["fastapi", "pydantic", "starlette"]
|
||||
|
||||
[tool.mypy]
|
||||
# --strict
|
||||
disallow_any_generics = true
|
||||
disallow_subclassing_any = true
|
||||
disallow_untyped_calls = true
|
||||
disallow_untyped_defs = true
|
||||
disallow_incomplete_defs = true
|
||||
check_untyped_defs = true
|
||||
disallow_untyped_decorators = true
|
||||
no_implicit_optional = true
|
||||
warn_redundant_casts = true
|
||||
warn_unused_ignores = true
|
||||
warn_return_any = true
|
||||
implicit_reexport = false
|
||||
strict_equality = true
|
||||
# --strict end
|
||||
strict = true
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = "fastapi.concurrency"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import http
|
||||
from typing import Optional
|
||||
from typing import FrozenSet, Optional
|
||||
|
||||
from fastapi import FastAPI, Path, Query
|
||||
|
||||
@@ -192,3 +192,8 @@ def get_query_param_required_type(query: int = Query()):
|
||||
@app.get("/enum-status-code", status_code=http.HTTPStatus.CREATED)
|
||||
def get_enum_status_code():
|
||||
return "foo bar"
|
||||
|
||||
|
||||
@app.get("/query/frozenset")
|
||||
def get_query_type_frozenset(query: FrozenSet[int] = Query(...)):
|
||||
return ",".join(map(str, sorted(query)))
|
||||
|
||||
@@ -1090,6 +1090,41 @@ openapi_schema = {
|
||||
"operationId": "get_enum_status_code_enum_status_code_get",
|
||||
}
|
||||
},
|
||||
"/query/frozenset": {
|
||||
"get": {
|
||||
"summary": "Get Query Type Frozenset",
|
||||
"operationId": "get_query_type_frozenset_query_frozenset_get",
|
||||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {
|
||||
"title": "Query",
|
||||
"uniqueItems": True,
|
||||
"type": "array",
|
||||
"items": {"type": "integer"},
|
||||
},
|
||||
"name": "query",
|
||||
"in": "query",
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
|
||||
95
tests/test_custom_middleware_exception.py
Normal file
95
tests/test_custom_middleware_exception.py
Normal file
@@ -0,0 +1,95 @@
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, FastAPI, File, UploadFile
|
||||
from fastapi.exceptions import HTTPException
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
class ContentSizeLimitMiddleware:
|
||||
"""Content size limiting middleware for ASGI applications
|
||||
Args:
|
||||
app (ASGI application): ASGI application
|
||||
max_content_size (optional): the maximum content size allowed in bytes, None for no limit
|
||||
"""
|
||||
|
||||
def __init__(self, app: APIRouter, max_content_size: Optional[int] = None):
|
||||
self.app = app
|
||||
self.max_content_size = max_content_size
|
||||
|
||||
def receive_wrapper(self, receive):
|
||||
received = 0
|
||||
|
||||
async def inner():
|
||||
nonlocal received
|
||||
message = await receive()
|
||||
if message["type"] != "http.request":
|
||||
return message # pragma: no cover
|
||||
|
||||
body_len = len(message.get("body", b""))
|
||||
received += body_len
|
||||
if received > self.max_content_size:
|
||||
raise HTTPException(
|
||||
422,
|
||||
detail={
|
||||
"name": "ContentSizeLimitExceeded",
|
||||
"code": 999,
|
||||
"message": "File limit exceeded",
|
||||
},
|
||||
)
|
||||
return message
|
||||
|
||||
return inner
|
||||
|
||||
async def __call__(self, scope, receive, send):
|
||||
if scope["type"] != "http" or self.max_content_size is None:
|
||||
await self.app(scope, receive, send)
|
||||
return
|
||||
|
||||
wrapper = self.receive_wrapper(receive)
|
||||
await self.app(scope, wrapper, send)
|
||||
|
||||
|
||||
@router.post("/middleware")
|
||||
def run_middleware(file: UploadFile = File(..., description="Big File")):
|
||||
return {"message": "OK"}
|
||||
|
||||
|
||||
app.include_router(router)
|
||||
app.add_middleware(ContentSizeLimitMiddleware, max_content_size=2**8)
|
||||
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
def test_custom_middleware_exception(tmp_path: Path):
|
||||
default_pydantic_max_size = 2**16
|
||||
path = tmp_path / "test.txt"
|
||||
path.write_bytes(b"x" * (default_pydantic_max_size + 1))
|
||||
|
||||
with client:
|
||||
with open(path, "rb") as file:
|
||||
response = client.post("/middleware", files={"file": file})
|
||||
assert response.status_code == 422, response.text
|
||||
assert response.json() == {
|
||||
"detail": {
|
||||
"name": "ContentSizeLimitExceeded",
|
||||
"code": 999,
|
||||
"message": "File limit exceeded",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def test_custom_middleware_exception_not_raised(tmp_path: Path):
|
||||
path = tmp_path / "test.txt"
|
||||
path.write_bytes(b"<file content>")
|
||||
|
||||
with client:
|
||||
with open(path, "rb") as file:
|
||||
response = client.post("/middleware", files={"file": file})
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {"message": "OK"}
|
||||
@@ -1,4 +1,4 @@
|
||||
from fastapi import Depends, FastAPI
|
||||
from fastapi import Depends, FastAPI, Security
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
app = FastAPI()
|
||||
@@ -35,6 +35,19 @@ async def get_sub_counter_no_cache(
|
||||
return {"counter": count, "subcounter": subcount}
|
||||
|
||||
|
||||
@app.get("/scope-counter")
|
||||
async def get_scope_counter(
|
||||
count: int = Security(dep_counter),
|
||||
scope_count_1: int = Security(dep_counter, scopes=["scope"]),
|
||||
scope_count_2: int = Security(dep_counter, scopes=["scope"]),
|
||||
):
|
||||
return {
|
||||
"counter": count,
|
||||
"scope_counter_1": scope_count_1,
|
||||
"scope_counter_2": scope_count_2,
|
||||
}
|
||||
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
@@ -66,3 +79,13 @@ def test_sub_counter_no_cache():
|
||||
response = client.get("/sub-counter-no-cache/")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {"counter": 4, "subcounter": 3}
|
||||
|
||||
|
||||
def test_security_cache():
|
||||
counter_holder["counter"] = 0
|
||||
response = client.get("/scope-counter/")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {"counter": 1, "scope_counter_1": 2, "scope_counter_2": 2}
|
||||
response = client.get("/scope-counter/")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {"counter": 3, "scope_counter_1": 4, "scope_counter_2": 4}
|
||||
|
||||
127
tests/test_openapi_query_parameter_extension.py
Normal file
127
tests/test_openapi_query_parameter_extension.py
Normal file
@@ -0,0 +1,127 @@
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.get(
|
||||
"/",
|
||||
openapi_extra={
|
||||
"parameters": [
|
||||
{
|
||||
"required": False,
|
||||
"schema": {"title": "Extra Param 1"},
|
||||
"name": "extra_param_1",
|
||||
"in": "query",
|
||||
},
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "Extra Param 2"},
|
||||
"name": "extra_param_2",
|
||||
"in": "query",
|
||||
},
|
||||
]
|
||||
},
|
||||
)
|
||||
def route_with_extra_query_parameters(standard_query_param: Optional[int] = 50):
|
||||
return {}
|
||||
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
openapi_schema = {
|
||||
"openapi": "3.0.2",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/": {
|
||||
"get": {
|
||||
"summary": "Route With Extra Query Parameters",
|
||||
"operationId": "route_with_extra_query_parameters__get",
|
||||
"parameters": [
|
||||
{
|
||||
"required": False,
|
||||
"schema": {
|
||||
"title": "Standard Query Param",
|
||||
"type": "integer",
|
||||
"default": 50,
|
||||
},
|
||||
"name": "standard_query_param",
|
||||
"in": "query",
|
||||
},
|
||||
{
|
||||
"required": False,
|
||||
"schema": {"title": "Extra Param 1"},
|
||||
"name": "extra_param_1",
|
||||
"in": "query",
|
||||
},
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "Extra Param 2"},
|
||||
"name": "extra_param_2",
|
||||
"in": "query",
|
||||
},
|
||||
],
|
||||
"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": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
|
||||
},
|
||||
"msg": {"title": "Message", "type": "string"},
|
||||
"type": {"title": "Error Type", "type": "string"},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def test_openapi():
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == openapi_schema
|
||||
|
||||
|
||||
def test_get_route():
|
||||
response = client.get("/")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {}
|
||||
@@ -53,6 +53,7 @@ response_not_valid_int = {
|
||||
("/query/param-required/int", 422, response_missing),
|
||||
("/query/param-required/int?query=50", 200, "foo bar 50"),
|
||||
("/query/param-required/int?query=foo", 422, response_not_valid_int),
|
||||
("/query/frozenset/?query=1&query=1&query=2", 200, "1,2"),
|
||||
],
|
||||
)
|
||||
def test_get_path(path, expected_status, expected_response):
|
||||
|
||||
@@ -149,7 +149,7 @@ def query_example_examples(
|
||||
default=None,
|
||||
example="query_overriden",
|
||||
examples={
|
||||
"example1": {"summary": "Qeury example 1", "value": "query1"},
|
||||
"example1": {"summary": "Query example 1", "value": "query1"},
|
||||
"example2": {"value": "query2"},
|
||||
},
|
||||
),
|
||||
@@ -186,7 +186,7 @@ def header_example_examples(
|
||||
default=None,
|
||||
example="header_overriden",
|
||||
examples={
|
||||
"example1": {"summary": "Qeury example 1", "value": "header1"},
|
||||
"example1": {"summary": "Query example 1", "value": "header1"},
|
||||
"example2": {"value": "header2"},
|
||||
},
|
||||
),
|
||||
@@ -223,7 +223,7 @@ def cookie_example_examples(
|
||||
default=None,
|
||||
example="cookie_overriden",
|
||||
examples={
|
||||
"example1": {"summary": "Qeury example 1", "value": "cookie1"},
|
||||
"example1": {"summary": "Query example 1", "value": "cookie1"},
|
||||
"example2": {"value": "cookie2"},
|
||||
},
|
||||
),
|
||||
@@ -561,7 +561,7 @@ openapi_schema = {
|
||||
"schema": {"title": "Data", "type": "string"},
|
||||
"examples": {
|
||||
"example1": {
|
||||
"summary": "Qeury example 1",
|
||||
"summary": "Query example 1",
|
||||
"value": "query1",
|
||||
},
|
||||
"example2": {"value": "query2"},
|
||||
@@ -666,7 +666,7 @@ openapi_schema = {
|
||||
"schema": {"title": "Data", "type": "string"},
|
||||
"examples": {
|
||||
"example1": {
|
||||
"summary": "Qeury example 1",
|
||||
"summary": "Query example 1",
|
||||
"value": "header1",
|
||||
},
|
||||
"example2": {"value": "header2"},
|
||||
@@ -771,7 +771,7 @@ openapi_schema = {
|
||||
"schema": {"title": "Data", "type": "string"},
|
||||
"examples": {
|
||||
"example1": {
|
||||
"summary": "Qeury example 1",
|
||||
"summary": "Query example 1",
|
||||
"value": "cookie1",
|
||||
},
|
||||
"example2": {"value": "cookie2"},
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from typing import List, Optional
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
from pydantic.dataclasses import dataclass
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
@@ -10,54 +11,64 @@ app = FastAPI()
|
||||
@dataclass
|
||||
class Item:
|
||||
name: str
|
||||
date: datetime
|
||||
price: Optional[float] = None
|
||||
owner_ids: Optional[List[int]] = None
|
||||
|
||||
|
||||
@app.get("/items/valid", response_model=Item)
|
||||
def get_valid():
|
||||
return {"name": "valid", "price": 1.0}
|
||||
return {"name": "valid", "date": datetime(2021, 7, 26), "price": 1.0}
|
||||
|
||||
|
||||
@app.get("/items/object", response_model=Item)
|
||||
def get_object():
|
||||
return Item(name="object", price=1.0, owner_ids=[1, 2, 3])
|
||||
return Item(
|
||||
name="object", date=datetime(2021, 7, 26), price=1.0, owner_ids=[1, 2, 3]
|
||||
)
|
||||
|
||||
|
||||
@app.get("/items/coerce", response_model=Item)
|
||||
def get_coerce():
|
||||
return {"name": "coerce", "price": "1.0"}
|
||||
return {"name": "coerce", "date": datetime(2021, 7, 26).isoformat(), "price": "1.0"}
|
||||
|
||||
|
||||
@app.get("/items/validlist", response_model=List[Item])
|
||||
def get_validlist():
|
||||
return [
|
||||
{"name": "foo"},
|
||||
{"name": "bar", "price": 1.0},
|
||||
{"name": "baz", "price": 2.0, "owner_ids": [1, 2, 3]},
|
||||
{"name": "foo", "date": datetime(2021, 7, 26)},
|
||||
{"name": "bar", "date": datetime(2021, 7, 26), "price": 1.0},
|
||||
{
|
||||
"name": "baz",
|
||||
"date": datetime(2021, 7, 26),
|
||||
"price": 2.0,
|
||||
"owner_ids": [1, 2, 3],
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@app.get("/items/objectlist", response_model=List[Item])
|
||||
def get_objectlist():
|
||||
return [
|
||||
Item(name="foo"),
|
||||
Item(name="bar", price=1.0),
|
||||
Item(name="baz", price=2.0, owner_ids=[1, 2, 3]),
|
||||
Item(name="foo", date=datetime(2021, 7, 26)),
|
||||
Item(name="bar", date=datetime(2021, 7, 26), price=1.0),
|
||||
Item(name="baz", date=datetime(2021, 7, 26), price=2.0, owner_ids=[1, 2, 3]),
|
||||
]
|
||||
|
||||
|
||||
@app.get("/items/no-response-model/object")
|
||||
def get_no_response_model_object():
|
||||
return Item(name="object", price=1.0, owner_ids=[1, 2, 3])
|
||||
return Item(
|
||||
name="object", date=datetime(2021, 7, 26), price=1.0, owner_ids=[1, 2, 3]
|
||||
)
|
||||
|
||||
|
||||
@app.get("/items/no-response-model/objectlist")
|
||||
def get_no_response_model_objectlist():
|
||||
return [
|
||||
Item(name="foo"),
|
||||
Item(name="bar", price=1.0),
|
||||
Item(name="baz", price=2.0, owner_ids=[1, 2, 3]),
|
||||
Item(name="foo", date=datetime(2021, 7, 26)),
|
||||
Item(name="bar", date=datetime(2021, 7, 26), price=1.0),
|
||||
Item(name="baz", date=datetime(2021, 7, 26), price=2.0, owner_ids=[1, 2, 3]),
|
||||
]
|
||||
|
||||
|
||||
@@ -67,28 +78,58 @@ client = TestClient(app)
|
||||
def test_valid():
|
||||
response = client.get("/items/valid")
|
||||
response.raise_for_status()
|
||||
assert response.json() == {"name": "valid", "price": 1.0, "owner_ids": None}
|
||||
assert response.json() == {
|
||||
"name": "valid",
|
||||
"date": datetime(2021, 7, 26).isoformat(),
|
||||
"price": 1.0,
|
||||
"owner_ids": None,
|
||||
}
|
||||
|
||||
|
||||
def test_object():
|
||||
response = client.get("/items/object")
|
||||
response.raise_for_status()
|
||||
assert response.json() == {"name": "object", "price": 1.0, "owner_ids": [1, 2, 3]}
|
||||
assert response.json() == {
|
||||
"name": "object",
|
||||
"date": datetime(2021, 7, 26).isoformat(),
|
||||
"price": 1.0,
|
||||
"owner_ids": [1, 2, 3],
|
||||
}
|
||||
|
||||
|
||||
def test_coerce():
|
||||
response = client.get("/items/coerce")
|
||||
response.raise_for_status()
|
||||
assert response.json() == {"name": "coerce", "price": 1.0, "owner_ids": None}
|
||||
assert response.json() == {
|
||||
"name": "coerce",
|
||||
"date": datetime(2021, 7, 26).isoformat(),
|
||||
"price": 1.0,
|
||||
"owner_ids": None,
|
||||
}
|
||||
|
||||
|
||||
def test_validlist():
|
||||
response = client.get("/items/validlist")
|
||||
response.raise_for_status()
|
||||
assert response.json() == [
|
||||
{"name": "foo", "price": None, "owner_ids": None},
|
||||
{"name": "bar", "price": 1.0, "owner_ids": None},
|
||||
{"name": "baz", "price": 2.0, "owner_ids": [1, 2, 3]},
|
||||
{
|
||||
"name": "foo",
|
||||
"date": datetime(2021, 7, 26).isoformat(),
|
||||
"price": None,
|
||||
"owner_ids": None,
|
||||
},
|
||||
{
|
||||
"name": "bar",
|
||||
"date": datetime(2021, 7, 26).isoformat(),
|
||||
"price": 1.0,
|
||||
"owner_ids": None,
|
||||
},
|
||||
{
|
||||
"name": "baz",
|
||||
"date": datetime(2021, 7, 26).isoformat(),
|
||||
"price": 2.0,
|
||||
"owner_ids": [1, 2, 3],
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@@ -96,23 +137,58 @@ def test_objectlist():
|
||||
response = client.get("/items/objectlist")
|
||||
response.raise_for_status()
|
||||
assert response.json() == [
|
||||
{"name": "foo", "price": None, "owner_ids": None},
|
||||
{"name": "bar", "price": 1.0, "owner_ids": None},
|
||||
{"name": "baz", "price": 2.0, "owner_ids": [1, 2, 3]},
|
||||
{
|
||||
"name": "foo",
|
||||
"date": datetime(2021, 7, 26).isoformat(),
|
||||
"price": None,
|
||||
"owner_ids": None,
|
||||
},
|
||||
{
|
||||
"name": "bar",
|
||||
"date": datetime(2021, 7, 26).isoformat(),
|
||||
"price": 1.0,
|
||||
"owner_ids": None,
|
||||
},
|
||||
{
|
||||
"name": "baz",
|
||||
"date": datetime(2021, 7, 26).isoformat(),
|
||||
"price": 2.0,
|
||||
"owner_ids": [1, 2, 3],
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def test_no_response_model_object():
|
||||
response = client.get("/items/no-response-model/object")
|
||||
response.raise_for_status()
|
||||
assert response.json() == {"name": "object", "price": 1.0, "owner_ids": [1, 2, 3]}
|
||||
assert response.json() == {
|
||||
"name": "object",
|
||||
"date": datetime(2021, 7, 26).isoformat(),
|
||||
"price": 1.0,
|
||||
"owner_ids": [1, 2, 3],
|
||||
}
|
||||
|
||||
|
||||
def test_no_response_model_objectlist():
|
||||
response = client.get("/items/no-response-model/objectlist")
|
||||
response.raise_for_status()
|
||||
assert response.json() == [
|
||||
{"name": "foo", "price": None, "owner_ids": None},
|
||||
{"name": "bar", "price": 1.0, "owner_ids": None},
|
||||
{"name": "baz", "price": 2.0, "owner_ids": [1, 2, 3]},
|
||||
{
|
||||
"name": "foo",
|
||||
"date": datetime(2021, 7, 26).isoformat(),
|
||||
"price": None,
|
||||
"owner_ids": None,
|
||||
},
|
||||
{
|
||||
"name": "bar",
|
||||
"date": datetime(2021, 7, 26).isoformat(),
|
||||
"price": 1.0,
|
||||
"owner_ids": None,
|
||||
},
|
||||
{
|
||||
"name": "baz",
|
||||
"date": datetime(2021, 7, 26).isoformat(),
|
||||
"price": 2.0,
|
||||
"owner_ids": [1, 2, 3],
|
||||
},
|
||||
]
|
||||
|
||||
@@ -31,7 +31,7 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
"summary": "Create an item",
|
||||
"description": "Create an item with all the information:\n\n- **name**: each item must have a name\n- **description**: a long description\n- **price**: required\n- **tax**: if the item doesn't have tax, you can omit this\n- **tags**: a set of unique tag strings for this item\n",
|
||||
"description": "Create an item with all the information:\n\n- **name**: each item must have a name\n- **description**: a long description\n- **price**: required\n- **tax**: if the item doesn't have tax, you can omit this\n- **tags**: a set of unique tag strings for this item",
|
||||
"operationId": "create_item_items__post",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
|
||||
@@ -356,12 +356,6 @@ def test_create_item(client):
|
||||
item_to_check = [it for it in user_data["items"] if it["id"] == item_data["id"]][0]
|
||||
assert item_to_check["title"] == item["title"]
|
||||
assert item_to_check["description"] == item["description"]
|
||||
response = client.get("/users/1")
|
||||
assert response.status_code == 200, response.text
|
||||
user_data = response.json()
|
||||
item_to_check = [it for it in user_data["items"] if it["id"] == item_data["id"]][0]
|
||||
assert item_to_check["title"] == item["title"]
|
||||
assert item_to_check["description"] == item["description"]
|
||||
|
||||
|
||||
def test_read_items(client):
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import List, Optional
|
||||
from typing import List, Optional, Union
|
||||
|
||||
import pytest
|
||||
from fastapi import FastAPI
|
||||
@@ -19,6 +19,19 @@ def get_invalid():
|
||||
return {"name": "invalid", "price": "foo"}
|
||||
|
||||
|
||||
@app.get("/items/invalidnone", response_model=Item)
|
||||
def get_invalid_none():
|
||||
return None
|
||||
|
||||
|
||||
@app.get("/items/validnone", response_model=Union[Item, None])
|
||||
def get_valid_none(send_none: bool = False):
|
||||
if send_none:
|
||||
return None
|
||||
else:
|
||||
return {"name": "invalid", "price": 3.2}
|
||||
|
||||
|
||||
@app.get("/items/innerinvalid", response_model=Item)
|
||||
def get_innerinvalid():
|
||||
return {"name": "double invalid", "price": "foo", "owner_ids": ["foo", "bar"]}
|
||||
@@ -41,6 +54,25 @@ def test_invalid():
|
||||
client.get("/items/invalid")
|
||||
|
||||
|
||||
def test_invalid_none():
|
||||
with pytest.raises(ValidationError):
|
||||
client.get("/items/invalidnone")
|
||||
|
||||
|
||||
def test_valid_none_data():
|
||||
response = client.get("/items/validnone")
|
||||
data = response.json()
|
||||
assert response.status_code == 200
|
||||
assert data == {"name": "invalid", "price": 3.2, "owner_ids": None}
|
||||
|
||||
|
||||
def test_valid_none_none():
|
||||
response = client.get("/items/validnone", params={"send_none": "true"})
|
||||
data = response.json()
|
||||
assert response.status_code == 200
|
||||
assert data is None
|
||||
|
||||
|
||||
def test_double_invalid():
|
||||
with pytest.raises(ValidationError):
|
||||
client.get("/items/innerinvalid")
|
||||
|
||||
Reference in New Issue
Block a user