mirror of
https://github.com/fastapi/fastapi.git
synced 2025-12-24 06:39:31 -05:00
Compare commits
250 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c7c69586ae | ||
|
|
4e09feda9e | ||
|
|
73260971b5 | ||
|
|
b36bfff56e | ||
|
|
83d04df8a6 | ||
|
|
7bc78c5fd3 | ||
|
|
ae8fa3aacd | ||
|
|
08bc120771 | ||
|
|
a39efb029f | ||
|
|
58ca98285f | ||
|
|
3f5f81bbdc | ||
|
|
90236c8135 | ||
|
|
c200bc2240 | ||
|
|
e9861cd918 | ||
|
|
202fa11d50 | ||
|
|
4b6e09296c | ||
|
|
9bd0d6fa96 | ||
|
|
35510a5ea7 | ||
|
|
c1788a25c7 | ||
|
|
19c77e35bd | ||
|
|
cc4c13e4ae | ||
|
|
4f3764faa9 | ||
|
|
c27ad0dc26 | ||
|
|
6d0caf7522 | ||
|
|
06df32e84c | ||
|
|
28c089c029 | ||
|
|
44b26bb64c | ||
|
|
e04bae2286 | ||
|
|
23f5940e8b | ||
|
|
4915cf0561 | ||
|
|
a1c9eff041 | ||
|
|
57cb3f3089 | ||
|
|
bd6b3b07c5 | ||
|
|
3cf8b86dc1 | ||
|
|
55165f292a | ||
|
|
f3ddc7bdeb | ||
|
|
4356cc9588 | ||
|
|
cd9e87e60e | ||
|
|
4834d87dcd | ||
|
|
7781cc0936 | ||
|
|
23459d4a35 | ||
|
|
ab2b86fe2c | ||
|
|
90a5796b94 | ||
|
|
bb8a630fc3 | ||
|
|
f5a503afae | ||
|
|
49fba853c2 | ||
|
|
bac2f587b7 | ||
|
|
e1fd6785aa | ||
|
|
4e50f53459 | ||
|
|
933d4327fb | ||
|
|
c7902dd23a | ||
|
|
c5f5e63810 | ||
|
|
c3cc077fa9 | ||
|
|
c6f98c009f | ||
|
|
e4206772cb | ||
|
|
723ef07ccf | ||
|
|
8609beb9ab | ||
|
|
65536cbf63 | ||
|
|
0192eab557 | ||
|
|
3f9f4a0f8f | ||
|
|
380e3731a8 | ||
|
|
d6d99b86cb | ||
|
|
5592fa0f6f | ||
|
|
b65be5d496 | ||
|
|
6c7da43e51 | ||
|
|
dfec2d7644 | ||
|
|
8c3ef76139 | ||
|
|
7a504a721c | ||
|
|
dd963511d6 | ||
|
|
fdb6d43e10 | ||
|
|
a7c718e968 | ||
|
|
f4d753620b | ||
|
|
fadfe4c586 | ||
|
|
5fd83c5fa4 | ||
|
|
14daaf409f | ||
|
|
c7dc26b760 | ||
|
|
f5ccb3c35d | ||
|
|
4cea311e6e | ||
|
|
f8718072a0 | ||
|
|
3dbbecdd16 | ||
|
|
6d5530ec1c | ||
|
|
0761f11d1a | ||
|
|
f2e7ef7056 | ||
|
|
d5d9a20937 | ||
|
|
96f092179f | ||
|
|
8505b716af | ||
|
|
78272ac1f3 | ||
|
|
f1bee9a271 | ||
|
|
b20b2218cd | ||
|
|
b9cf69cd42 | ||
|
|
f803c77515 | ||
|
|
0c67022048 | ||
|
|
d8fe307d61 | ||
|
|
580cf8f4e2 | ||
|
|
af390af77c | ||
|
|
4642f63a1e | ||
|
|
203e10596f | ||
|
|
5a2278d09a | ||
|
|
47a8387a04 | ||
|
|
27ca0c9dca | ||
|
|
9418d78de6 | ||
|
|
4b74aef429 | ||
|
|
fc7d123347 | ||
|
|
53da56146e | ||
|
|
3799b9027e | ||
|
|
c70f3f1198 | ||
|
|
58dddc5e4f | ||
|
|
c90c4fb6c1 | ||
|
|
5b3df28f0c | ||
|
|
6c6bdb6233 | ||
|
|
f156f45193 | ||
|
|
f24d744a3b | ||
|
|
937b462cdd | ||
|
|
3025a368c6 | ||
|
|
c218e0d560 | ||
|
|
1ed5aa23e6 | ||
|
|
106d2171d8 | ||
|
|
c5817912d2 | ||
|
|
a7a92bc637 | ||
|
|
68d1fea961 | ||
|
|
8c6b2d5804 | ||
|
|
19c53b21c1 | ||
|
|
44d63cd555 | ||
|
|
55c4b5fb0b | ||
|
|
c32e800c23 | ||
|
|
73dbbeab55 | ||
|
|
417a3ab140 | ||
|
|
a3235ed8de | ||
|
|
38495fffa5 | ||
|
|
b77a43bcac | ||
|
|
483eb73b26 | ||
|
|
51a928d3f5 | ||
|
|
e71636e381 | ||
|
|
f7f17fcfd6 | ||
|
|
033bc2a6c9 | ||
|
|
28d3b9f783 | ||
|
|
0c55553328 | ||
|
|
b66056aa34 | ||
|
|
4f10b8b98d | ||
|
|
06eb421934 | ||
|
|
bf229ad5d8 | ||
|
|
d0319001be | ||
|
|
c4682af13d | ||
|
|
6ca3ce80e4 | ||
|
|
25e85c8522 | ||
|
|
6bf3ab3b7a | ||
|
|
f5ea5eef2a | ||
|
|
46a986cacf | ||
|
|
e620aeb46d | ||
|
|
d1e2e46b80 | ||
|
|
b1c4a8acd5 | ||
|
|
362e2cdc79 | ||
|
|
93e6a08acd | ||
|
|
3ec4342282 | ||
|
|
dc483478eb | ||
|
|
bdd251a05b | ||
|
|
195559ccba | ||
|
|
9a71672a95 | ||
|
|
7e48be1561 | ||
|
|
508f9ce954 | ||
|
|
afbdf2546f | ||
|
|
62df417807 | ||
|
|
09d2747a70 | ||
|
|
d3ea6f7514 | ||
|
|
02187636ea | ||
|
|
687065509b | ||
|
|
b30cca8e9e | ||
|
|
3906777065 | ||
|
|
d60a10fa59 | ||
|
|
54368e7b22 | ||
|
|
acc556e416 | ||
|
|
700585f99d | ||
|
|
4c2993f353 | ||
|
|
ea9277aab4 | ||
|
|
8d86fca027 | ||
|
|
fc0716a7dd | ||
|
|
1e593dc4d4 | ||
|
|
dcc1e1bcf8 | ||
|
|
06eb775c63 | ||
|
|
ab77c069d4 | ||
|
|
46fffc0e94 | ||
|
|
1c2cdb97e9 | ||
|
|
76b6fd5c18 | ||
|
|
a2fb716035 | ||
|
|
aa84ac8e3e | ||
|
|
4ed2bd1fea | ||
|
|
87b7a63ff2 | ||
|
|
06d0918c3d | ||
|
|
5b3adfe449 | ||
|
|
bdd794a0e6 | ||
|
|
f0df79aa91 | ||
|
|
c26f1760d4 | ||
|
|
e5fa4b0af6 | ||
|
|
a33c299fd7 | ||
|
|
6939621730 | ||
|
|
120ab08360 | ||
|
|
3f5521fdfb | ||
|
|
7244e4b612 | ||
|
|
d329745064 | ||
|
|
5f7fe926ab | ||
|
|
c8eea09664 | ||
|
|
5700d65188 | ||
|
|
46178a5347 | ||
|
|
bff5dbbf5d | ||
|
|
09cd7c47a1 | ||
|
|
e2fadcbc90 | ||
|
|
b3bb29afa8 | ||
|
|
c7db2ff858 | ||
|
|
2a7ef5504a | ||
|
|
27964c5ffd | ||
|
|
d262f6e929 | ||
|
|
d61f5e4b55 | ||
|
|
3ed112e8a9 | ||
|
|
9da626eb2c | ||
|
|
6f74c7327b | ||
|
|
360a2797c1 | ||
|
|
0552977cd6 | ||
|
|
bd407cc4ed | ||
|
|
83b1a117cc | ||
|
|
2a1ff213a0 | ||
|
|
62af6e0eeb | ||
|
|
15da01af5c | ||
|
|
d544bdf092 | ||
|
|
703ade7967 | ||
|
|
58f135ba2f | ||
|
|
713d374484 | ||
|
|
24e9ea28d3 | ||
|
|
cae53138b2 | ||
|
|
a49d45eaa9 | ||
|
|
3986f79029 | ||
|
|
7379fde5ee | ||
|
|
7b63bc5551 | ||
|
|
747ae8210f | ||
|
|
c651416e05 | ||
|
|
814f95e2bf | ||
|
|
d8716f94ae | ||
|
|
67f8cb3b4f | ||
|
|
5c2828bd13 | ||
|
|
b087246f26 | ||
|
|
219d299426 | ||
|
|
31da760729 | ||
|
|
fc89eb8f81 | ||
|
|
6fca1041e9 | ||
|
|
9db1f5641b | ||
|
|
c3beb56e63 | ||
|
|
325edd5f00 | ||
|
|
08322ef359 | ||
|
|
01b43e6e25 | ||
|
|
3cf92a156c | ||
|
|
f54d8d57a4 |
56
.github/ISSUE_TEMPLATE/bug_report.md
vendored
56
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -7,29 +7,48 @@ assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
### Describe the bug
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Create a file with '...'
|
||||
2. Add a path operation function with '....'
|
||||
3. Open the browser and call it with a payload of '....'
|
||||
4. See error
|
||||
Write here a clear and concise description of what the bug is.
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
### To Reproduce
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
Steps to reproduce the behavior with a minimum self-contained file.
|
||||
|
||||
**Environment:**
|
||||
- OS: [e.g. Linux / Windows / macOS]
|
||||
- FastAPI Version [e.g. 0.3.0], get it with:
|
||||
Replace each part with your own scenario:
|
||||
|
||||
1. Create a file with:
|
||||
|
||||
```Python
|
||||
import fastapi
|
||||
print(fastapi.__version__)
|
||||
from fastapi import FastAPI
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.get("/")
|
||||
def read_root():
|
||||
return {"Hello": "World"}
|
||||
```
|
||||
|
||||
3. Open the browser and call the endpoint `/`.
|
||||
4. It returns a JSON with `{"Hello": "World"}`.
|
||||
5. But I expected it to return `{"Hello": "Sara"}`.
|
||||
|
||||
### Expected behavior
|
||||
|
||||
Add a clear and concise description of what you expected to happen.
|
||||
|
||||
### Screenshots
|
||||
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
### Environment
|
||||
|
||||
- OS: [e.g. Linux / Windows / macOS]
|
||||
- FastAPI Version [e.g. 0.3.0], get it with:
|
||||
|
||||
```bash
|
||||
python -c "import fastapi; print(fastapi.__version__)"
|
||||
```
|
||||
|
||||
- Python version, get it with:
|
||||
@@ -38,5 +57,6 @@ print(fastapi.__version__)
|
||||
python --version
|
||||
```
|
||||
|
||||
**Additional context**
|
||||
### Additional context
|
||||
|
||||
Add any other context about the problem here.
|
||||
|
||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -7,14 +7,20 @@ assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I want to be able to [...] but I can't because [...]
|
||||
### Is your feature request related to a problem
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
Is your feature request related to a problem?
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
Add a clear and concise description of what the problem is. Ex. I want to be able to [...] but I can't because [...]
|
||||
|
||||
### The solution you would like
|
||||
|
||||
Add a clear and concise description of what you want to happen.
|
||||
|
||||
### Describe alternatives you've considered
|
||||
|
||||
Add a clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
### Additional context
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
|
||||
11
.github/ISSUE_TEMPLATE/question.md
vendored
11
.github/ISSUE_TEMPLATE/question.md
vendored
@@ -7,11 +7,18 @@ assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Description**
|
||||
### First check
|
||||
|
||||
* [ ] I used the GitHub search to find a similar issue and didn't find it.
|
||||
* [ ] I searched the FastAPI documentation, with the integrated search.
|
||||
* [ ] I already searched in Google "How to X in FastAPI" and didn't find any information.
|
||||
|
||||
### Description
|
||||
|
||||
How can I [...]?
|
||||
|
||||
Is it possible to [...]?
|
||||
|
||||
**Additional context**
|
||||
### Additional context
|
||||
|
||||
Add any other context or screenshots about the feature request here.
|
||||
|
||||
19
.github/workflows/main.yml
vendored
Normal file
19
.github/workflows/main.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
|
||||
jobs:
|
||||
issue-manager:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: tiangolo/issue-manager@master
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
config: >
|
||||
{
|
||||
"answered": {
|
||||
"users": ["tiangolo", "dmontagu"],
|
||||
"delay": 864000,
|
||||
"message": "Assuming the original issue was solved, it will be automatically closed now. But feel free to add more comments or create new issues."
|
||||
}
|
||||
}
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -12,3 +12,4 @@ coverage.xml
|
||||
.netlify
|
||||
test.db
|
||||
log.txt
|
||||
Pipfile.lock
|
||||
|
||||
@@ -7,6 +7,12 @@ cache: pip
|
||||
python:
|
||||
- "3.6"
|
||||
- "3.7"
|
||||
- "3.8"
|
||||
- "nightly"
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- python: "nightly"
|
||||
|
||||
install:
|
||||
- pip install flit
|
||||
|
||||
1
CONTRIBUTING.md
Normal file
1
CONTRIBUTING.md
Normal file
@@ -0,0 +1 @@
|
||||
Please read the [Development - Contributing](https://fastapi.tiangolo.com/contributing/) guidelines in the documentation site.
|
||||
5
Pipfile
5
Pipfile
@@ -25,10 +25,11 @@ sqlalchemy = "*"
|
||||
uvicorn = "*"
|
||||
|
||||
[packages]
|
||||
starlette = "==0.11.1"
|
||||
pydantic = "==0.25.0"
|
||||
starlette = "==0.12.9"
|
||||
pydantic = "==1.0.0"
|
||||
databases = {extras = ["sqlite"],version = "*"}
|
||||
hypercorn = "*"
|
||||
orjson = "*"
|
||||
|
||||
[requires]
|
||||
python_version = "3.6"
|
||||
|
||||
937
Pipfile.lock
generated
937
Pipfile.lock
generated
@@ -1,937 +0,0 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "3366422de5c4cdc49b82ebef5fe9268c48c8582a444a4fa1ae304dcb2654c469"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
"python_version": "3.6"
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
"name": "pypi",
|
||||
"url": "https://pypi.org/simple",
|
||||
"verify_ssl": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"aiocontextvars": {
|
||||
"hashes": [
|
||||
"sha256:885daf8261818767d8f7cbd79f9d4482d118f024b6586ef6e67980236a27bfa3",
|
||||
"sha256:f027372dc48641f683c559f247bd84962becaacdc9ba711d583c3871fb5652aa"
|
||||
],
|
||||
"markers": "python_version < '3.7'",
|
||||
"version": "==0.2.2"
|
||||
},
|
||||
"aiosqlite": {
|
||||
"hashes": [
|
||||
"sha256:ad84fbd7516ca7065d799504fc41d6845c938e5306d1b7dd960caaeda12e22a9"
|
||||
],
|
||||
"version": "==0.10.0"
|
||||
},
|
||||
"contextvars": {
|
||||
"hashes": [
|
||||
"sha256:f38c908aaa59c14335eeea12abea5f443646216c4e29380d7bf34d2018e2c39e"
|
||||
],
|
||||
"markers": "python_version < '3.7'",
|
||||
"version": "==2.4"
|
||||
},
|
||||
"databases": {
|
||||
"extras": [
|
||||
"sqlite"
|
||||
],
|
||||
"hashes": [
|
||||
"sha256:d365cff2035c5177ef5fd8c5abf6671da01189521da64848a01251c870daf48f"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.2.2"
|
||||
},
|
||||
"dataclasses": {
|
||||
"hashes": [
|
||||
"sha256:454a69d788c7fda44efd71e259be79577822f5e3f53f029a22d08004e951dc9f",
|
||||
"sha256:6988bd2b895eef432d562370bb707d540f32f7360ab13da45340101bc2307d84"
|
||||
],
|
||||
"markers": "python_version < '3.7'",
|
||||
"version": "==0.6"
|
||||
},
|
||||
"h11": {
|
||||
"hashes": [
|
||||
"sha256:acca6a44cb52a32ab442b1779adf0875c443c689e9e028f8d831a3769f9c5208",
|
||||
"sha256:f2b1ca39bfed357d1f19ac732913d5f9faa54a5062eca7d2ec3a916cfb7ae4c7"
|
||||
],
|
||||
"version": "==0.8.1"
|
||||
},
|
||||
"h2": {
|
||||
"hashes": [
|
||||
"sha256:c8f387e0e4878904d4978cd688a3195f6b169d49b1ffa572a3d347d7adc5e09f",
|
||||
"sha256:fd07e865a3272ac6ef195d8904de92dc7b38dc28297ec39cfa22716b6d62e6eb"
|
||||
],
|
||||
"version": "==3.1.0"
|
||||
},
|
||||
"hpack": {
|
||||
"hashes": [
|
||||
"sha256:0edd79eda27a53ba5be2dfabf3b15780928a0dff6eb0c60a3d6767720e970c89",
|
||||
"sha256:8eec9c1f4bfae3408a3f30500261f7e6a65912dc138526ea054f9ad98892e9d2"
|
||||
],
|
||||
"version": "==3.0.0"
|
||||
},
|
||||
"hypercorn": {
|
||||
"hashes": [
|
||||
"sha256:cfe7811a93ab7bc22c8a0d5514a2a7a512e812c1e4ee13b9731b705b79d4d453",
|
||||
"sha256:f2577806223fa44d57d6f136b6c37a046794f961252699aec8afb15f35d226d5"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.5.4"
|
||||
},
|
||||
"hyperframe": {
|
||||
"hashes": [
|
||||
"sha256:5187962cb16dcc078f23cb5a4b110098d546c3f41ff2d4038a9896893bbd0b40",
|
||||
"sha256:a9f5c17f2cc3c719b917c4f33ed1c61bd1f8dfac4b1bd23b7c80b3400971b41f"
|
||||
],
|
||||
"version": "==5.2.0"
|
||||
},
|
||||
"immutables": {
|
||||
"hashes": [
|
||||
"sha256:10861f2a2b86139f0c91d5073392d76117f37e84f912dc47c943c23a64008cc7",
|
||||
"sha256:3e23eeb4bc55d57b2a97bef4c1a2891bbb731050b4167c855545797d45e84e45",
|
||||
"sha256:4373876879f147986808f71e6ca02380192a279e8b8d45832f6fed4e7f717562",
|
||||
"sha256:46f9122da033fecf84d7f4c6257aec780f370b20f3ce6bc521702b63ee3d99f7",
|
||||
"sha256:5104db6102e53702af45c6b0af36e45a80970123b11a80c14e0fce48444cdbe3",
|
||||
"sha256:59274bcb631f4fdc9731e9a4a96d16d96b3a17e29fd5e46516518f38406f678f",
|
||||
"sha256:65a9c624e50ca5c50464dbf432996b5c4f056a411bcff5690ef4cab59f913f99",
|
||||
"sha256:b64e0672497b884d21170ca61c693da8488d77f043650efa7911378cbbad0f2c",
|
||||
"sha256:b70655dba00742b033310933066a2202e1cfbbb0f63841b4597cd8787974b242",
|
||||
"sha256:c3d8c238a6f9b60355578579563773348674b6da63c1a0d7394384ed341f3d41",
|
||||
"sha256:cd66bcd11b6a1c1a80fb8d90e25870ff2d5c705ab5eb9666355a33d3fef6ac70",
|
||||
"sha256:d59310fc4f97c1ff8c3660cb98032db266ac0c285a86ca7a512e8e84a95f44c9",
|
||||
"sha256:d71d1c822498646143270580dd6f743bb31ab89ae0ded8b2307c356d3a00f1c0",
|
||||
"sha256:f53da698b42db83cfb1f5073560838051430798c8d8e34a57a27031edbc3041d",
|
||||
"sha256:f958ba15745e30d3a38e3c9fcead8496037135bb21c78c0f925c104abba3a6fa",
|
||||
"sha256:ff95e2aa618eed1a0ef4479938f18f3522c89562b9bbb59d677597c0337569dd"
|
||||
],
|
||||
"version": "==0.9"
|
||||
},
|
||||
"pydantic": {
|
||||
"hashes": [
|
||||
"sha256:2203e01c1d87a3d964aa0db56efdb1b89a90eca610ab3f0ddea396e2a5fa4cc4",
|
||||
"sha256:ac207906e78b1cafbbff6d57b0ce51b989cf5361d2487013f0b353f3bb3b8442"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.25.0"
|
||||
},
|
||||
"pytoml": {
|
||||
"hashes": [
|
||||
"sha256:ca2d0cb127c938b8b76a9a0d0f855cf930c1d50cc3a0af6d3595b566519a1013"
|
||||
],
|
||||
"version": "==0.1.20"
|
||||
},
|
||||
"sqlalchemy": {
|
||||
"hashes": [
|
||||
"sha256:91c54ca8345008fceaec987e10924bf07dcab36c442925357e5a467b36a38319"
|
||||
],
|
||||
"version": "==1.3.3"
|
||||
},
|
||||
"starlette": {
|
||||
"hashes": [
|
||||
"sha256:9d48b35d1fc7521d59ae53c421297ab3878d3c7cd4b75266d77f6c73cccb78bb"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.11.1"
|
||||
},
|
||||
"typing-extensions": {
|
||||
"hashes": [
|
||||
"sha256:07b2c978670896022a43c4b915df8958bec4a6b84add7f2c87b2b728bda3ba64",
|
||||
"sha256:f3f0e67e1d42de47b5c67c32c9b26641642e9170fe7e292991793705cd5fef7c",
|
||||
"sha256:fb2cd053238d33a8ec939190f30cfd736c00653a85a2919415cecf7dc3d9da71"
|
||||
],
|
||||
"version": "==3.7.2"
|
||||
},
|
||||
"wsproto": {
|
||||
"hashes": [
|
||||
"sha256:55c3da870460e8838b2fbe4d10f3accc0cea3a13d5e8dbbdc6da5d537d6d44dc",
|
||||
"sha256:c7f35e0af250b9f25583b090039eb2159a079fbe71b7daf86cc3ddcd2f3a70b3"
|
||||
],
|
||||
"version": "==0.14.0"
|
||||
}
|
||||
},
|
||||
"develop": {
|
||||
"appdirs": {
|
||||
"hashes": [
|
||||
"sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92",
|
||||
"sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"
|
||||
],
|
||||
"version": "==1.4.3"
|
||||
},
|
||||
"atomicwrites": {
|
||||
"hashes": [
|
||||
"sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4",
|
||||
"sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"
|
||||
],
|
||||
"version": "==1.3.0"
|
||||
},
|
||||
"attrs": {
|
||||
"hashes": [
|
||||
"sha256:69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79",
|
||||
"sha256:f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399"
|
||||
],
|
||||
"version": "==19.1.0"
|
||||
},
|
||||
"autoflake": {
|
||||
"hashes": [
|
||||
"sha256:6b59e5b9b82e30077499578856282debb81186d10b4f899e8c2e1d616cdef973"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.3"
|
||||
},
|
||||
"backcall": {
|
||||
"hashes": [
|
||||
"sha256:38ecd85be2c1e78f77fd91700c76e14667dc21e2713b63876c0eb901196e01e4",
|
||||
"sha256:bbbf4b1e5cd2bdb08f915895b51081c041bac22394fdfcfdfbe9f14b77c08bf2"
|
||||
],
|
||||
"version": "==0.1.0"
|
||||
},
|
||||
"better-exceptions": {
|
||||
"hashes": [
|
||||
"sha256:bf79c87659bc849989d726bf0e4a2100edefe7eded112d201f54fe08467fdf63",
|
||||
"sha256:c196cad849de615abb9f6eb67ca1b83f33b938818f0e2fe8fa157b22aeb7b992"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.2.2"
|
||||
},
|
||||
"black": {
|
||||
"hashes": [
|
||||
"sha256:09a9dcb7c46ed496a9850b76e4e825d6049ecd38b611f1224857a79bd985a8cf",
|
||||
"sha256:68950ffd4d9169716bcb8719a56c07a2f4485354fec061cdd5910aa07369731c"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==19.3b0"
|
||||
},
|
||||
"bleach": {
|
||||
"hashes": [
|
||||
"sha256:213336e49e102af26d9cde77dd2d0397afabc5a6bf2fed985dc35b5d1e285a16",
|
||||
"sha256:3fdf7f77adcf649c9911387df51254b813185e32b2c6619f690b593a617e19fa"
|
||||
],
|
||||
"version": "==3.1.0"
|
||||
},
|
||||
"certifi": {
|
||||
"hashes": [
|
||||
"sha256:59b7658e26ca9c7339e00f8f4636cdfe59d34fa37b9b04f6f9e9926b3cece1a5",
|
||||
"sha256:b26104d6835d1f5e49452a26eb2ff87fe7090b89dfcaee5ea2212697e1e1d7ae"
|
||||
],
|
||||
"version": "==2019.3.9"
|
||||
},
|
||||
"chardet": {
|
||||
"hashes": [
|
||||
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
|
||||
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
|
||||
],
|
||||
"version": "==3.0.4"
|
||||
},
|
||||
"click": {
|
||||
"hashes": [
|
||||
"sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13",
|
||||
"sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"
|
||||
],
|
||||
"version": "==7.0"
|
||||
},
|
||||
"coverage": {
|
||||
"hashes": [
|
||||
"sha256:0402b1822d513d0231589494bceddb067d20581f5083598c451b56c684b0e5d6",
|
||||
"sha256:0644e28e8aea9d9d563607ee8b7071b07dd57a4a3de11f8684cd33c51c0d1b93",
|
||||
"sha256:0874a283686803884ec0665018881130604956dbaa344f2539c46d82cbe29eda",
|
||||
"sha256:0988c3837df4bc371189bb3425d5232cf150055452034c232dda9cbe04f9c38e",
|
||||
"sha256:20bc3205b3100956bb72293fabb97f0ed972c81fed10b3251c90c70dcb0599ab",
|
||||
"sha256:2cc9142a3367e74eb6b19d58c53ebb1dfd7336b91cdcc91a6a2888bf8c7af984",
|
||||
"sha256:3ae9a0a59b058ce0761c3bd2c2d66ecb2ee2b8ac592620184370577f7a546fb3",
|
||||
"sha256:3b2e30b835df58cb973f478d09f3d82e90c98c8e5059acc245a8e4607e023801",
|
||||
"sha256:401e9b04894eb1498c639c6623ee78a646990ce5f095248e2440968aafd6e90e",
|
||||
"sha256:41ec5812d5decdaa72708be3018e7443e90def4b5a71294236a4df192cf9eab9",
|
||||
"sha256:475769b638a055e75b3d3219e054fe2a023c0b077ff15bff6c95aba7e93e6cac",
|
||||
"sha256:61424f4e2e82c4129a4ba71e10ebacb32a9ecd6f80de2cd05bdead6ba75ed736",
|
||||
"sha256:811969904d4dd0bee7d958898be8d9d75cef672d9b7e7db819dfeac3d20d2d0c",
|
||||
"sha256:86224bb99abfd672bf2f9fcecad5e8d7a3fa94f7f71513f2210460a0350307cd",
|
||||
"sha256:9a238a20a3af00665f8381f7e53e9c606f9bb652d2423f6b822f6cb790d887e8",
|
||||
"sha256:a23b3fbc14d4e6182ecebfd22f3729beef0636d151d94764a1c28330d185e4e5",
|
||||
"sha256:ac162b4ebe51b7a2b7f5e462c4402802633eb81e77c94f8a7c1ed8a556e72c75",
|
||||
"sha256:b6187378726c84365bf297b5dcdae8789b6a5823b200bea23797777e5a63be09",
|
||||
"sha256:bcd5723d905ed4a825f17410a53535f880b6d7548ae3d89078db7b1ceefcd853",
|
||||
"sha256:c48a4f9c5fb385269bb7fbaf9c1326a94863b65ec7f5c96b2ea56b252f01ad08",
|
||||
"sha256:cd40199d6f1c29c85b170d25589be9a97edff8ee7e62be180a2a137823896030",
|
||||
"sha256:d1bc331a7d069485ac1d8c25a0ea1f6aab6cb2a87146fb652222481c1bddc9ff",
|
||||
"sha256:d7e0cdc249aa0f94aa2e531b03999ddaf03a10b4fa090a894712d4c8066abd89",
|
||||
"sha256:e9ee8fcd8e067fcc5d7276d46e07e863102b70a52545ef4254df1ff0893ce75f",
|
||||
"sha256:eb313c23d983b7810504f42104e8dcd1c7ccdda8fbaab82aab92ab79fea19345",
|
||||
"sha256:f9cfd478654b509941b85ed70f870f5e3c74678f566bec12fd26545e5340ba47",
|
||||
"sha256:fae1fa144034d021a52cb9ea200eb8dedf91869c6df8202ad5d149b41ed91cc8"
|
||||
],
|
||||
"version": "==5.0a5"
|
||||
},
|
||||
"decorator": {
|
||||
"hashes": [
|
||||
"sha256:86156361c50488b84a3f148056ea716ca587df2f0de1d34750d35c21312725de",
|
||||
"sha256:f069f3a01830ca754ba5258fde2278454a0b5b79e0d7f5c13b3b97e57d4acff6"
|
||||
],
|
||||
"version": "==4.4.0"
|
||||
},
|
||||
"defusedxml": {
|
||||
"hashes": [
|
||||
"sha256:6687150770438374ab581bb7a1b327a847dd9c5749e396102de3fad4e8a3ef93",
|
||||
"sha256:f684034d135af4c6cbb949b8a4d2ed61634515257a67299e5f940fbaa34377f5"
|
||||
],
|
||||
"version": "==0.6.0"
|
||||
},
|
||||
"dnspython": {
|
||||
"hashes": [
|
||||
"sha256:36c5e8e38d4369a08b6780b7f27d790a292b2b08eea01607865bf0936c558e01",
|
||||
"sha256:f69c21288a962f4da86e56c4905b49d11aba7938d3d740e80d9e366ee4f1632d"
|
||||
],
|
||||
"version": "==1.16.0"
|
||||
},
|
||||
"docutils": {
|
||||
"hashes": [
|
||||
"sha256:02aec4bd92ab067f6ff27a38a38a41173bf01bed8f89157768c1573f53e474a6",
|
||||
"sha256:51e64ef2ebfb29cae1faa133b3710143496eca21c530f3f71424d77687764274",
|
||||
"sha256:7a4bd47eaf6596e1295ecb11361139febe29b084a87bf005bf899f9a42edc3c6"
|
||||
],
|
||||
"version": "==0.14"
|
||||
},
|
||||
"email-validator": {
|
||||
"hashes": [
|
||||
"sha256:79966e318d6d68fed359c90f8f19d242bcc178b724011f1c07145bd093da6cc7"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.0.4"
|
||||
},
|
||||
"entrypoints": {
|
||||
"hashes": [
|
||||
"sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19",
|
||||
"sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"
|
||||
],
|
||||
"version": "==0.3"
|
||||
},
|
||||
"flake8": {
|
||||
"hashes": [
|
||||
"sha256:859996073f341f2670741b51ec1e67a01da142831aa1fdc6242dbf88dffbe661",
|
||||
"sha256:a796a115208f5c03b18f332f7c11729812c8c3ded6c46319c59b53efd3819da8"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.7.7"
|
||||
},
|
||||
"flit": {
|
||||
"hashes": [
|
||||
"sha256:1d93f7a833ed8a6e120ddc40db5c4763bc39bccc75c05081ec8285ece718aefb",
|
||||
"sha256:6f6f0fb83c51ffa3a150fa41b5ac118df9ea4a87c2c06dff4ebf9adbe7b52b36"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.3"
|
||||
},
|
||||
"h11": {
|
||||
"hashes": [
|
||||
"sha256:acca6a44cb52a32ab442b1779adf0875c443c689e9e028f8d831a3769f9c5208",
|
||||
"sha256:f2b1ca39bfed357d1f19ac732913d5f9faa54a5062eca7d2ec3a916cfb7ae4c7"
|
||||
],
|
||||
"version": "==0.8.1"
|
||||
},
|
||||
"httptools": {
|
||||
"hashes": [
|
||||
"sha256:e00cbd7ba01ff748e494248183abc6e153f49181169d8a3d41bb49132ca01dfc"
|
||||
],
|
||||
"version": "==0.0.13"
|
||||
},
|
||||
"idna": {
|
||||
"hashes": [
|
||||
"sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407",
|
||||
"sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"
|
||||
],
|
||||
"version": "==2.8"
|
||||
},
|
||||
"ipykernel": {
|
||||
"hashes": [
|
||||
"sha256:0aeb7ec277ac42cc2b59ae3d08b10909b2ec161dc6908096210527162b53675d",
|
||||
"sha256:0fc0bf97920d454102168ec2008620066878848fcfca06c22b669696212e292f"
|
||||
],
|
||||
"version": "==5.1.0"
|
||||
},
|
||||
"ipython": {
|
||||
"hashes": [
|
||||
"sha256:54c5a8aa1eadd269ac210b96923688ccf01ebb2d0f21c18c3c717909583579a8",
|
||||
"sha256:e840810029224b56cd0d9e7719dc3b39cf84d577f8ac686547c8ba7a06eeab26"
|
||||
],
|
||||
"markers": "python_version >= '3.3'",
|
||||
"version": "==7.5.0"
|
||||
},
|
||||
"ipython-genutils": {
|
||||
"hashes": [
|
||||
"sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8",
|
||||
"sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"
|
||||
],
|
||||
"version": "==0.2.0"
|
||||
},
|
||||
"ipywidgets": {
|
||||
"hashes": [
|
||||
"sha256:0f2b5cde9f272cb49d52f3f0889fdd1a7ae1e74f37b48dac35a83152780d2b7b",
|
||||
"sha256:a3e224f430163f767047ab9a042fc55adbcab0c24bbe6cf9f306c4f89fdf0ba3"
|
||||
],
|
||||
"version": "==7.4.2"
|
||||
},
|
||||
"isort": {
|
||||
"hashes": [
|
||||
"sha256:49293e2ff590cc8d48bc1f51970548b5b102bf038439ca1af77f352164725628",
|
||||
"sha256:ba69a4be8474be11720636bc2f0cf66f7054d417d4c1dbc1dfe504bb8e739541"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.3.19"
|
||||
},
|
||||
"jedi": {
|
||||
"hashes": [
|
||||
"sha256:2bb0603e3506f708e792c7f4ad8fc2a7a9d9c2d292a358fbbd58da531695595b",
|
||||
"sha256:2c6bcd9545c7d6440951b12b44d373479bf18123a401a52025cf98563fbd826c"
|
||||
],
|
||||
"version": "==0.13.3"
|
||||
},
|
||||
"jinja2": {
|
||||
"hashes": [
|
||||
"sha256:065c4f02ebe7f7cf559e49ee5a95fb800a9e4528727aec6f24402a5374c65013",
|
||||
"sha256:14dd6caf1527abb21f08f86c784eac40853ba93edb79552aa1e4b8aef1b61c7b"
|
||||
],
|
||||
"version": "==2.10.1"
|
||||
},
|
||||
"jsonschema": {
|
||||
"hashes": [
|
||||
"sha256:0c0a81564f181de3212efa2d17de1910f8732fa1b71c42266d983cd74304e20d",
|
||||
"sha256:a5f6559964a3851f59040d3b961de5e68e70971afb88ba519d27e6a039efff1a"
|
||||
],
|
||||
"version": "==3.0.1"
|
||||
},
|
||||
"jupyter": {
|
||||
"hashes": [
|
||||
"sha256:3e1f86076bbb7c8c207829390305a2b1fe836d471ed54be66a3b8c41e7f46cc7",
|
||||
"sha256:5b290f93b98ffbc21c0c7e749f054b3267782166d72fa5e3ed1ed4eaf34a2b78",
|
||||
"sha256:d9dc4b3318f310e34c82951ea5d6683f67bed7def4b259fafbfe4f1beb1d8e5f"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.0.0"
|
||||
},
|
||||
"jupyter-client": {
|
||||
"hashes": [
|
||||
"sha256:b5f9cb06105c1d2d30719db5ffb3ea67da60919fb68deaefa583deccd8813551",
|
||||
"sha256:c44411eb1463ed77548bc2d5ec0d744c9b81c4a542d9637c7a52824e2121b987"
|
||||
],
|
||||
"version": "==5.2.4"
|
||||
},
|
||||
"jupyter-console": {
|
||||
"hashes": [
|
||||
"sha256:308ce876354924fb6c540b41d5d6d08acfc946984bf0c97777c1ddcb42e0b2f5",
|
||||
"sha256:cc80a97a5c389cbd30252ffb5ce7cefd4b66bde98219edd16bf5cb6f84bb3568"
|
||||
],
|
||||
"version": "==6.0.0"
|
||||
},
|
||||
"jupyter-core": {
|
||||
"hashes": [
|
||||
"sha256:927d713ffa616ea11972534411544589976b2493fc7e09ad946e010aa7eb9970",
|
||||
"sha256:ba70754aa680300306c699790128f6fbd8c306ee5927976cbe48adacf240c0b7"
|
||||
],
|
||||
"version": "==4.4.0"
|
||||
},
|
||||
"livereload": {
|
||||
"hashes": [
|
||||
"sha256:78d55f2c268a8823ba499305dcac64e28ddeb9a92571e12d543cd304faf5817b",
|
||||
"sha256:89254f78d7529d7ea0a3417d224c34287ebfe266b05e67e51facaf82c27f0f66"
|
||||
],
|
||||
"version": "==2.6.1"
|
||||
},
|
||||
"markdown": {
|
||||
"hashes": [
|
||||
"sha256:fc4a6f69a656b8d858d7503bda633f4dd63c2d70cf80abdc6eafa64c4ae8c250",
|
||||
"sha256:fe463ff51e679377e3624984c829022e2cfb3be5518726b06f608a07a3aad680"
|
||||
],
|
||||
"version": "==3.1"
|
||||
},
|
||||
"markdown-include": {
|
||||
"hashes": [
|
||||
"sha256:72a45461b589489a088753893bc95c5fa5909936186485f4ed55caa57d10250f"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.5.1"
|
||||
},
|
||||
"markupsafe": {
|
||||
"hashes": [
|
||||
"sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473",
|
||||
"sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161",
|
||||
"sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235",
|
||||
"sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5",
|
||||
"sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff",
|
||||
"sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b",
|
||||
"sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1",
|
||||
"sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e",
|
||||
"sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183",
|
||||
"sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66",
|
||||
"sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1",
|
||||
"sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1",
|
||||
"sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e",
|
||||
"sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b",
|
||||
"sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905",
|
||||
"sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735",
|
||||
"sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d",
|
||||
"sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e",
|
||||
"sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d",
|
||||
"sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c",
|
||||
"sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21",
|
||||
"sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2",
|
||||
"sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5",
|
||||
"sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b",
|
||||
"sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6",
|
||||
"sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f",
|
||||
"sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f",
|
||||
"sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"
|
||||
],
|
||||
"version": "==1.1.1"
|
||||
},
|
||||
"mccabe": {
|
||||
"hashes": [
|
||||
"sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
|
||||
"sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"
|
||||
],
|
||||
"version": "==0.6.1"
|
||||
},
|
||||
"mistune": {
|
||||
"hashes": [
|
||||
"sha256:59a3429db53c50b5c6bcc8a07f8848cb00d7dc8bdb431a4ab41920d201d4756e",
|
||||
"sha256:88a1051873018da288eee8538d476dffe1262495144b33ecb586c4ab266bb8d4"
|
||||
],
|
||||
"version": "==0.8.4"
|
||||
},
|
||||
"mkdocs": {
|
||||
"hashes": [
|
||||
"sha256:17d34329aad75d5de604b9ed4e31df3a4d235afefdc46ce7b1964fddb2e1e939",
|
||||
"sha256:8cc8b38325456b9e942c981a209eaeb1e9f3f77b493ad755bfef889b9c8d356a"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.0.4"
|
||||
},
|
||||
"mkdocs-material": {
|
||||
"hashes": [
|
||||
"sha256:4ffe7d8c0c3c53c5313a910c14a88820be74beebb53ed14c9056e521ea9793d5",
|
||||
"sha256:d64b9555ae4ee86fe07a18612c9bd488f3b74a0afd3d9ead7e29efc59d98ca80"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.2.0"
|
||||
},
|
||||
"more-itertools": {
|
||||
"hashes": [
|
||||
"sha256:2112d2ca570bb7c3e53ea1a35cd5df42bb0fd10c45f0fb97178679c3c03d64c7",
|
||||
"sha256:c3e4748ba1aad8dba30a4886b0b1a2004f9a863837b8654e7059eebf727afa5a"
|
||||
],
|
||||
"markers": "python_version > '2.7'",
|
||||
"version": "==7.0.0"
|
||||
},
|
||||
"mypy": {
|
||||
"hashes": [
|
||||
"sha256:2afe51527b1f6cdc4a5f34fc90473109b22bf7f21086ba3e9451857cf11489e6",
|
||||
"sha256:56a16df3e0abb145d8accd5dbb70eba6c4bd26e2f89042b491faa78c9635d1e2",
|
||||
"sha256:5764f10d27b2e93c84f70af5778941b8f4aa1379b2430f85c827e0f5464e8714",
|
||||
"sha256:5bbc86374f04a3aa817622f98e40375ccb28c4836f36b66706cf3c6ccce86eda",
|
||||
"sha256:6a9343089f6377e71e20ca734cd8e7ac25d36478a9df580efabfe9059819bf82",
|
||||
"sha256:6c9851bc4a23dc1d854d3f5dfd5f20a016f8da86bcdbb42687879bb5f86434b0",
|
||||
"sha256:b8e85956af3fcf043d6f87c91cbe8705073fc67029ba6e22d3468bfee42c4823",
|
||||
"sha256:b9a0af8fae490306bc112229000aa0c2ccc837b49d29a5c42e088c132a2334dd",
|
||||
"sha256:bbf643528e2a55df2c1587008d6e3bda5c0445f1240dfa85129af22ae16d7a9a",
|
||||
"sha256:c46ab3438bd21511db0f2c612d89d8344154c0c9494afc7fbc932de514cf8d15",
|
||||
"sha256:f7a83d6bd805855ef83ec605eb01ab4fa42bcef254b13631e451cbb44914a9b0"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.701"
|
||||
},
|
||||
"mypy-extensions": {
|
||||
"hashes": [
|
||||
"sha256:37e0e956f41369209a3d5f34580150bcacfabaa57b33a15c0b25f4b5725e0812",
|
||||
"sha256:b16cabe759f55e3409a7d231ebd2841378fb0c27a5d1994719e340e4f429ac3e"
|
||||
],
|
||||
"version": "==0.4.1"
|
||||
},
|
||||
"nbconvert": {
|
||||
"hashes": [
|
||||
"sha256:138381baa41d83584459b5cfecfc38c800ccf1f37d9ddd0bd440783346a4c39c",
|
||||
"sha256:4a978548d8383f6b2cfca4a3b0543afb77bc7cb5a96e8b424337ab58c12da9bc"
|
||||
],
|
||||
"version": "==5.5.0"
|
||||
},
|
||||
"nbformat": {
|
||||
"hashes": [
|
||||
"sha256:b9a0dbdbd45bb034f4f8893cafd6f652ea08c8c1674ba83f2dc55d3955743b0b",
|
||||
"sha256:f7494ef0df60766b7cabe0a3651556345a963b74dbc16bc7c18479041170d402"
|
||||
],
|
||||
"version": "==4.4.0"
|
||||
},
|
||||
"notebook": {
|
||||
"hashes": [
|
||||
"sha256:573e0ae650c5d76b18b6e564ba6d21bf321d00847de1d215b418acb64f056eb8",
|
||||
"sha256:f64fa6624d2323fbef6210a621817d6505a45d0d4a9367f1843b20a38a4666ee"
|
||||
],
|
||||
"version": "==5.7.8"
|
||||
},
|
||||
"pandocfilters": {
|
||||
"hashes": [
|
||||
"sha256:b3dd70e169bb5449e6bc6ff96aea89c5eea8c5f6ab5e207fc2f521a2cf4a0da9"
|
||||
],
|
||||
"version": "==1.4.2"
|
||||
},
|
||||
"parso": {
|
||||
"hashes": [
|
||||
"sha256:17cc2d7a945eb42c3569d4564cdf49bde221bc2b552af3eca9c1aad517dcdd33",
|
||||
"sha256:2e9574cb12e7112a87253e14e2c380ce312060269d04bd018478a3c92ea9a376"
|
||||
],
|
||||
"version": "==0.4.0"
|
||||
},
|
||||
"pexpect": {
|
||||
"hashes": [
|
||||
"sha256:2094eefdfcf37a1fdbfb9aa090862c1a4878e5c7e0e7e7088bdb511c558e5cd1",
|
||||
"sha256:9e2c1fd0e6ee3a49b28f95d4b33bc389c89b20af6a1255906e90ff1262ce62eb"
|
||||
],
|
||||
"markers": "sys_platform != 'win32'",
|
||||
"version": "==4.7.0"
|
||||
},
|
||||
"pickleshare": {
|
||||
"hashes": [
|
||||
"sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca",
|
||||
"sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"
|
||||
],
|
||||
"version": "==0.7.5"
|
||||
},
|
||||
"pluggy": {
|
||||
"hashes": [
|
||||
"sha256:25a1bc1d148c9a640211872b4ff859878d422bccb59c9965e04eed468a0aa180",
|
||||
"sha256:964cedd2b27c492fbf0b7f58b3284a09cf7f99b0f715941fb24a439b3af1bd1a"
|
||||
],
|
||||
"version": "==0.11.0"
|
||||
},
|
||||
"prometheus-client": {
|
||||
"hashes": [
|
||||
"sha256:1b38b958750f66f208bcd9ab92a633c0c994d8859c831f7abc1f46724fcee490"
|
||||
],
|
||||
"version": "==0.6.0"
|
||||
},
|
||||
"prompt-toolkit": {
|
||||
"hashes": [
|
||||
"sha256:11adf3389a996a6d45cc277580d0d53e8a5afd281d0c9ec71b28e6f121463780",
|
||||
"sha256:2519ad1d8038fd5fc8e770362237ad0364d16a7650fb5724af6997ed5515e3c1",
|
||||
"sha256:977c6583ae813a37dc1c2e1b715892461fcbdaa57f6fc62f33a528c4886c8f55"
|
||||
],
|
||||
"version": "==2.0.9"
|
||||
},
|
||||
"ptyprocess": {
|
||||
"hashes": [
|
||||
"sha256:923f299cc5ad920c68f2bc0bc98b75b9f838b93b599941a6b63ddbc2476394c0",
|
||||
"sha256:d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f"
|
||||
],
|
||||
"markers": "os_name != 'nt'",
|
||||
"version": "==0.6.0"
|
||||
},
|
||||
"py": {
|
||||
"hashes": [
|
||||
"sha256:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa",
|
||||
"sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53"
|
||||
],
|
||||
"version": "==1.8.0"
|
||||
},
|
||||
"pycodestyle": {
|
||||
"hashes": [
|
||||
"sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56",
|
||||
"sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c"
|
||||
],
|
||||
"version": "==2.5.0"
|
||||
},
|
||||
"pyflakes": {
|
||||
"hashes": [
|
||||
"sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0",
|
||||
"sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2"
|
||||
],
|
||||
"version": "==2.1.1"
|
||||
},
|
||||
"pygments": {
|
||||
"hashes": [
|
||||
"sha256:31cba6ffb739f099a85e243eff8cb717089fdd3c7300767d9fc34cb8e1b065f5",
|
||||
"sha256:5ad302949b3c98dd73f8d9fcdc7e9cb592f120e32a18e23efd7f3dc51194472b"
|
||||
],
|
||||
"version": "==2.4.0"
|
||||
},
|
||||
"pymdown-extensions": {
|
||||
"hashes": [
|
||||
"sha256:25b0a7967fa697b5035e23340a48594e3e93acb10b06d74574218ace3347d1df",
|
||||
"sha256:6cf0cf36b5a03b291ace22dc2f320f4789ce56fbdb6635a3be5fadbf5d7694dd"
|
||||
],
|
||||
"version": "==6.0"
|
||||
},
|
||||
"pyrsistent": {
|
||||
"hashes": [
|
||||
"sha256:16692ee739d42cf5e39cef8d27649a8c1fdb7aa99887098f1460057c5eb75c3a"
|
||||
],
|
||||
"version": "==0.15.2"
|
||||
},
|
||||
"pytest": {
|
||||
"hashes": [
|
||||
"sha256:1a8aa4fa958f8f451ac5441f3ac130d9fc86ea38780dd2715e6d5c5882700b24",
|
||||
"sha256:b8bf138592384bd4e87338cb0f256bf5f615398a649d4bd83915f0e4047a5ca6"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.5.0"
|
||||
},
|
||||
"pytest-cov": {
|
||||
"hashes": [
|
||||
"sha256:2b097cde81a302e1047331b48cadacf23577e431b61e9c6f49a1170bbe3d3da6",
|
||||
"sha256:e00ea4fdde970725482f1f35630d12f074e121a23801aabf2ae154ec6bdd343a"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.7.1"
|
||||
},
|
||||
"python-dateutil": {
|
||||
"hashes": [
|
||||
"sha256:7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb",
|
||||
"sha256:c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e"
|
||||
],
|
||||
"version": "==2.8.0"
|
||||
},
|
||||
"python-multipart": {
|
||||
"hashes": [
|
||||
"sha256:f7bb5f611fc600d15fa47b3974c8aa16e93724513b49b5f95c81e6624c83fa43"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.0.5"
|
||||
},
|
||||
"pytoml": {
|
||||
"hashes": [
|
||||
"sha256:ca2d0cb127c938b8b76a9a0d0f855cf930c1d50cc3a0af6d3595b566519a1013"
|
||||
],
|
||||
"version": "==0.1.20"
|
||||
},
|
||||
"pyyaml": {
|
||||
"hashes": [
|
||||
"sha256:1adecc22f88d38052fb787d959f003811ca858b799590a5eaa70e63dca50308c",
|
||||
"sha256:436bc774ecf7c103814098159fbb84c2715d25980175292c648f2da143909f95",
|
||||
"sha256:460a5a4248763f6f37ea225d19d5c205677d8d525f6a83357ca622ed541830c2",
|
||||
"sha256:5a22a9c84653debfbf198d02fe592c176ea548cccce47553f35f466e15cf2fd4",
|
||||
"sha256:7a5d3f26b89d688db27822343dfa25c599627bc92093e788956372285c6298ad",
|
||||
"sha256:9372b04a02080752d9e6f990179a4ab840227c6e2ce15b95e1278456664cf2ba",
|
||||
"sha256:a5dcbebee834eaddf3fa7366316b880ff4062e4bcc9787b78c7fbb4a26ff2dd1",
|
||||
"sha256:aee5bab92a176e7cd034e57f46e9df9a9862a71f8f37cad167c6fc74c65f5b4e",
|
||||
"sha256:c51f642898c0bacd335fc119da60baae0824f2cde95b0330b56c0553439f0673",
|
||||
"sha256:c68ea4d3ba1705da1e0d85da6684ac657912679a649e8868bd850d2c299cce13",
|
||||
"sha256:e23d0cc5299223dcc37885dae624f382297717e459ea24053709675a976a3e19"
|
||||
],
|
||||
"version": "==5.1"
|
||||
},
|
||||
"pyzmq": {
|
||||
"hashes": [
|
||||
"sha256:1651e52ed91f0736afd6d94ef9f3259b5534ce8beddb054f3d5ca989c4ef7c4f",
|
||||
"sha256:5ccb9b3d4cd20c000a9b75689d5add8cd3bce67fcbd0f8ae1b59345247d803af",
|
||||
"sha256:5e120c4cd3872e332fb35d255ad5998ebcee32ace4387b1b337416b6b90436c7",
|
||||
"sha256:5e2a3707c69a7281a9957f83718815fd74698cba31f6d69f9ed359921f662221",
|
||||
"sha256:63d51add9af8d0442dc90f916baf98fdc04e3b0a32afec4bfc83f8d85e72959f",
|
||||
"sha256:65c5a0bdc49e20f7d6b03a661f71e2fda7a99c51270cafe71598146d09810d0d",
|
||||
"sha256:66828fabe911aa545d919028441a585edb7c9c77969a5fea6722ef6e6ece38ab",
|
||||
"sha256:7d79427e82d9dad6e9b47c0b3e7ae5f9d489b1601e3a36ea629bb49501a4daf3",
|
||||
"sha256:824ee5d3078c4eae737ffc500fbf32f2b14e6ec89b26b435b7834febd70120cf",
|
||||
"sha256:89dc0a83cccec19ff3c62c091e43e66e0183d1e6b4658c16ee4e659518131494",
|
||||
"sha256:8b319805f6f7c907b101c864c3ca6cefc9db8ce0791356f180b1b644c7347e4c",
|
||||
"sha256:90facfb379ab47f94b19519c1ecc8ec8d10813b69d9c163117944948bdec5d15",
|
||||
"sha256:a0a178c7420021fc0730180a914a4b4b3092ce9696ceb8e72d0f60f8ce1655dd",
|
||||
"sha256:a7a89591ae315baccb8072f216614b3e59aed7385aef4393a6c741783d6ee9cf",
|
||||
"sha256:ba2578f0ae582452c02ed9fac2dc477b08e80ce05d2c0885becf5fff6651ccb0",
|
||||
"sha256:c69b0055c55702f5b0b6b354133e8325b9a56dbc80e1be2d240bead253fb9825",
|
||||
"sha256:ca434e1858fe222380221ddeb81e86f45522773344c9da63c311d17161df5e06",
|
||||
"sha256:d4b8ecfc3d92f114f04d5c40f60a65e5196198b827503341521dda12d8b14939",
|
||||
"sha256:d706025c47b09a54f005953ebe206f6d07a22516776faa4f509aaff681cc5468",
|
||||
"sha256:d8f27e958f8a2c0c8ffd4d8855c3ce8ac3fa1e105f0491ce31729aa2b3229740",
|
||||
"sha256:dbd264298f76b9060ce537008eb989317ca787c857e23cbd1b3ddf89f190a9b1",
|
||||
"sha256:e926d66f0df8fdbf03ba20583af0f215e475c667fb033d45fd031c66c63e34c9",
|
||||
"sha256:efc3bd48237f973a749f7312f68062f1b4ca5c2032a0673ca3ea8e46aa77187b",
|
||||
"sha256:f59bc782228777cbfe04555707a9c56d269c787ed25d6d28ed9d0fbb41cb1ad2",
|
||||
"sha256:f8da5322f4ff5f667a0d5a27e871b560c6637153c81e318b35cb012b2a98835c"
|
||||
],
|
||||
"version": "==18.0.1"
|
||||
},
|
||||
"qtconsole": {
|
||||
"hashes": [
|
||||
"sha256:a667558c7b1e1442a2e5bcef1686c55e096efd0b58d8b2a0a8415f4579991ee3",
|
||||
"sha256:fdfc6002d9d2834c88f9c92e0f6f590284ff3740fa53016f188a62d58bcca6d8"
|
||||
],
|
||||
"version": "==4.4.4"
|
||||
},
|
||||
"requests": {
|
||||
"hashes": [
|
||||
"sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e",
|
||||
"sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.21.0"
|
||||
},
|
||||
"send2trash": {
|
||||
"hashes": [
|
||||
"sha256:60001cc07d707fe247c94f74ca6ac0d3255aabcb930529690897ca2a39db28b2",
|
||||
"sha256:f1691922577b6fa12821234aeb57599d887c4900b9ca537948d2dac34aea888b"
|
||||
],
|
||||
"version": "==1.5.0"
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
"sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c",
|
||||
"sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"
|
||||
],
|
||||
"version": "==1.12.0"
|
||||
},
|
||||
"sqlalchemy": {
|
||||
"hashes": [
|
||||
"sha256:91c54ca8345008fceaec987e10924bf07dcab36c442925357e5a467b36a38319"
|
||||
],
|
||||
"version": "==1.3.3"
|
||||
},
|
||||
"terminado": {
|
||||
"hashes": [
|
||||
"sha256:d9d012de63acb8223ac969c17c3043337c2fcfd28f3aea1ee429b345d01ef460",
|
||||
"sha256:de08e141f83c3a0798b050ecb097ab6259c3f0331b2f7b7750c9075ced2c20c2"
|
||||
],
|
||||
"version": "==0.8.2"
|
||||
},
|
||||
"testpath": {
|
||||
"hashes": [
|
||||
"sha256:46c89ebb683f473ffe2aab0ed9f12581d4d078308a3cb3765d79c6b2317b0109",
|
||||
"sha256:b694b3d9288dbd81685c5d2e7140b81365d46c29f5db4bc659de5aa6b98780f8"
|
||||
],
|
||||
"version": "==0.4.2"
|
||||
},
|
||||
"toml": {
|
||||
"hashes": [
|
||||
"sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c",
|
||||
"sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"
|
||||
],
|
||||
"version": "==0.10.0"
|
||||
},
|
||||
"tornado": {
|
||||
"hashes": [
|
||||
"sha256:1174dcb84d08887b55defb2cda1986faeeea715fff189ef3dc44cce99f5fca6b",
|
||||
"sha256:2613fab506bd2aedb3722c8c64c17f8f74f4070afed6eea17f20b2115e445aec",
|
||||
"sha256:44b82bc1146a24e5b9853d04c142576b4e8fa7a92f2e30bc364a85d1f75c4de2",
|
||||
"sha256:457fcbee4df737d2defc181b9073758d73f54a6cfc1f280533ff48831b39f4a8",
|
||||
"sha256:49603e1a6e24104961497ad0c07c799aec1caac7400a6762b687e74c8206677d",
|
||||
"sha256:8c2f40b99a8153893793559919a355d7b74649a11e59f411b0b0a1793e160bc0",
|
||||
"sha256:e1d897889c3b5a829426b7d52828fb37b28bc181cd598624e65c8be40ee3f7fa"
|
||||
],
|
||||
"version": "==6.0.2"
|
||||
},
|
||||
"traitlets": {
|
||||
"hashes": [
|
||||
"sha256:9c4bd2d267b7153df9152698efb1050a5d84982d3384a37b2c1f7723ba3e7835",
|
||||
"sha256:c6cb5e6f57c5a9bdaa40fa71ce7b4af30298fbab9ece9815b5d995ab6217c7d9"
|
||||
],
|
||||
"version": "==4.3.2"
|
||||
},
|
||||
"typed-ast": {
|
||||
"hashes": [
|
||||
"sha256:132eae51d6ef3ff4a8c47c393a4ef5ebf0d1aecc96880eb5d6c8ceab7017cc9b",
|
||||
"sha256:18141c1484ab8784006c839be8b985cfc82a2e9725837b0ecfa0203f71c4e39d",
|
||||
"sha256:2baf617f5bbbfe73fd8846463f5aeafc912b5ee247f410700245d68525ec584a",
|
||||
"sha256:3d90063f2cbbe39177e9b4d888e45777012652d6110156845b828908c51ae462",
|
||||
"sha256:4304b2218b842d610aa1a1d87e1dc9559597969acc62ce717ee4dfeaa44d7eee",
|
||||
"sha256:4983ede548ffc3541bae49a82675996497348e55bafd1554dc4e4a5d6eda541a",
|
||||
"sha256:5315f4509c1476718a4825f45a203b82d7fdf2a6f5f0c8f166435975b1c9f7d4",
|
||||
"sha256:6cdfb1b49d5345f7c2b90d638822d16ba62dc82f7616e9b4caa10b72f3f16649",
|
||||
"sha256:7b325f12635598c604690efd7a0197d0b94b7d7778498e76e0710cd582fd1c7a",
|
||||
"sha256:8d3b0e3b8626615826f9a626548057c5275a9733512b137984a68ba1598d3d2f",
|
||||
"sha256:8f8631160c79f53081bd23446525db0bc4c5616f78d04021e6e434b286493fd7",
|
||||
"sha256:912de10965f3dc89da23936f1cc4ed60764f712e5fa603a09dd904f88c996760",
|
||||
"sha256:b010c07b975fe853c65d7bbe9d4ac62f1c69086750a574f6292597763781ba18",
|
||||
"sha256:c908c10505904c48081a5415a1e295d8403e353e0c14c42b6d67f8f97fae6616",
|
||||
"sha256:c94dd3807c0c0610f7c76f078119f4ea48235a953512752b9175f9f98f5ae2bd",
|
||||
"sha256:ce65dee7594a84c466e79d7fb7d3303e7295d16a83c22c7c4037071b059e2c21",
|
||||
"sha256:eaa9cfcb221a8a4c2889be6f93da141ac777eb8819f077e1d09fb12d00a09a93",
|
||||
"sha256:f3376bc31bad66d46d44b4e6522c5c21976bf9bca4ef5987bb2bf727f4506cbb",
|
||||
"sha256:f9202fa138544e13a4ec1a6792c35834250a85958fde1251b6a22e07d1260ae7"
|
||||
],
|
||||
"version": "==1.3.5"
|
||||
},
|
||||
"ujson": {
|
||||
"hashes": [
|
||||
"sha256:f66073e5506e91d204ab0c614a148d5aa938bdbf104751be66f8ad7a222f5f86"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.35"
|
||||
},
|
||||
"urllib3": {
|
||||
"hashes": [
|
||||
"sha256:2393a695cd12afedd0dcb26fe5d50d0cf248e5a66f75dbd89a3d4eb333a61af4",
|
||||
"sha256:a637e5fae88995b256e3409dc4d52c2e2e0ba32c42a6365fee8bbd2238de3cfb"
|
||||
],
|
||||
"version": "==1.24.3"
|
||||
},
|
||||
"uvicorn": {
|
||||
"hashes": [
|
||||
"sha256:c10da7a54a6552279870900c881a2f1726314e2dd6270d4d3f9251683c643783"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.7.1"
|
||||
},
|
||||
"uvloop": {
|
||||
"hashes": [
|
||||
"sha256:0fcd894f6fc3226a962ee7ad895c4f52e3f5c3c55098e21efb17c071849a0573",
|
||||
"sha256:2f31de1742c059c96cb76b91c5275b22b22b965c886ee1fced093fa27dde9e64",
|
||||
"sha256:459e4649fcd5ff719523de33964aa284898e55df62761e7773d088823ccbd3e0",
|
||||
"sha256:67867aafd6e0bc2c30a079603a85d83b94f23c5593b3cc08ec7e58ac18bf48e5",
|
||||
"sha256:8c200457e6847f28d8bb91c5e5039d301716f5f2fce25646f5fb3fd65eda4a26",
|
||||
"sha256:958906b9ca39eb158414fbb7d6b8ef1b7aee4db5c8e8e5d00fcbb69a1ce9dca7",
|
||||
"sha256:ac1dca3d8f3ef52806059e81042ee397ac939e5a86c8a3cea55d6b087db66115",
|
||||
"sha256:b284c22d8938866318e3b9d178142b8be316c52d16fcfe1560685a686718a021",
|
||||
"sha256:c48692bf4587ce281d641087658eca275a5ad3b63c78297bbded96570ae9ce8f",
|
||||
"sha256:fefc3b2b947c99737c348887db2c32e539160dcbeb7af9aa6b53db7a283538fe"
|
||||
],
|
||||
"version": "==0.12.2"
|
||||
},
|
||||
"wcwidth": {
|
||||
"hashes": [
|
||||
"sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e",
|
||||
"sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c"
|
||||
],
|
||||
"version": "==0.1.7"
|
||||
},
|
||||
"webencodings": {
|
||||
"hashes": [
|
||||
"sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78",
|
||||
"sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"
|
||||
],
|
||||
"version": "==0.5.1"
|
||||
},
|
||||
"websockets": {
|
||||
"hashes": [
|
||||
"sha256:04b42a1b57096ffa5627d6a78ea1ff7fad3bc2c0331ffc17bc32a4024da7fea0",
|
||||
"sha256:08e3c3e0535befa4f0c4443824496c03ecc25062debbcf895874f8a0b4c97c9f",
|
||||
"sha256:10d89d4326045bf5e15e83e9867c85d686b612822e4d8f149cf4840aab5f46e0",
|
||||
"sha256:232fac8a1978fc1dead4b1c2fa27c7756750fb393eb4ac52f6bc87ba7242b2fa",
|
||||
"sha256:4bf4c8097440eff22bc78ec76fe2a865a6e658b6977a504679aaf08f02c121da",
|
||||
"sha256:51642ea3a00772d1e48fb0c492f0d3ae3b6474f34d20eca005a83f8c9c06c561",
|
||||
"sha256:55d86102282a636e195dad68aaaf85b81d0bef449d7e2ef2ff79ac450bb25d53",
|
||||
"sha256:564d2675682bd497b59907d2205031acbf7d3fadf8c763b689b9ede20300b215",
|
||||
"sha256:5d13bf5197a92149dc0badcc2b699267ff65a867029f465accfca8abab95f412",
|
||||
"sha256:5eda665f6789edb9b57b57a159b9c55482cbe5b046d7db458948370554b16439",
|
||||
"sha256:5edb2524d4032be4564c65dc4f9d01e79fe8fad5f966e5b552f4e5164fef0885",
|
||||
"sha256:79691794288bc51e2a3b8de2bc0272ca8355d0b8503077ea57c0716e840ebaef",
|
||||
"sha256:7fcc8681e9981b9b511cdee7c580d5b005f3bb86b65bde2188e04a29f1d63317",
|
||||
"sha256:8e447e05ec88b1b408a4c9cde85aa6f4b04f06aa874b9f0b8e8319faf51b1fee",
|
||||
"sha256:90ea6b3e7787620bb295a4ae050d2811c807d65b1486749414f78cfd6fb61489",
|
||||
"sha256:9e13239952694b8b831088431d15f771beace10edfcf9ef230cefea14f18508f",
|
||||
"sha256:d40f081187f7b54d7a99d8a5c782eaa4edc335a057aa54c85059272ed826dc09",
|
||||
"sha256:e1df1a58ed2468c7b7ce9a2f9752a32ad08eac2bcd56318625c3647c2cd2da6f",
|
||||
"sha256:e98d0cec437097f09c7834a11c69d79fe6241729b23f656cfc227e93294fc242",
|
||||
"sha256:f8d59627702d2ff27cb495ca1abdea8bd8d581de425c56e93bff6517134e0a9b",
|
||||
"sha256:fc30cdf2e949a2225b012a7911d1d031df3d23e99b7eda7dfc982dc4a860dae9"
|
||||
],
|
||||
"version": "==7.0"
|
||||
},
|
||||
"widgetsnbextension": {
|
||||
"hashes": [
|
||||
"sha256:14b2c65f9940c9a7d3b70adbe713dbd38b5ec69724eebaba034d1036cf3d4740",
|
||||
"sha256:fa618be8435447a017fd1bf2c7ae922d0428056cfc7449f7a8641edf76b48265"
|
||||
],
|
||||
"version": "==3.4.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
15
README.md
15
README.md
@@ -63,8 +63,19 @@ The key features are:
|
||||
|
||||
---
|
||||
|
||||
"*If you're looking to learn one **modern framework** for building REST APIs, check out **FastAPI** [...] It's fast, easy to use and easy to learn [...]*"
|
||||
|
||||
"*We've switched over to **FastAPI** for our **APIs** [...] I think you'll like it [...]*"
|
||||
|
||||
<div style="text-align: right; margin-right: 10%;">Ines Montani - Matthew Honnibal - <strong><a href="https://explosion.ai" target="_blank">Explosion AI</a> founders - <a href="https://spacy.io" target="_blank">spaCy</a> creators</strong> <a href="https://twitter.com/_inesmontani/status/1144173225322143744" target="_blank"><small>(ref)</small></a> - <a href="https://twitter.com/honnibal/status/1144031421859655680" target="_blank"><small>(ref)</small></a></div>
|
||||
|
||||
---
|
||||
|
||||
"*We adopted the **FastAPI** library to spawn a **REST** server that can be queried to obtain **predictions**. [for Ludwig]*"
|
||||
|
||||
<div style="text-align: right; margin-right: 10%;">Piero Molino, Yaroslav Dudin, and Sai Sumanth Miryala - <strong>Uber</strong> <a href="https://eng.uber.com/ludwig-v0-2/" target="_blank"><small>(ref)</small></a></div>
|
||||
|
||||
---
|
||||
|
||||
## Requirements
|
||||
|
||||
@@ -79,13 +90,13 @@ FastAPI stands on the shoulders of giants:
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
$ pip install fastapi
|
||||
pip install fastapi
|
||||
```
|
||||
|
||||
You will also need an ASGI server, for production such as <a href="http://www.uvicorn.org" target="_blank">Uvicorn</a> or <a href="https://gitlab.com/pgjones/hypercorn" target="_blank">Hypercorn</a>.
|
||||
|
||||
```bash
|
||||
$ pip install uvicorn
|
||||
pip install uvicorn
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
@@ -142,12 +142,12 @@ Another big feature required by APIs is <abbr title="reading and converting to P
|
||||
|
||||
Webargs is a tool that was made to provide that on top of several frameworks, including Flask.
|
||||
|
||||
It uses Marshmallow underneath to do the data validation. And it was created by the same guys.
|
||||
It uses Marshmallow underneath to do the data validation. And it was created by the same developers.
|
||||
|
||||
It's a great tool and I have used it a lot too, before having **FastAPI**.
|
||||
|
||||
!!! info
|
||||
Webargs was created by the same Marshmallow guys.
|
||||
Webargs was created by the same Marshmallow developers.
|
||||
|
||||
!!! check "Inspired **FastAPI** to"
|
||||
Have automatic validation of incoming request data.
|
||||
@@ -171,7 +171,7 @@ But then, we have again the problem of having a micro-syntax, inside of a Python
|
||||
The editor can't help much with that. And if we modify parameters or Marshmallow schemas and forget to also modify that YAML docstring, the generated schema would be obsolete.
|
||||
|
||||
!!! info
|
||||
APISpec was created by the same Marshmallow guys.
|
||||
APISpec was created by the same Marshmallow developers.
|
||||
|
||||
|
||||
!!! check "Inspired **FastAPI** to"
|
||||
@@ -198,7 +198,7 @@ Using it led to the creation of several Flask full-stack generators. These are t
|
||||
And these same full-stack generators were the base of the <a href="/project-generation/" target="_blank">**FastAPI** project generator</a>.
|
||||
|
||||
!!! info
|
||||
Flask-apispec was created by the same Marshmallow guys.
|
||||
Flask-apispec was created by the same Marshmallow developers.
|
||||
|
||||
!!! check "Inspired **FastAPI** to"
|
||||
Generate the OpenAPI schema automatically, from the same code that defines serialization and validation.
|
||||
@@ -240,7 +240,7 @@ It was one of the first extremely fast Python frameworks based on `asyncio`. It
|
||||
|
||||
Falcon is another high performance Python framework, it is designed to be minimal, and work as the foundation of other frameworks like Hug.
|
||||
|
||||
It uses the previous standard for Python web frameworks (WSGI) which is synchronous, so it can't handle Websockets and other use cases. Nevertheless, it also has a very good performance.
|
||||
It uses the previous standard for Python web frameworks (WSGI) which is synchronous, so it can't handle WebSockets and other use cases. Nevertheless, it also has a very good performance.
|
||||
|
||||
It is designed to have functions that receive two parameters, one "request" and one "response". Then you "read" parts from the request, and "write" parts to the response. Because of this design, it is not possible to declare request parameters and bodies with standard Python type hints as function parameters.
|
||||
|
||||
@@ -249,6 +249,10 @@ So, data validation, serialization, and documentation, have to be done in code,
|
||||
!!! check "Inspired **FastAPI** to"
|
||||
Find ways to get great performance.
|
||||
|
||||
Along with Hug (as Hug is based on Falcon) inspired **FastAPI** to declare a `response` parameter in functions.
|
||||
|
||||
Although in FastAPI it's optional, and is used mainly to set headers, cookies, and alternative status codes.
|
||||
|
||||
### <a href="https://moltenframework.com/" target="_blank">Molten</a>
|
||||
|
||||
I discovered Molten in the first stages of building **FastAPI**. And it has quite similar ideas:
|
||||
@@ -292,6 +296,7 @@ As it is based on the previous standard for synchronous Python web frameworks (W
|
||||
|
||||
Hug helped inspiring **FastAPI** to use Python type hints to declare parameters, and to generate a schema defining the API automatically.
|
||||
|
||||
Hug inspired **FastAPI** to declare a `response` parameter in functions to set headers and cookies.
|
||||
|
||||
### <a href="https://github.com/encode/apistar" target="_blank">APIStar</a> (<= 0.5)
|
||||
|
||||
|
||||
@@ -170,32 +170,33 @@ Now, from a developer's perspective, here are several things to have in mind whi
|
||||
* So, the certificate and encryption handling is done before HTTP.
|
||||
* TCP doesn't know about "domains". Only about IP addresses.
|
||||
* The information about the specific domain requested goes in the HTTP data.
|
||||
* The HTTPS certificates "certificate" a certain domain, but the protocol and encryption happen at the TCP level, before knowing which domain is being dealt with.
|
||||
* The HTTPS certificates "certify" a certain domain, but the protocol and encryption happen at the TCP level, before knowing which domain is being dealt with.
|
||||
* By default, that would mean that you can only have one HTTPS certificate per IP address.
|
||||
* No matter how big is your server and how small each application you have there might be. But...
|
||||
* No matter how big your server is or how small each application you have on it might be.
|
||||
* There is a solution to this, however.
|
||||
* There's an extension to the TLS protocol (the one handling the encryption at the TCP level, before HTTP) called <a href="https://en.wikipedia.org/wiki/Server_Name_Indication" target="_blank"><abbr title="Server Name Indication">SNI</abbr></a>.
|
||||
* This SNI extension allows one single server (with a single IP address) to have several HTTPS certificates and server multiple HTTPS domains/applications.
|
||||
* For this to work, a single component (program) running in the server, listening in the public IP address, must have all the HTTPS certificates in the server.
|
||||
* After having a secure connection, the communication protocol is the same HTTP.
|
||||
* It goes encrypted, but the encrypted contents are the same HTTP protocol.
|
||||
* This SNI extension allows one single server (with a single IP address) to have several HTTPS certificates and serve multiple HTTPS domains/applications.
|
||||
* For this to work, a single component (program) running on the server, listening on the public IP address, must have all the HTTPS certificates in the server.
|
||||
* After obtaining a secure connection, the communication protocol is still HTTP.
|
||||
* The contents are encrypted, even though they are being sent with the HTTP protocol.
|
||||
|
||||
|
||||
It is a common practice to have one program/HTTP server running in the server (the machine, host, etc) and managing all the HTTPS parts, sending the decrypted HTTP requests to the actual HTTP application running in the same server (the **FastAPI** application, in this case), take the HTTP response from the application, encrypt it using the appropriate certificate and sending it back to the client using HTTPS. This server is ofter called a <a href="https://en.wikipedia.org/wiki/TLS_termination_proxy" target="_blank">TLS Termination Proxy</a>.
|
||||
It is a common practice to have one program/HTTP server running on the server (the machine, host, etc.) and managing all the HTTPS parts : sending the decrypted HTTP requests to the actual HTTP application running in the same server (the **FastAPI** application, in this case), take the HTTP response from the application, encrypt it using the appropriate certificate and sending it back to the client using HTTPS. This server is often called a <a href="https://en.wikipedia.org/wiki/TLS_termination_proxy" target="_blank">TLS Termination Proxy</a>.
|
||||
|
||||
|
||||
### Let's Encrypt
|
||||
|
||||
Up to some years ago, these HTTPS certificates were sold by trusted third-parties.
|
||||
Before Let's Encrypt, these HTTPS certificates were sold by trusted third-parties.
|
||||
|
||||
The process to acquire one of these certificates used to be cumbersome, require quite some paperwork and the certificates were quite expensive.
|
||||
|
||||
But then <a href="https://letsencrypt.org/" target="_blank">Let's Encrypt</a> was created.
|
||||
|
||||
It is a project from the Linux Foundation. It provides HTTPS certificates for free. In an automated way. These certificates use all the standard cryptographic security, and are short lived (about 3 months), so, the security is actually increased, by reducing their lifespan.
|
||||
It is a project from the Linux Foundation. It provides HTTPS certificates for free. In an automated way. These certificates use all the standard cryptographic security, and are short lived (about 3 months), so the security is actually better because of their reduced lifespan.
|
||||
|
||||
The domain's are securely verified and the certificates are generated automatically. This also allows automatizing the renewal of these certificates.
|
||||
The domains are securely verified and the certificates are generated automatically. This also allows automating the renewal of these certificates.
|
||||
|
||||
The idea is to automatize the acquisition and renewal of these certificates, so that you can have secure HTTPS, free, forever.
|
||||
The idea is to automate the acquisition and renewal of these certificates, so that you can have secure HTTPS, for free, forever.
|
||||
|
||||
|
||||
### Traefik
|
||||
@@ -204,7 +205,7 @@ The idea is to automatize the acquisition and renewal of these certificates, so
|
||||
|
||||
It has integration with Let's Encrypt. So, it can handle all the HTTPS parts, including certificate acquisition and renewal.
|
||||
|
||||
It also has integrations with Docker. So, you can declare your domains in each application configurations and have it read those configurations, generate the HTTPS certificates and serve HTTPS to your application, all automatically. Without requiring any change in its configuration.
|
||||
It also has integrations with Docker. So, you can declare your domains in each application configurations and have it read those configurations, generate the HTTPS certificates and serve HTTPS to your application automatically, without requiring any change in its configuration.
|
||||
|
||||
---
|
||||
|
||||
@@ -230,7 +231,7 @@ It is designed to be integrated with this Docker Swarm cluster with Traefik and
|
||||
|
||||
You can generate a project in about 2 min.
|
||||
|
||||
The generated project has instructions to deploy it, doing it takes other 2 min.
|
||||
The generated project has instructions to deploy it, doing it takes another 2 min.
|
||||
|
||||
|
||||
## Alternatively, deploy **FastAPI** without Docker
|
||||
|
||||
70
docs/external-links.md
Normal file
70
docs/external-links.md
Normal file
@@ -0,0 +1,70 @@
|
||||
**FastAPI** has a great community constantly growing.
|
||||
|
||||
There are many posts, articles, tools, and projects, related to **FastAPI**.
|
||||
|
||||
Here's an incomplete list of some of them.
|
||||
|
||||
!!! tip
|
||||
If you have an article, project, tool, or anything related to **FastAPI** that is not yet listed here, create a <a href="https://github.com/tiangolo/fastapi/edit/master/docs/external-links.md" target="_blank">Pull Request adding it</a>.
|
||||
|
||||
## Articles
|
||||
|
||||
### English
|
||||
|
||||
* <a href="https://medium.com/@williamhayes/fastapi-starlette-debug-vs-prod-5f7561db3a59" target="_blank">FastAPI/Starlette debug vs prod</a> by <a href="https://medium.com/@williamhayes" target="_blank">William Hayes</a>.
|
||||
|
||||
* <a href="https://medium.com/data-rebels/fastapi-google-as-an-external-authentication-provider-3a527672cf33" target="_blank">FastAPI — Google as an external authentication provider</a> by <a href="https://medium.com/@nils_29588" target="_blank">Nils de Bruin</a>.
|
||||
|
||||
* <a href="https://medium.com/data-rebels/fastapi-how-to-add-basic-and-cookie-authentication-a45c85ef47d3" target="_blank">FastAPI — How to add basic and cookie authentication</a> by <a href="https://medium.com/@nils_29588" target="_blank">Nils de Bruin</a>.
|
||||
|
||||
* <a href="https://dev.to/errietta/introduction-to-the-fastapi-python-framework-2n10" target="_blank">Introduction to the fastapi python framework</a> by <a href="https://dev.to/errietta" target="_blank">Errieta Kostala</a>.
|
||||
|
||||
* <a href="http://nickc1.github.io/api,/scikit-learn/2019/01/10/scikit-fastapi.html" target="_blank">FastAPI and Scikit-Learn: Easily Deploy Models</a> by <a href="http://nickc1.github.io/" target="_blank">Nick Cortale</a>.
|
||||
|
||||
* <a href="https://medium.com/data-rebels/fastapi-authentication-revisited-enabling-api-key-authentication-122dc5975680" target="_blank">FastAPI authentication revisited: Enabling API key authentication</a> by <a href="https://medium.com/@nils_29588" target="_blank">Nils de Bruin</a>.
|
||||
|
||||
* <a href="https://blog.bartab.fr/fastapi-logging-on-the-fly/" target="_blank">FastAPI, a simple use case on logging</a> by <a href="https://blog.bartab.fr/" target="_blank">@euri10</a>.
|
||||
|
||||
* <a href="https://medium.com/@nico.axtmann95/deploying-a-scikit-learn-model-with-onnx-und-fastapi-1af398268915" target="_blank">Deploying a scikit-learn model with ONNX and FastAPI</a> by <a href="https://www.linkedin.com/in/nico-axtmann" target="_blank">Nico Axtmann</a>.
|
||||
|
||||
* <a href="https://geekflare.com/python-asynchronous-web-frameworks/" target="_blank">Top 5 Asynchronous Web Frameworks for Python</a> by <a href="https://geekflare.com/author/ankush/" target="_blank">Ankush Thakur</a> on <a href="https://geekflare.com" target="_blank">GeekFlare</a>.
|
||||
|
||||
* <a href="https://medium.com/@gntrm/jwt-authentication-with-fastapi-and-aws-cognito-1333f7f2729e" target="_blank">JWT Authentication with FastAPI and AWS Cognito</a> by <a href="https://twitter.com/gntrm" target="_blank">Johannes Gontrum</a>.
|
||||
|
||||
* <a href="https://towardsdatascience.com/how-to-deploy-a-machine-learning-model-dc51200fe8cf" target="_blank">How to Deploy a Machine Learning Model</a> by <a href="https://www.linkedin.com/in/mgrootendorst/" target="_blank">Maarten Grootendorst</a> on <a href="https://towardsdatascience.com/" target="_blank">Towards Data Science</a>.
|
||||
|
||||
* <a href="https://eng.uber.com/ludwig-v0-2/" target="_blank">Uber: Ludwig v0.2 Adds New Features and Other Improvements to its Deep Learning Toolbox [including a FastAPI server]</a> on <a href="https://eng.uber.com" target="_blank">Uber Engineering</a>.
|
||||
|
||||
* <a href="https://gitlab.com/euri10/fastapi_cheatsheet" target="_blank">A FastAPI and Swagger UI visual cheatsheet</a> by <a href="https://gitlab.com/euri10" target="_blank">@euri10</a>
|
||||
|
||||
### Japanese
|
||||
|
||||
* <a href="https://qiita.com/mtitg/items/47770e9a562dd150631d" target="_blank">FastAPI|DB接続してCRUDするPython製APIサーバーを構築</a> by <a href="https://qiita.com/mtitg" target="_blank">@mtitg</a>.
|
||||
|
||||
* <a href="https://qiita.com/ryoryomaru/items/59958ed385b3571d50de" target="_blank">python製の最新APIフレームワーク FastAPI を触ってみた</a> by <a href="https://qiita.com/ryoryomaru" target="_blank">@ryoryomaru</a>.
|
||||
|
||||
* <a href="https://qiita.com/angel_katayoku/items/0e1f5dbbe62efc612a78" target="_blank">FastAPIでCORSを回避</a> by <a href="https://qiita.com/angel_katayoku" target="_blank">@angel_katayoku</a>.
|
||||
|
||||
* <a href="https://qiita.com/angel_katayoku/items/4fbc1a4e2b33fa2237d2" target="_blank">FastAPIをMySQLと接続してDockerで管理してみる</a> by <a href="https://qiita.com/angel_katayoku" target="_blank">@angel_katayoku</a>.
|
||||
|
||||
* <a href="https://qiita.com/angel_katayoku/items/8a458a8952f50b73f420" target="_blank">FastAPIでPOSTされたJSONのレスポンスbodyを受け取る</a> by <a href="https://qiita.com/angel_katayoku" target="_blank">@angel_katayoku</a>.
|
||||
|
||||
* <a href="https://qiita.com/hikarut/items/b178af2e2440c67c6ac4" target="_blank">フロントエンド開発者向けのDockerによるPython開発環境構築</a> by <a href="https://qiita.com/hikarut" target="_blank">Hikaru Takahashi</a>.
|
||||
|
||||
### Chinese
|
||||
|
||||
* <a href="https://cloud.tencent.com/developer/article/1431448" target="_blank">使用FastAPI框架快速构建高性能的api服务</a> by <a href="https://cloud.tencent.com/developer/user/5471722" target="_blank">逍遥散人</a>.
|
||||
|
||||
### Vietnamese
|
||||
|
||||
* <a href="https://fullstackstation.com/fastapi-trien-khai-bang-docker/" target="_blank">FASTAPI: TRIỂN KHAI BẰNG DOCKER</a> by <a href="https://fullstackstation.com/author/figonking/" target="_blank">Nguyễn Nhân</a>.
|
||||
|
||||
### Russian
|
||||
|
||||
* <a href="https://habr.com/ru/post/454440/" target="_blank">Мелкая питонячая радость #2: Starlette - Солидная примочка – FastAPI</a> by <a href="https://habr.com/ru/users/57uff3r/" target="_blank">Andrey Korchak</a>.
|
||||
|
||||
* <a href="https://habr.com/ru/post/478620/" target="_blank">Почему Вы должны попробовать FastAPI?</a> by <a href="https://github.com/prostomarkeloff" target="_blank">prostomarkeloff</a>.
|
||||
|
||||
## Podcasts
|
||||
|
||||
* <a href="https://pythonbytes.fm/episodes/show/123/time-to-right-the-py-wrongs?time_in_sec=855" target="_blank">FastAPI on PythonBytes</a> by <a href="https://pythonbytes.fm/" target="_blank">Python Bytes FM</a>.
|
||||
@@ -37,7 +37,7 @@ from datetime import date
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
# Declare a variable as an str
|
||||
# Declare a variable as a str
|
||||
# and get editor support inside the function
|
||||
def main(user_id: str):
|
||||
return user_id
|
||||
|
||||
@@ -70,10 +70,8 @@ You can let me know:
|
||||
|
||||
## Vote for FastAPI
|
||||
|
||||
You can vote to include FastAPI in several "awesome lists":
|
||||
|
||||
* <a href="https://github.com/vinta/awesome-python/pull/1209" target="_blank">Vote to include **FastAPI** in `awesome-python`</a>.
|
||||
* <a href="https://github.com/timofurrer/awesome-asyncio/pull/43" target="_blank">Vote to include **FastAPI** in `awesome-asyncio`</a>.
|
||||
* <a href="https://www.slant.co/options/34241/~fastapi-review" target="_blank">Vote for **FastAPI** in Slant</a>.
|
||||
|
||||
## Help others with issues in GitHub
|
||||
|
||||
|
||||
BIN
docs/img/github-social-preview.png
Normal file
BIN
docs/img/github-social-preview.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 57 KiB |
98
docs/img/github-social-preview.svg
Normal file
98
docs/img/github-social-preview.svg
Normal file
@@ -0,0 +1,98 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
id="svg8"
|
||||
version="1.1"
|
||||
viewBox="0 0 338.66665 169.33332"
|
||||
height="169.33333mm"
|
||||
width="338.66666mm"
|
||||
sodipodi:docname="github-social-preview.svg"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)"
|
||||
inkscape:export-filename="/home/user/code/fastapi/docs/img/github-social-preview.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96">
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1025"
|
||||
id="namedview9"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.52249777"
|
||||
inkscape:cx="565.37328"
|
||||
inkscape:cy="403.61034"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg8" />
|
||||
<defs
|
||||
id="defs2" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<rect
|
||||
style="opacity:0.98000004;fill:#ffffff;fill-opacity:1;stroke-width:0.26458332"
|
||||
id="rect853"
|
||||
width="338.66666"
|
||||
height="169.33333"
|
||||
x="-1.0833333e-05"
|
||||
y="0.71613133"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96" />
|
||||
<g
|
||||
transform="matrix(0.73259569,0,0,0.73259569,64.842852,-4.5763945)"
|
||||
id="layer1">
|
||||
<path
|
||||
style="opacity:0.98000004;fill:#009688;fill-opacity:1;stroke-width:3.20526505"
|
||||
id="path817"
|
||||
d="m 1.4365174,55.50154 c -17.6610514,0 -31.9886064,14.327532 -31.9886064,31.988554 0,17.661036 14.327555,31.988586 31.9886064,31.988586 17.6609756,0 31.9885196,-14.32755 31.9885196,-31.988586 0,-17.661022 -14.327544,-31.988554 -31.9885196,-31.988554 z m -1.66678692,57.63069 V 93.067264 H -11.384533 L 4.6417437,61.847974 V 81.912929 H 15.379405 Z"
|
||||
inkscape:connector-curvature="0" />
|
||||
<text
|
||||
id="text979"
|
||||
y="114.91215"
|
||||
x="52.115433"
|
||||
style="font-style:normal;font-weight:normal;font-size:79.71511078px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#009688;fill-opacity:1;stroke:none;stroke-width:1.99287772"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;fill:#009688;fill-opacity:1;stroke-width:1.99287772"
|
||||
y="114.91215"
|
||||
x="52.115433"
|
||||
id="tspan977">FastAPI</tspan></text>
|
||||
</g>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:10.58333302px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
|
||||
x="169.60979"
|
||||
y="119.20409"
|
||||
id="text851"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan849"
|
||||
x="169.60979"
|
||||
y="119.20409"
|
||||
style="font-style:italic;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Roboto;-inkscape-font-specification:'Roboto Italic';text-align:center;text-anchor:middle;stroke-width:0.26458332">High performance, easy to learn,</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="169.60979"
|
||||
y="132.53661"
|
||||
style="font-style:italic;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Roboto;-inkscape-font-specification:'Roboto Italic';text-align:center;text-anchor:middle;stroke-width:0.26458332"
|
||||
id="tspan855">fast to code, ready for production</tspan></text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 77 KiB |
BIN
docs/img/tutorial/openapi-callbacks/image01.png
Normal file
BIN
docs/img/tutorial/openapi-callbacks/image01.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 97 KiB |
BIN
docs/img/tutorial/path-params/image03.png
Normal file
BIN
docs/img/tutorial/path-params/image03.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 82 KiB |
@@ -63,8 +63,19 @@ The key features are:
|
||||
|
||||
---
|
||||
|
||||
"*If you're looking to learn one **modern framework** for building REST APIs, check out **FastAPI** [...] It's fast, easy to use and easy to learn [...]*"
|
||||
|
||||
"*We've switched over to **FastAPI** for our **APIs** [...] I think you'll like it [...]*"
|
||||
|
||||
<div style="text-align: right; margin-right: 10%;">Ines Montani - Matthew Honnibal - <strong><a href="https://explosion.ai" target="_blank">Explosion AI</a> founders - <a href="https://spacy.io" target="_blank">spaCy</a> creators</strong> <a href="https://twitter.com/_inesmontani/status/1144173225322143744" target="_blank"><small>(ref)</small></a> - <a href="https://twitter.com/honnibal/status/1144031421859655680" target="_blank"><small>(ref)</small></a></div>
|
||||
|
||||
---
|
||||
|
||||
"*We adopted the **FastAPI** library to spawn a **REST** server that can be queried to obtain **predictions**. [for Ludwig]*"
|
||||
|
||||
<div style="text-align: right; margin-right: 10%;">Piero Molino, Yaroslav Dudin, and Sai Sumanth Miryala - <strong>Uber</strong> <a href="https://eng.uber.com/ludwig-v0-2/" target="_blank"><small>(ref)</small></a></div>
|
||||
|
||||
---
|
||||
|
||||
## Requirements
|
||||
|
||||
@@ -79,13 +90,13 @@ FastAPI stands on the shoulders of giants:
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
$ pip install fastapi
|
||||
pip install fastapi
|
||||
```
|
||||
|
||||
You will also need an ASGI server, for production such as <a href="http://www.uvicorn.org" target="_blank">Uvicorn</a> or <a href="https://gitlab.com/pgjones/hypercorn" target="_blank">Hypercorn</a>.
|
||||
|
||||
```bash
|
||||
$ pip install uvicorn
|
||||
pip install uvicorn
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
@@ -1,4 +1,347 @@
|
||||
## Next release
|
||||
## Latest changes
|
||||
|
||||
## 0.45.0
|
||||
|
||||
* Add support for OpenAPI Callbacks:
|
||||
* New docs: [OpenAPI Callbacks](https://fastapi.tiangolo.com/tutorial/openapi-callbacks/).
|
||||
* Refactor generation of `operationId`s to be valid Python names (also valid variables in most languages).
|
||||
* Add `default_response_class` parameter to `APIRouter`.
|
||||
* Original PR [#722](https://github.com/tiangolo/fastapi/pull/722) by [@booooh](https://github.com/booooh).
|
||||
* Refactor logging to use the same logger everywhere, update log strings and levels. PR [#781](https://github.com/tiangolo/fastapi/pull/781).
|
||||
* Add article to [External Links](https://fastapi.tiangolo.com/external-links/): [Почему Вы должны попробовать FastAPI?](https://habr.com/ru/post/478620/). PR [#766](https://github.com/tiangolo/fastapi/pull/766) by [@prostomarkeloff](https://github.com/prostomarkeloff).
|
||||
* Remove gender bias in docs for handling errors. PR [#780](https://github.com/tiangolo/fastapi/pull/780). Original idea in PR [#761](https://github.com/tiangolo/fastapi/pull/761) by [@classywhetten](https://github.com/classywhetten).
|
||||
* Rename docs and references to `body-schema` to `body-fields` to keep in line with Pydantic. PR [#746](https://github.com/tiangolo/fastapi/pull/746) by [@prostomarkeloff](https://github.com/prostomarkeloff).
|
||||
|
||||
## 0.44.1
|
||||
|
||||
* Add GitHub social preview images to git. PR [#752](https://github.com/tiangolo/fastapi/pull/752).
|
||||
* Update PyPI "trove classifiers". PR [#751](https://github.com/tiangolo/fastapi/pull/751).
|
||||
* Add full support for Python 3.8. Enable Python 3.8 in full in Travis. PR [749](https://github.com/tiangolo/fastapi/pull/749).
|
||||
* Update "new issue" templates. PR [#749](https://github.com/tiangolo/fastapi/pull/749).
|
||||
* Fix serialization of errors for exotic Pydantic types. PR [#748](https://github.com/tiangolo/fastapi/pull/748) by [@dmontagu](https://github.com/dmontagu).
|
||||
|
||||
## 0.44.0
|
||||
|
||||
* Add GitHub action [Issue Manager](https://github.com/tiangolo/issue-manager). PR [#742](https://github.com/tiangolo/fastapi/pull/742).
|
||||
* Fix typos in docs. PR [734](https://github.com/tiangolo/fastapi/pull/734) by [@bundabrg](https://github.com/bundabrg).
|
||||
* Fix usage of `custom_encoder` in `jsonable_encoder`. PR [#715](https://github.com/tiangolo/fastapi/pull/715) by [@matrixise](https://github.com/matrixise).
|
||||
* Fix invalid XML example. PR [710](https://github.com/tiangolo/fastapi/pull/710) by [@OcasoProtal](https://github.com/OcasoProtal).
|
||||
* Fix typos and update wording in deployment docs. PR [#700](https://github.com/tiangolo/fastapi/pull/700) by [@marier-nico](https://github.com/tiangolo/fastapi/pull/700).
|
||||
* Add note about dependencies in `APIRouter` docs. PR [#698](https://github.com/tiangolo/fastapi/pull/698) by [@marier-nico](https://github.com/marier-nico).
|
||||
* Add support for async class methods as dependencies [#681](https://github.com/tiangolo/fastapi/pull/681) by [@frankie567](https://github.com/frankie567).
|
||||
* Add FastAPI with Swagger UI cheatsheet to external links. PR [#671](https://github.com/tiangolo/fastapi/pull/671) by [@euri10](https://github.com/euri10).
|
||||
* Fix typo in HTTP protocol in CORS example. PR [#647](https://github.com/tiangolo/fastapi/pull/647) by [@forestmonster](https://github.com/forestmonster).
|
||||
* Add support for Pydantic versions `1.0.0` and above, with temporary (deprecated) backwards compatibility for Pydantic `0.32.2`. PR [#646](https://github.com/tiangolo/fastapi/pull/646) by [@dmontagu](https://github.com/dmontagu).
|
||||
|
||||
## 0.43.0
|
||||
|
||||
* Update docs to reduce gender bias. PR [#645](https://github.com/tiangolo/fastapi/pull/645) by [@ticosax](https://github.com/ticosax).
|
||||
* Add docs about [overriding the `operationId` for all the *path operations*](https://fastapi.tiangolo.com/tutorial/path-operation-advanced-configuration/#using-the-path-operation-function-name-as-the-operationid) based on their function name. PR [#642](https://github.com/tiangolo/fastapi/pull/642) by [@SKalt](https://github.com/SKalt).
|
||||
* Fix validators in models generating an incorrect key order. PR [#637](https://github.com/tiangolo/fastapi/pull/637) by [@jaddison](https://github.com/jaddison).
|
||||
* Generate correct OpenAPI docs for responses with no content. PR [#621](https://github.com/tiangolo/fastapi/pull/621) by [@brotskydotcom](https://github.com/brotskydotcom).
|
||||
* Remove `$` from Bash code blocks in docs for consistency. PR [#613](https://github.com/tiangolo/fastapi/pull/613) by [@nstapelbroek](https://github.com/nstapelbroek).
|
||||
* Add docs for [self-serving docs' (Swagger UI) static assets](https://fastapi.tiangolo.com/tutorial/extending-openapi/#self-hosting-javascript-and-css-for-docs), e.g. to use the docs offline, or without Internet. Initial PR [#557](https://github.com/tiangolo/fastapi/pull/557) by [@svalouch](https://github.com/svalouch).
|
||||
* Fix `black` linting after upgrade. PR [#682](https://github.com/tiangolo/fastapi/pull/682) by [@frankie567](https://github.com/frankie567).
|
||||
|
||||
## 0.42.0
|
||||
|
||||
* Add dependencies with `yield`, a.k.a. exit steps, context managers, cleanup, teardown, ...
|
||||
* This allows adding extra code after a dependency is done. It can be used, for example, to close database connections.
|
||||
* Dependencies with `yield` can be normal or `async`, **FastAPI** will run normal dependencies in a threadpool.
|
||||
* They can be combined with normal dependencies.
|
||||
* It's possible to have arbitrary trees/levels of dependencies with `yield` and exit steps are handled in the correct order automatically.
|
||||
* It works by default in Python 3.7 or above. For Python 3.6, it requires the extra backport dependencies:
|
||||
* `async-exit-stack`
|
||||
* `async-generator`
|
||||
* New docs at [Dependencies with `yield`](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-with-yield/).
|
||||
* Updated database docs [SQL (Relational) Databases: Main **FastAPI** app](https://fastapi.tiangolo.com/tutorial/sql-databases/#main-fastapi-app).
|
||||
* PR [#595](https://github.com/tiangolo/fastapi/pull/595).
|
||||
* Fix `sitemap.xml` in website. PR [#598](https://github.com/tiangolo/fastapi/pull/598) by [@samuelcolvin](https://github.com/samuelcolvin).
|
||||
|
||||
## 0.41.0
|
||||
|
||||
* Upgrade required Starlette to `0.12.9`, the new range is `>=0.12.9,<=0.12.9`.
|
||||
* Add `State` to FastAPI apps at `app.state`.
|
||||
* PR [#593](https://github.com/tiangolo/fastapi/pull/593).
|
||||
* Improve handling of custom classes for `Request`s and `APIRoute`s.
|
||||
* This helps to more easily solve use cases like:
|
||||
* Reading a body before and/or after a request (equivalent to a middleware).
|
||||
* Run middleware-like code only for a subset of *path operations*.
|
||||
* Process a request before passing it to a *path operation function*. E.g. decompressing, deserializing, etc.
|
||||
* Processing a response after being generated by *path operation functions* but before returning it. E.g. adding custom headers, logging, adding extra metadata.
|
||||
* New docs section: [Custom Request and APIRoute class](https://fastapi.tiangolo.com/tutorial/custom-request-and-route/).
|
||||
* PR [#589](https://github.com/tiangolo/fastapi/pull/589) by [@dmontagu](https://github.com/dmontagu).
|
||||
* Fix preserving custom route class in routers when including other sub-routers. PR [#538](https://github.com/tiangolo/fastapi/pull/538) by [@dmontagu](https://github.com/dmontagu).
|
||||
|
||||
## 0.40.0
|
||||
|
||||
* Add notes to docs about installing `python-multipart` when using forms. PR [#574](https://github.com/tiangolo/fastapi/pull/574) by [@sliptonic](https://github.com/sliptonic).
|
||||
* Generate OpenAPI schemas in alphabetical order. PR [#554](https://github.com/tiangolo/fastapi/pull/554) by [@dmontagu](https://github.com/dmontagu).
|
||||
* Add support for truncating docstrings from *path operation functions*.
|
||||
* New docs at [Advanced description from docstring](https://fastapi.tiangolo.com/tutorial/path-operation-advanced-configuration/#advanced-description-from-docstring).
|
||||
* PR [#556](https://github.com/tiangolo/fastapi/pull/556) by [@svalouch](https://github.com/svalouch).
|
||||
* Fix `DOCTYPE` in HTML files generated for Swagger UI and ReDoc. PR [#537](https://github.com/tiangolo/fastapi/pull/537) by [@Trim21](https://github.com/Trim21).
|
||||
* Fix handling `4XX` responses overriding default `422` validation error responses. PR [#517](https://github.com/tiangolo/fastapi/pull/517) by [@tsouvarev](https://github.com/tsouvarev).
|
||||
* Fix typo in documentation for [Simple HTTP Basic Auth](https://fastapi.tiangolo.com/tutorial/security/http-basic-auth/#simple-http-basic-auth). PR [#514](https://github.com/tiangolo/fastapi/pull/514) by [@prostomarkeloff](https://github.com/prostomarkeloff).
|
||||
* Fix incorrect documentation example in [first steps](https://fastapi.tiangolo.com/tutorial/first-steps/). PR [#511](https://github.com/tiangolo/fastapi/pull/511) by [@IgnatovFedor](https://github.com/IgnatovFedor).
|
||||
* Add support for Swagger UI [initOauth](https://github.com/swagger-api/swagger-ui/blob/master/docs/usage/oauth2.md) settings with the parameter `swagger_ui_init_oauth`. PR [#499](https://github.com/tiangolo/fastapi/pull/499) by [@zamiramir](https://github.com/zamiramir).
|
||||
|
||||
## 0.39.0
|
||||
|
||||
* Allow path parameters to have default values (e.g. `None`) and discard them instead of raising an error.
|
||||
* This allows declaring a parameter like `user_id: str = None` that can be taken from a query parameter, but the same path operation can be included in a router with a path `/users/{user_id}`, in which case will be taken from the path and will be required.
|
||||
* PR [#464](https://github.com/tiangolo/fastapi/pull/464) by [@jonathanunderwood](https://github.com/jonathanunderwood).
|
||||
* Add support for setting a `default_response_class` in the `FastAPI` instance or in `include_router`. Initial PR [#467](https://github.com/tiangolo/fastapi/pull/467) by [@toppk](https://github.com/toppk).
|
||||
* Add support for type annotations using strings and `from __future__ import annotations`. PR [#451](https://github.com/tiangolo/fastapi/pull/451) by [@dmontagu](https://github.com/dmontagu).
|
||||
|
||||
## 0.38.1
|
||||
|
||||
* Fix incorrect `Request` class import. PR [#493](https://github.com/tiangolo/fastapi/pull/493) by [@kamalgill](https://github.com/kamalgill).
|
||||
|
||||
## 0.38.0
|
||||
|
||||
* Add recent articles to [External Links](https://fastapi.tiangolo.com/external-links/) and recent opinions. PR [#490](https://github.com/tiangolo/fastapi/pull/490).
|
||||
* Upgrade support range for Starlette to include `0.12.8`. The new range is `>=0.11.1,<=0.12.8"`. PR [#477](https://github.com/tiangolo/fastapi/pull/477) by [@dmontagu](https://github.com/dmontagu).
|
||||
* Upgrade support to Pydantic version 0.32.2 and update internal code to use it (breaking change). PR [#463](https://github.com/tiangolo/fastapi/pull/463) by [@dmontagu](https://github.com/dmontagu).
|
||||
|
||||
## 0.37.0
|
||||
|
||||
* Add support for custom route classes for advanced use cases. PR [#468](https://github.com/tiangolo/fastapi/pull/468) by [@dmontagu](https://github.com/dmontagu).
|
||||
* Allow disabling Google fonts in ReDoc. PR [#481](https://github.com/tiangolo/fastapi/pull/481) by [@b1-luettje](https://github.com/b1-luettje).
|
||||
* Fix security issue: when returning a sub-class of a response model and using `skip_defaults` it could leak information. PR [#485](https://github.com/tiangolo/fastapi/pull/485) by [@dmontagu](https://github.com/dmontagu).
|
||||
* Enable tests for Python 3.8-dev. PR [#465](https://github.com/tiangolo/fastapi/pull/465) by [@Jamim](https://github.com/Jamim).
|
||||
* Add support and tests for Pydantic dataclasses in `response_model`. PR [#454](https://github.com/tiangolo/fastapi/pull/454) by [@dconathan](https://github.com/dconathan).
|
||||
* Fix typo in OAuth2 JWT tutorial. PR [#447](https://github.com/tiangolo/fastapi/pull/447) by [@pablogamboa](https://github.com/pablogamboa).
|
||||
* Use the `media_type` parameter in `Body()` params to set the media type in OpenAPI for `requestBody`. PR [#439](https://github.com/tiangolo/fastapi/pull/439) by [@divums](https://github.com/divums).
|
||||
* Add article [Deploying a scikit-learn model with ONNX and FastAPI](https://medium.com/@nico.axtmann95/deploying-a-scikit-learn-model-with-onnx-und-fastapi-1af398268915) by [https://www.linkedin.com/in/nico-axtmann](Nico Axtmann). PR [#438](https://github.com/tiangolo/fastapi/pull/438) by [@naxty](https://github.com/naxty).
|
||||
* Allow setting custom `422` (validation error) response/schema in OpenAPI.
|
||||
* And use media type from response class instead of fixed `application/json` (the default).
|
||||
* PR [#437](https://github.com/tiangolo/fastapi/pull/437) by [@divums](https://github.com/divums).
|
||||
* Fix using `"default"` extra response with status codes at the same time. PR [#489](https://github.com/tiangolo/fastapi/pull/489).
|
||||
* Allow additional responses to use status code ranges (like `5XX` and `4XX`) and `"default"`. PR [#435](https://github.com/tiangolo/fastapi/pull/435) by [@divums](https://github.com/divums).
|
||||
|
||||
## 0.36.0
|
||||
|
||||
* Fix implementation for `skip_defaults` when returning a Pydantic model. PR [#422](https://github.com/tiangolo/fastapi/pull/422) by [@dmontagu](https://github.com/dmontagu).
|
||||
* Fix OpenAPI generation when using the same dependency in multiple places for the same *path operation*. PR [#417](https://github.com/tiangolo/fastapi/pull/417) by [@dmontagu](https://github.com/dmontagu).
|
||||
* Allow having empty paths in *path operations* used with `include_router` and a `prefix`.
|
||||
* This allows having a router for `/cats` and all its *path operations*, while having one of them for `/cats`.
|
||||
* Now it doesn't have to be only `/cats/` (with a trailing slash).
|
||||
* To use it, declare the path in the *path operation* as the empty string (`""`).
|
||||
* PR [#415](https://github.com/tiangolo/fastapi/pull/415) by [@vitalik](https://github.com/vitalik).
|
||||
* Fix mypy error after merging PR #415. PR [#462](https://github.com/tiangolo/fastapi/pull/462).
|
||||
|
||||
## 0.35.0
|
||||
|
||||
* Fix typo in routing `assert`. PR [#419](https://github.com/tiangolo/fastapi/pull/419) by [@pablogamboa](https://github.com/pablogamboa).
|
||||
* Fix typo in docs. PR [#411](https://github.com/tiangolo/fastapi/pull/411) by [@bronsen](https://github.com/bronsen).
|
||||
* Fix parsing a body type declared with `Union`. PR [#400](https://github.com/tiangolo/fastapi/pull/400) by [@koxudaxi](https://github.com/koxudaxi).
|
||||
|
||||
## 0.34.0
|
||||
|
||||
* Upgrade Starlette supported range to include the latest `0.12.7`. The new range is `0.11.1,<=0.12.7`. PR [#367](https://github.com/tiangolo/fastapi/pull/367) by [@dedsm](https://github.com/dedsm).
|
||||
|
||||
* Add test for OpenAPI schema with duplicate models from PR [#333](https://github.com/tiangolo/fastapi/pull/333) by [@dmontagu](https://github.com/dmontagu). PR [#385](https://github.com/tiangolo/fastapi/pull/385).
|
||||
|
||||
## 0.33.0
|
||||
|
||||
* Upgrade Pydantic version to `0.30.0`. PR [#384](https://github.com/tiangolo/fastapi/pull/384) by [@jekirl](https://github.com/jekirl).
|
||||
|
||||
## 0.32.0
|
||||
|
||||
* Fix typo in docs for features. PR [#380](https://github.com/tiangolo/fastapi/pull/380) by [@MartinoMensio](https://github.com/MartinoMensio).
|
||||
|
||||
* Fix source code `limit` for example in [Query Parameters](https://fastapi.tiangolo.com/tutorial/query-params/). PR [#366](https://github.com/tiangolo/fastapi/pull/366) by [@Smashman](https://github.com/Smashman).
|
||||
|
||||
* Update wording in docs about [OAuth2 scopes](https://fastapi.tiangolo.com/tutorial/security/oauth2-scopes/). PR [#371](https://github.com/tiangolo/fastapi/pull/371) by [@cjw296](https://github.com/cjw296).
|
||||
|
||||
* Update docs for `Enum`s to inherit from `str` and improve Swagger UI rendering. PR [#351](https://github.com/tiangolo/fastapi/pull/351).
|
||||
|
||||
* Fix regression, add Swagger UI deep linking again. PR [#350](https://github.com/tiangolo/fastapi/pull/350).
|
||||
|
||||
* Add test for having path templates in `prefix` of `.include_router`. PR [#349](https://github.com/tiangolo/fastapi/pull/349).
|
||||
|
||||
* Add note to docs: [Include the same router multiple times with different `prefix`](https://fastapi.tiangolo.com/tutorial/bigger-applications/#include-the-same-router-multiple-times-with-different-prefix). PR [#348](https://github.com/tiangolo/fastapi/pull/348).
|
||||
|
||||
* Fix OpenAPI/JSON Schema generation for two functions with the same name (in different modules) with the same composite bodies.
|
||||
* Composite bodies' IDs are now based on path, not only on route name, as the auto-generated name uses the function names, that can be duplicated in different modules.
|
||||
* The same new ID generation applies to response models.
|
||||
* This also changes the generated title for those models.
|
||||
* Only composite bodies and response models are affected because those are generated dynamically, they don't have a module (a Python file).
|
||||
* This also adds the possibility of using `.include_router()` with the same `APIRouter` *multiple* times, with different prefixes, e.g. `/api/v2` and `/api/latest`, and it will now work correctly.
|
||||
* PR [#347](https://github.com/tiangolo/fastapi/pull/347).
|
||||
|
||||
## 0.31.0
|
||||
|
||||
* Upgrade Pydantic supported version to `0.29.0`.
|
||||
* New supported version range is `"pydantic >=0.28,<=0.29.0"`.
|
||||
* This adds support for Pydantic [Generic Models](https://pydantic-docs.helpmanual.io/#generic-models), kudos to [@dmontagu](https://github.com/dmontagu).
|
||||
* PR [#344](https://github.com/tiangolo/fastapi/pull/344).
|
||||
|
||||
## 0.30.1
|
||||
|
||||
* Add section in docs about [External Links and Articles](https://fastapi.tiangolo.com/external-links/). PR [#341](https://github.com/tiangolo/fastapi/pull/341).
|
||||
|
||||
* Remove `Pipfile.lock` from the repository as it is only used by FastAPI contributors (developers of FastAPI itself). See the PR for more details. PR [#340](https://github.com/tiangolo/fastapi/pull/340).
|
||||
|
||||
* Update section about [Help FastAPI - Get Help](https://fastapi.tiangolo.com/help-fastapi/). PR [#339](https://github.com/tiangolo/fastapi/pull/339).
|
||||
|
||||
* Refine internal type declarations to improve/remove Mypy errors in users' code. PR [#338](https://github.com/tiangolo/fastapi/pull/338).
|
||||
|
||||
* Update and clarify [SQL tutorial with SQLAlchemy](https://fastapi.tiangolo.com/tutorial/sql-databases/). PR [#331](https://github.com/tiangolo/fastapi/pull/331) by [@mariacamilagl](https://github.com/mariacamilagl).
|
||||
|
||||
* Add SQLite [online viewers to the docs](https://fastapi.tiangolo.com/tutorial/sql-databases/#interact-with-the-database-directly). PR [#330](https://github.com/tiangolo/fastapi/pull/330) by [@cyrilbois](https://github.com/cyrilbois).
|
||||
|
||||
## 0.30.0
|
||||
|
||||
* Add support for Pydantic's ORM mode:
|
||||
* Updated documentation about SQL with SQLAlchemy, using Pydantic models with ORM mode, SQLAlchemy models with relations, separation of files, simplification of code and other changes. New docs: [SQL (Relational) Databases](https://fastapi.tiangolo.com/tutorial/sql-databases/).
|
||||
* The new support for ORM mode fixes issues/adds features related to ORMs with lazy-loading, hybrid properties, dynamic/getters (using `@property` decorators) and several other use cases.
|
||||
* This applies to ORMs like SQLAlchemy, Peewee, Tortoise ORM, GINO ORM and virtually any other.
|
||||
* If your *path operations* return an arbitrary object with attributes (e.g. `my_item.name` instead of `my_item["name"]`) AND you use a `response_model`, make sure to update the Pydantic models with `orm_mode = True` as described in the docs (link above).
|
||||
* New documentation about receiving plain `dict`s as request bodies: [Bodies of arbitrary `dict`s](https://fastapi.tiangolo.com/tutorial/body-nested-models/#bodies-of-arbitrary-dicts).
|
||||
* New documentation about returning arbitrary `dict`s in responses: [Response with arbitrary `dict`](https://fastapi.tiangolo.com/tutorial/extra-models/#response-with-arbitrary-dict).
|
||||
* **Technical Details**:
|
||||
* When declaring a `response_model` it is used directly to generate the response content, from whatever was returned from the *path operation function*.
|
||||
* Before this, the return content was first passed through `jsonable_encoder` to ensure it was a "jsonable" object, like a `dict`, instead of an arbitrary object with attributes (like an ORM model). That's why you should make sure to update your Pydantic models for objects with attributes to use `orm_mode = True`.
|
||||
* If you don't have a `response_model`, the return object will still be passed through `jsonable_encoder` first.
|
||||
* When a `response_model` is declared, the same `response_model` type declaration won't be used as is, it will be "cloned" to create an new one (a cloned Pydantic `Field` with all the submodels cloned as well).
|
||||
* This avoids/fixes a potential security issue: as the returned object is passed directly to Pydantic, if the returned object was a subclass of the `response_model` (e.g. you return a `UserInDB` that inherits from `User` but contains extra fields, like `hashed_password`, and `User` is used in the `response_model`), it would still pass the validation (because `UserInDB` is a subclass of `User`) and the object would be returned as-is, including the `hashed_password`. To fix this, the declared `response_model` is cloned, if it is a Pydantic model class (or contains Pydantic model classes in it, e.g. in a `List[Item]`), the Pydantic model class(es) will be a different one (the "cloned" one). So, an object that is a subclass won't simply pass the validation and returned as-is, because it is no longer a sub-class of the cloned `response_model`. Instead, a new Pydantic model object will be created with the contents of the returned object. So, it will be a new object (made with the data from the returned one), and will be filtered by the cloned `response_model`, containing only the declared fields as normally.
|
||||
* PR [#322](https://github.com/tiangolo/fastapi/pull/322).
|
||||
|
||||
* Remove/clean unused RegEx code in routing. PR [#314](https://github.com/tiangolo/fastapi/pull/314) by [@dmontagu](https://github.com/dmontagu).
|
||||
|
||||
* Use default response status code descriptions for additional responses. PR [#313](https://github.com/tiangolo/fastapi/pull/313) by [@duxiaoyao](https://github.com/duxiaoyao).
|
||||
|
||||
* Upgrade Pydantic support to `0.28`. PR [#320](https://github.com/tiangolo/fastapi/pull/320) by [@jekirl](https://github.com/jekirl).
|
||||
|
||||
## 0.29.1
|
||||
|
||||
* Fix handling an empty-body request with a required body param. PR [#311](https://github.com/tiangolo/fastapi/pull/311).
|
||||
|
||||
* Fix broken link in docs: [Return a Response directly](https://fastapi.tiangolo.com/tutorial/response-directly/). PR [#306](https://github.com/tiangolo/fastapi/pull/306) by [@dmontagu](https://github.com/dmontagu).
|
||||
|
||||
* Fix docs discrepancy in docs for [Response Model](https://fastapi.tiangolo.com/tutorial/response-model/). PR [#288](https://github.com/tiangolo/fastapi/pull/288) by [@awiddersheim](https://github.com/awiddersheim).
|
||||
|
||||
## 0.29.0
|
||||
|
||||
* Add support for declaring a `Response` parameter:
|
||||
* This allows declaring:
|
||||
* [Response Cookies](https://fastapi.tiangolo.com/tutorial/response-cookies/).
|
||||
* [Response Headers](https://fastapi.tiangolo.com/tutorial/response-headers/).
|
||||
* An HTTP Status Code different than the default: [Response - Change Status Code](https://fastapi.tiangolo.com/tutorial/response-change-status-code/).
|
||||
* All of this while still being able to return arbitrary objects (`dict`, DB model, etc).
|
||||
* Update attribution to Hug, for inspiring the `response` parameter pattern.
|
||||
* PR [#294](https://github.com/tiangolo/fastapi/pull/294).
|
||||
|
||||
## 0.28.0
|
||||
|
||||
* Implement dependency cache per request.
|
||||
* This avoids calling each dependency multiple times for the same request.
|
||||
* This is useful while calling external services, performing costly computation, etc.
|
||||
* This also means that if a dependency was declared as a *path operation decorator* dependency, possibly at the router level (with `.include_router()`) and then it is declared again in a specific *path operation*, the dependency will be called only once.
|
||||
* The cache can be disabled per dependency declaration, using `use_cache=False` as in `Depends(your_dependency, use_cache=False)`.
|
||||
* Updated docs at: [Using the same dependency multiple times](https://fastapi.tiangolo.com/tutorial/dependencies/sub-dependencies/#using-the-same-dependency-multiple-times).
|
||||
* PR [#292](https://github.com/tiangolo/fastapi/pull/292).
|
||||
|
||||
* Implement dependency overrides for testing.
|
||||
* This allows using overrides/mocks of dependencies during tests.
|
||||
* New docs: [Testing Dependencies with Overrides](https://fastapi.tiangolo.com/tutorial/testing-dependencies/).
|
||||
* PR [#291](https://github.com/tiangolo/fastapi/pull/291).
|
||||
|
||||
## 0.27.2
|
||||
|
||||
* Fix path and query parameters receiving `dict` as a valid type. It should be mapped to a body payload. PR [#287](https://github.com/tiangolo/fastapi/pull/287). Updated docs at: [Query parameter list / multiple values with defaults: Using `list`](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#using-list).
|
||||
|
||||
## 0.27.1
|
||||
|
||||
* Fix `auto_error=False` handling in `HTTPBearer` security scheme. Do not `raise` when there's an incorrect `Authorization` header if `auto_error=False`. PR [#282](https://github.com/tiangolo/fastapi/pull/282).
|
||||
|
||||
* Fix type declaration of `HTTPException`. PR [#279](https://github.com/tiangolo/fastapi/pull/279).
|
||||
|
||||
## 0.27.0
|
||||
|
||||
* Fix broken link in docs about OAuth 2.0 with scopes. PR [#275](https://github.com/tiangolo/fastapi/pull/275) by [@dmontagu](https://github.com/dmontagu).
|
||||
|
||||
* Refactor param extraction using Pydantic `Field`:
|
||||
* Large refactor, improvement, and simplification of param extraction from *path operations*.
|
||||
* Fix/add support for list *query parameters* with list defaults. New documentation: [Query parameter list / multiple values with defaults](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#query-parameter-list-multiple-values-with-defaults).
|
||||
* Add support for enumerations in *path operation* parameters. New documentation: [Path Parameters: Predefined values](https://fastapi.tiangolo.com/tutorial/path-params/#predefined-values).
|
||||
* Add support for type annotations using `Optional` as in `param: Optional[str] = None`. New documentation: [Optional type declarations](https://fastapi.tiangolo.com/tutorial/query-params/#optional-type-declarations).
|
||||
* PR [#278](https://github.com/tiangolo/fastapi/pull/278).
|
||||
|
||||
## 0.26.0
|
||||
|
||||
* Separate error handling for validation errors.
|
||||
* This will allow developers to customize the exception handlers.
|
||||
* Document better how to handle exceptions and use error handlers.
|
||||
* Include `RequestValidationError` and `WebSocketRequestValidationError` (this last one will be useful once [encode/starlette#527](https://github.com/encode/starlette/pull/527) or equivalent is merged).
|
||||
* New documentation about exceptions handlers:
|
||||
* [Install custom exception handlers](https://fastapi.tiangolo.com/tutorial/handling-errors/#install-custom-exception-handlers).
|
||||
* [Override the default exception handlers](https://fastapi.tiangolo.com/tutorial/handling-errors/#override-the-default-exception-handlers).
|
||||
* [Re-use **FastAPI's** exception handlers](https://fastapi.tiangolo.com/tutorial/handling-errors/#re-use-fastapis-exception-handlers).
|
||||
* PR [#273](https://github.com/tiangolo/fastapi/pull/273).
|
||||
|
||||
* Fix support for *paths* in *path parameters* without needing explicit `Path(...)`.
|
||||
* PR [#256](https://github.com/tiangolo/fastapi/pull/256).
|
||||
* Documented in PR [#272](https://github.com/tiangolo/fastapi/pull/272) by [@wshayes](https://github.com/wshayes).
|
||||
* New documentation at: [Path Parameters containing paths](https://fastapi.tiangolo.com/tutorial/path-params/#path-parameters-containing-paths).
|
||||
|
||||
* Update docs for testing FastAPI. Include using `POST`, sending JSON, testing headers, etc. New documentation: [Testing](https://fastapi.tiangolo.com/tutorial/testing/#testing-extended-example). PR [#271](https://github.com/tiangolo/fastapi/pull/271).
|
||||
|
||||
* Fix type declaration of `response_model` to allow generic Python types as `List[Model]`. Mainly to fix `mypy` for users. PR [#266](https://github.com/tiangolo/fastapi/pull/266).
|
||||
|
||||
## 0.25.0
|
||||
|
||||
* Add support for Pydantic's `include`, `exclude`, `by_alias`.
|
||||
* Update documentation: [Response Model](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude).
|
||||
* Add docs for: [Body - updates](https://fastapi.tiangolo.com/tutorial/body-updates/), using Pydantic's `skip_defaults`.
|
||||
* Add method consistency tests.
|
||||
* PR [#264](https://github.com/tiangolo/fastapi/pull/264).
|
||||
|
||||
* Add `CONTRIBUTING.md` file to GitHub, to help new contributors. PR [#255](https://github.com/tiangolo/fastapi/pull/255) by [@wshayes](https://github.com/wshayes).
|
||||
|
||||
* Add support for Pydantic's `skip_defaults`:
|
||||
* There's a new *path operation decorator* parameter `response_model_skip_defaults`.
|
||||
* The name of the parameter will most probably change in a future version to `response_skip_defaults`, `model_skip_defaults` or something similar.
|
||||
* New [documentation section about using `response_model_skip_defaults`](https://fastapi.tiangolo.com/tutorial/response-model/#response-model-encoding-parameters).
|
||||
* PR [#248](https://github.com/tiangolo/fastapi/pull/248) by [@wshayes](https://github.com/wshayes).
|
||||
|
||||
## 0.24.0
|
||||
|
||||
* Add support for WebSockets with dependencies and parameters.
|
||||
* Support included for:
|
||||
* `Depends`
|
||||
* `Security`
|
||||
* `Cookie`
|
||||
* `Header`
|
||||
* `Path`
|
||||
* `Query`
|
||||
* ...as these are compatible with the WebSockets protocol (e.g. `Body` is not).
|
||||
* [Updated documentation for WebSockets](https://fastapi.tiangolo.com/tutorial/websockets/).
|
||||
* PR [#178](https://github.com/tiangolo/fastapi/pull/178) by [@jekirl](https://github.com/jekirl).
|
||||
|
||||
* Upgrade the compatible version of Pydantic to `0.26.0`.
|
||||
* This includes JSON Schema support for IP address and network objects, bug fixes, and other features.
|
||||
* PR [#247](https://github.com/tiangolo/fastapi/pull/247) by [@euri10](https://github.com/euri10).
|
||||
|
||||
## 0.23.0
|
||||
|
||||
* Upgrade the compatible version of Starlette to `0.12.0`.
|
||||
* This includes support for ASGI 3 (the latest version of the standard).
|
||||
* It's now possible to use [Starlette's `StreamingResponse`](https://www.starlette.io/responses/#streamingresponse) with iterators, like [file-like](https://docs.python.org/3/glossary.html#term-file-like-object) objects (as those returned by `open()`).
|
||||
* It's now possible to use the low level utility `iterate_in_threadpool` from `starlette.concurrency` (for advanced scenarios).
|
||||
* PR [#243](https://github.com/tiangolo/fastapi/pull/243).
|
||||
|
||||
* Add OAuth2 redirect page for Swagger UI. This allows having delegated authentication in the Swagger UI docs. For this to work, you need to add `{your_origin}/docs/oauth2-redirect` to the allowed callbacks in your OAuth2 provider (in Auth0, Facebook, Google, etc).
|
||||
* For example, during development, it could be `http://localhost:8000/docs/oauth2-redirect`.
|
||||
* Have in mind that this callback URL is independent of whichever one is used by your frontend. You might also have another callback at `https://yourdomain.com/login/callback`.
|
||||
* This is only to allow delegated authentication in the API docs with Swagger UI.
|
||||
* PR [#198](https://github.com/tiangolo/fastapi/pull/198) by [@steinitzu](https://github.com/steinitzu).
|
||||
|
||||
* Make Swagger UI and ReDoc route handlers (*path operations*) be `async` functions instead of lambdas to improve performance. PR [#241](https://github.com/tiangolo/fastapi/pull/241) by [@Trim21](https://github.com/Trim21).
|
||||
|
||||
* Make Swagger UI and ReDoc URLs parameterizable, allowing to host and serve local versions of them and have offline docs. PR [#112](https://github.com/tiangolo/fastapi/pull/112) by [@euri10](https://github.com/euri10).
|
||||
|
||||
## 0.22.0
|
||||
|
||||
|
||||
36
docs/src/app_testing/main_b.py
Normal file
36
docs/src/app_testing/main_b.py
Normal file
@@ -0,0 +1,36 @@
|
||||
from fastapi import FastAPI, Header, HTTPException
|
||||
from pydantic import BaseModel
|
||||
|
||||
fake_secret_token = "coneofsilence"
|
||||
|
||||
fake_db = {
|
||||
"foo": {"id": "foo", "title": "Foo", "description": "There goes my hero"},
|
||||
"bar": {"id": "bar", "title": "Bar", "description": "The bartenders"},
|
||||
}
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
class Item(BaseModel):
|
||||
id: str
|
||||
title: str
|
||||
description: str = None
|
||||
|
||||
|
||||
@app.get("/items/{item_id}", response_model=Item)
|
||||
async def read_main(item_id: str, x_token: str = Header(...)):
|
||||
if x_token != fake_secret_token:
|
||||
raise HTTPException(status_code=400, detail="Invalid X-Token header")
|
||||
if item_id not in fake_db:
|
||||
raise HTTPException(status_code=404, detail="Item not found")
|
||||
return fake_db[item_id]
|
||||
|
||||
|
||||
@app.post("/items/", response_model=Item)
|
||||
async def create_item(item: Item, x_token: str = Header(...)):
|
||||
if x_token != fake_secret_token:
|
||||
raise HTTPException(status_code=400, detail="Invalid X-Token header")
|
||||
if item.id in fake_db:
|
||||
raise HTTPException(status_code=400, detail="Item already exists")
|
||||
fake_db[item.id] = item
|
||||
return item
|
||||
65
docs/src/app_testing/test_main_b.py
Normal file
65
docs/src/app_testing/test_main_b.py
Normal file
@@ -0,0 +1,65 @@
|
||||
from starlette.testclient import TestClient
|
||||
|
||||
from .main_b import app
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
def test_read_item():
|
||||
response = client.get("/items/foo", headers={"X-Token": "coneofsilence"})
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"id": "foo",
|
||||
"title": "Foo",
|
||||
"description": "There goes my hero",
|
||||
}
|
||||
|
||||
|
||||
def test_read_item_bad_token():
|
||||
response = client.get("/items/foo", headers={"X-Token": "hailhydra"})
|
||||
assert response.status_code == 400
|
||||
assert response.json() == {"detail": "Invalid X-Token header"}
|
||||
|
||||
|
||||
def test_read_inexistent_item():
|
||||
response = client.get("/items/baz", headers={"X-Token": "coneofsilence"})
|
||||
assert response.status_code == 404
|
||||
assert response.json() == {"detail": "Item not found"}
|
||||
|
||||
|
||||
def test_create_item():
|
||||
response = client.post(
|
||||
"/items/",
|
||||
headers={"X-Token": "coneofsilence"},
|
||||
json={"id": "foobar", "title": "Foo Bar", "description": "The Foo Barters"},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"id": "foobar",
|
||||
"title": "Foo Bar",
|
||||
"description": "The Foo Barters",
|
||||
}
|
||||
|
||||
|
||||
def test_create_item_bad_token():
|
||||
response = client.post(
|
||||
"/items/",
|
||||
headers={"X-Token": "hailhydra"},
|
||||
json={"id": "bazz", "title": "Bazz", "description": "Drop the bazz"},
|
||||
)
|
||||
assert response.status_code == 400
|
||||
assert response.json() == {"detail": "Invalid X-Token header"}
|
||||
|
||||
|
||||
def test_create_existing_token():
|
||||
response = client.post(
|
||||
"/items/",
|
||||
headers={"X-Token": "coneofsilence"},
|
||||
json={
|
||||
"id": "foo",
|
||||
"title": "The Foo ID Stealers",
|
||||
"description": "There goes my stealer",
|
||||
},
|
||||
)
|
||||
assert response.status_code == 400
|
||||
assert response.json() == {"detail": "Item already exists"}
|
||||
@@ -1,13 +1,13 @@
|
||||
from fastapi import Body, FastAPI
|
||||
from pydantic import BaseModel, Schema
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
class Item(BaseModel):
|
||||
name: str
|
||||
description: str = Schema(None, title="The description of the item", max_length=300)
|
||||
price: float = Schema(..., gt=0, description="The price must be greater than zero")
|
||||
description: str = Field(None, title="The description of the item", max_length=300)
|
||||
price: float = Field(..., gt=0, description="The price must be greater than zero")
|
||||
tax: float = None
|
||||
|
||||
|
||||
10
docs/src/body_nested_models/tutorial009.py
Normal file
10
docs/src/body_nested_models/tutorial009.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from typing import Dict
|
||||
|
||||
from fastapi import FastAPI
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.post("/index-weights/")
|
||||
async def create_index_weights(weights: Dict[int, float]):
|
||||
return weights
|
||||
34
docs/src/body_updates/tutorial001.py
Normal file
34
docs/src/body_updates/tutorial001.py
Normal file
@@ -0,0 +1,34 @@
|
||||
from typing import List
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from pydantic import BaseModel
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
class Item(BaseModel):
|
||||
name: str = None
|
||||
description: str = None
|
||||
price: float = None
|
||||
tax: float = 10.5
|
||||
tags: List[str] = []
|
||||
|
||||
|
||||
items = {
|
||||
"foo": {"name": "Foo", "price": 50.2},
|
||||
"bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
|
||||
"baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
|
||||
}
|
||||
|
||||
|
||||
@app.get("/items/{item_id}", response_model=Item)
|
||||
async def read_item(item_id: str):
|
||||
return items[item_id]
|
||||
|
||||
|
||||
@app.put("/items/{item_id}", response_model=Item)
|
||||
async def update_item(item_id: str, item: Item):
|
||||
update_item_encoded = jsonable_encoder(item)
|
||||
items[item_id] = update_item_encoded
|
||||
return update_item_encoded
|
||||
37
docs/src/body_updates/tutorial002.py
Normal file
37
docs/src/body_updates/tutorial002.py
Normal file
@@ -0,0 +1,37 @@
|
||||
from typing import List
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from pydantic import BaseModel
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
class Item(BaseModel):
|
||||
name: str = None
|
||||
description: str = None
|
||||
price: float = None
|
||||
tax: float = 10.5
|
||||
tags: List[str] = []
|
||||
|
||||
|
||||
items = {
|
||||
"foo": {"name": "Foo", "price": 50.2},
|
||||
"bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
|
||||
"baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
|
||||
}
|
||||
|
||||
|
||||
@app.get("/items/{item_id}", response_model=Item)
|
||||
async def read_item(item_id: str):
|
||||
return items[item_id]
|
||||
|
||||
|
||||
@app.patch("/items/{item_id}", response_model=Item)
|
||||
async def update_item(item_id: str, item: Item):
|
||||
stored_item_data = items[item_id]
|
||||
stored_item_model = Item(**stored_item_data)
|
||||
update_data = item.dict(exclude_unset=True)
|
||||
updated_item = stored_item_model.copy(update=update_data)
|
||||
items[item_id] = jsonable_encoder(updated_item)
|
||||
return updated_item
|
||||
@@ -6,8 +6,8 @@ app = FastAPI()
|
||||
origins = [
|
||||
"http://localhost.tiangolo.com",
|
||||
"https://localhost.tiangolo.com",
|
||||
"http:localhost",
|
||||
"http:localhost:8080",
|
||||
"http://localhost",
|
||||
"http://localhost:8080",
|
||||
]
|
||||
|
||||
app.add_middleware(
|
||||
|
||||
37
docs/src/custom_request_and_route/tutorial001.py
Normal file
37
docs/src/custom_request_and_route/tutorial001.py
Normal file
@@ -0,0 +1,37 @@
|
||||
import gzip
|
||||
from typing import Callable, List
|
||||
|
||||
from fastapi import Body, FastAPI
|
||||
from fastapi.routing import APIRoute
|
||||
from starlette.requests import Request
|
||||
from starlette.responses import Response
|
||||
|
||||
|
||||
class GzipRequest(Request):
|
||||
async def body(self) -> bytes:
|
||||
if not hasattr(self, "_body"):
|
||||
body = await super().body()
|
||||
if "gzip" in self.headers.getlist("Content-Encoding"):
|
||||
body = gzip.decompress(body)
|
||||
self._body = body
|
||||
return self._body
|
||||
|
||||
|
||||
class GzipRoute(APIRoute):
|
||||
def get_route_handler(self) -> Callable:
|
||||
original_route_handler = super().get_route_handler()
|
||||
|
||||
async def custom_route_handler(request: Request) -> Response:
|
||||
request = GzipRequest(request.scope, request.receive)
|
||||
return await original_route_handler(request)
|
||||
|
||||
return custom_route_handler
|
||||
|
||||
|
||||
app = FastAPI()
|
||||
app.router.route_class = GzipRoute
|
||||
|
||||
|
||||
@app.post("/sum")
|
||||
async def sum_numbers(numbers: List[int] = Body(...)):
|
||||
return {"sum": sum(numbers)}
|
||||
31
docs/src/custom_request_and_route/tutorial002.py
Normal file
31
docs/src/custom_request_and_route/tutorial002.py
Normal file
@@ -0,0 +1,31 @@
|
||||
from typing import Callable, List
|
||||
|
||||
from fastapi import Body, FastAPI, HTTPException
|
||||
from fastapi.exceptions import RequestValidationError
|
||||
from fastapi.routing import APIRoute
|
||||
from starlette.requests import Request
|
||||
from starlette.responses import Response
|
||||
|
||||
|
||||
class ValidationErrorLoggingRoute(APIRoute):
|
||||
def get_route_handler(self) -> Callable:
|
||||
original_route_handler = super().get_route_handler()
|
||||
|
||||
async def custom_route_handler(request: Request) -> Response:
|
||||
try:
|
||||
return await original_route_handler(request)
|
||||
except RequestValidationError as exc:
|
||||
body = await request.body()
|
||||
detail = {"errors": exc.errors(), "body": body.decode()}
|
||||
raise HTTPException(status_code=422, detail=detail)
|
||||
|
||||
return custom_route_handler
|
||||
|
||||
|
||||
app = FastAPI()
|
||||
app.router.route_class = ValidationErrorLoggingRoute
|
||||
|
||||
|
||||
@app.post("/")
|
||||
async def sum_numbers(numbers: List[int] = Body(...)):
|
||||
return sum(numbers)
|
||||
41
docs/src/custom_request_and_route/tutorial003.py
Normal file
41
docs/src/custom_request_and_route/tutorial003.py
Normal file
@@ -0,0 +1,41 @@
|
||||
import time
|
||||
from typing import Callable
|
||||
|
||||
from fastapi import APIRouter, FastAPI
|
||||
from fastapi.routing import APIRoute
|
||||
from starlette.requests import Request
|
||||
from starlette.responses import Response
|
||||
|
||||
|
||||
class TimedRoute(APIRoute):
|
||||
def get_route_handler(self) -> Callable:
|
||||
original_route_handler = super().get_route_handler()
|
||||
|
||||
async def custom_route_handler(request: Request) -> Response:
|
||||
before = time.time()
|
||||
response: Response = await original_route_handler(request)
|
||||
duration = time.time() - before
|
||||
response.headers["X-Response-Time"] = str(duration)
|
||||
print(f"route duration: {duration}")
|
||||
print(f"route response: {response}")
|
||||
print(f"route response headers: {response.headers}")
|
||||
return response
|
||||
|
||||
return custom_route_handler
|
||||
|
||||
|
||||
app = FastAPI()
|
||||
router = APIRouter(route_class=TimedRoute)
|
||||
|
||||
|
||||
@app.get("/")
|
||||
async def not_timed():
|
||||
return {"message": "Not timed"}
|
||||
|
||||
|
||||
@router.get("/timed")
|
||||
async def timed():
|
||||
return {"message": "It's the time of my life"}
|
||||
|
||||
|
||||
app.include_router(router)
|
||||
@@ -1,21 +1,6 @@
|
||||
from fastapi import Depends, FastAPI
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
class FixedContentQueryChecker:
|
||||
def __init__(self, fixed_content: str):
|
||||
self.fixed_content = fixed_content
|
||||
|
||||
def __call__(self, q: str = ""):
|
||||
if q:
|
||||
return self.fixed_content in q
|
||||
return False
|
||||
|
||||
|
||||
checker = FixedContentQueryChecker("bar")
|
||||
|
||||
|
||||
@app.get("/query-checker/")
|
||||
async def read_query_check(fixed_content_included: bool = Depends(checker)):
|
||||
return {"fixed_content_in_query": fixed_content_included}
|
||||
async def get_db():
|
||||
db = DBSession()
|
||||
try:
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
25
docs/src/dependencies/tutorial008.py
Normal file
25
docs/src/dependencies/tutorial008.py
Normal file
@@ -0,0 +1,25 @@
|
||||
from fastapi import Depends
|
||||
|
||||
|
||||
async def dependency_a():
|
||||
dep_a = generate_dep_a()
|
||||
try:
|
||||
yield dep_a
|
||||
finally:
|
||||
dep_a.close()
|
||||
|
||||
|
||||
async def dependency_b(dep_a=Depends(dependency_a)):
|
||||
dep_b = generate_dep_b()
|
||||
try:
|
||||
yield dep_b
|
||||
finally:
|
||||
dep_b.close(dep_a)
|
||||
|
||||
|
||||
async def dependency_c(dep_b=Depends(dependency_b)):
|
||||
dep_c = generate_dep_c()
|
||||
try:
|
||||
yield dep_c
|
||||
finally:
|
||||
dep_c.close(dep_b)
|
||||
25
docs/src/dependencies/tutorial009.py
Normal file
25
docs/src/dependencies/tutorial009.py
Normal file
@@ -0,0 +1,25 @@
|
||||
from fastapi import Depends
|
||||
|
||||
|
||||
async def dependency_a():
|
||||
dep_a = generate_dep_a()
|
||||
try:
|
||||
yield dep_a
|
||||
finally:
|
||||
dep_a.close()
|
||||
|
||||
|
||||
async def dependency_b(dep_a=Depends(dependency_a)):
|
||||
dep_b = generate_dep_b()
|
||||
try:
|
||||
yield dep_b
|
||||
finally:
|
||||
dep_b.close(dep_a)
|
||||
|
||||
|
||||
async def dependency_c(dep_b=Depends(dependency_b)):
|
||||
dep_c = generate_dep_c()
|
||||
try:
|
||||
yield dep_c
|
||||
finally:
|
||||
dep_c.close(dep_b)
|
||||
14
docs/src/dependencies/tutorial010.py
Normal file
14
docs/src/dependencies/tutorial010.py
Normal file
@@ -0,0 +1,14 @@
|
||||
class MySuperContextManager:
|
||||
def __init__(self):
|
||||
self.db = DBSession()
|
||||
|
||||
def __enter__(self):
|
||||
return self.db
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
self.db.close()
|
||||
|
||||
|
||||
async def get_db():
|
||||
with MySuperContextManager() as db:
|
||||
yield db
|
||||
21
docs/src/dependencies/tutorial011.py
Normal file
21
docs/src/dependencies/tutorial011.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from fastapi import Depends, FastAPI
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
class FixedContentQueryChecker:
|
||||
def __init__(self, fixed_content: str):
|
||||
self.fixed_content = fixed_content
|
||||
|
||||
def __call__(self, q: str = ""):
|
||||
if q:
|
||||
return self.fixed_content in q
|
||||
return False
|
||||
|
||||
|
||||
checker = FixedContentQueryChecker("bar")
|
||||
|
||||
|
||||
@app.get("/query-checker/")
|
||||
async def read_query_check(fixed_content_included: bool = Depends(checker)):
|
||||
return {"fixed_content_in_query": fixed_content_included}
|
||||
55
docs/src/dependency_testing/tutorial001.py
Normal file
55
docs/src/dependency_testing/tutorial001.py
Normal file
@@ -0,0 +1,55 @@
|
||||
from fastapi import Depends, FastAPI
|
||||
from starlette.testclient import TestClient
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
async def common_parameters(q: str = None, skip: int = 0, limit: int = 100):
|
||||
return {"q": q, "skip": skip, "limit": limit}
|
||||
|
||||
|
||||
@app.get("/items/")
|
||||
async def read_items(commons: dict = Depends(common_parameters)):
|
||||
return {"message": "Hello Items!", "params": commons}
|
||||
|
||||
|
||||
@app.get("/users/")
|
||||
async def read_users(commons: dict = Depends(common_parameters)):
|
||||
return {"message": "Hello Users!", "params": commons}
|
||||
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
async def override_dependency(q: str = None):
|
||||
return {"q": q, "skip": 5, "limit": 10}
|
||||
|
||||
|
||||
app.dependency_overrides[common_parameters] = override_dependency
|
||||
|
||||
|
||||
def test_override_in_items():
|
||||
response = client.get("/items/")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"message": "Hello Items!",
|
||||
"params": {"q": None, "skip": 5, "limit": 10},
|
||||
}
|
||||
|
||||
|
||||
def test_override_in_items_with_q():
|
||||
response = client.get("/items/?q=foo")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"message": "Hello Items!",
|
||||
"params": {"q": "foo", "skip": 5, "limit": 10},
|
||||
}
|
||||
|
||||
|
||||
def test_override_in_items_with_params():
|
||||
response = client.get("/items/?q=foo&skip=100&limit=200")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"message": "Hello Items!",
|
||||
"params": {"q": "foo", "skip": 5, "limit": 10},
|
||||
}
|
||||
41
docs/src/extending_openapi/tutorial002.py
Normal file
41
docs/src/extending_openapi/tutorial002.py
Normal file
@@ -0,0 +1,41 @@
|
||||
from fastapi import FastAPI
|
||||
from fastapi.openapi.docs import (
|
||||
get_redoc_html,
|
||||
get_swagger_ui_html,
|
||||
get_swagger_ui_oauth2_redirect_html,
|
||||
)
|
||||
from starlette.staticfiles import StaticFiles
|
||||
|
||||
app = FastAPI(docs_url=None, redoc_url=None)
|
||||
|
||||
app.mount("/static", StaticFiles(directory="static"), name="static")
|
||||
|
||||
|
||||
@app.get("/docs", include_in_schema=False)
|
||||
async def custom_swagger_ui_html():
|
||||
return get_swagger_ui_html(
|
||||
openapi_url=app.openapi_url,
|
||||
title=app.title + " - Swagger UI",
|
||||
oauth2_redirect_url=app.swagger_ui_oauth2_redirect_url,
|
||||
swagger_js_url="/static/swagger-ui-bundle.js",
|
||||
swagger_css_url="/static/swagger-ui.css",
|
||||
)
|
||||
|
||||
|
||||
@app.get(app.swagger_ui_oauth2_redirect_url, include_in_schema=False)
|
||||
async def swagger_ui_redirect():
|
||||
return get_swagger_ui_oauth2_redirect_html()
|
||||
|
||||
|
||||
@app.get("/redoc", include_in_schema=False)
|
||||
async def redoc_html():
|
||||
return get_redoc_html(
|
||||
openapi_url=app.openapi_url,
|
||||
title=app.title + " - ReDoc",
|
||||
redoc_js_url="/static/redoc.standalone.js",
|
||||
)
|
||||
|
||||
|
||||
@app.get("/users/{username}")
|
||||
async def read_user(username: str):
|
||||
return {"message": f"Hello {username}"}
|
||||
@@ -1,6 +1,5 @@
|
||||
from fastapi import FastAPI
|
||||
from pydantic import BaseModel
|
||||
from pydantic.types import EmailStr
|
||||
from pydantic import BaseModel, EmailStr
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
from fastapi import FastAPI
|
||||
from pydantic import BaseModel
|
||||
from pydantic.types import EmailStr
|
||||
from pydantic import BaseModel, EmailStr
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
10
docs/src/extra_models/tutorial005.py
Normal file
10
docs/src/extra_models/tutorial005.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from typing import Dict
|
||||
|
||||
from fastapi import FastAPI
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.get("/keyword-weights/", response_model=Dict[str, float])
|
||||
async def read_keyword_weights():
|
||||
return {"foo": 2.3, "bar": 3.4}
|
||||
@@ -1,15 +1,26 @@
|
||||
from fastapi import FastAPI
|
||||
from starlette.exceptions import HTTPException
|
||||
from starlette.responses import PlainTextResponse
|
||||
from starlette.requests import Request
|
||||
from starlette.responses import JSONResponse
|
||||
|
||||
|
||||
class UnicornException(Exception):
|
||||
def __init__(self, name: str):
|
||||
self.name = name
|
||||
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.exception_handler(HTTPException)
|
||||
async def http_exception(request, exc):
|
||||
return PlainTextResponse(str(exc.detail), status_code=exc.status_code)
|
||||
@app.exception_handler(UnicornException)
|
||||
async def unicorn_exception_handler(request: Request, exc: UnicornException):
|
||||
return JSONResponse(
|
||||
status_code=418,
|
||||
content={"message": f"Oops! {exc.name} did something. There goes a rainbow..."},
|
||||
)
|
||||
|
||||
|
||||
@app.get("/")
|
||||
async def root():
|
||||
return {"message": "Hello World"}
|
||||
@app.get("/unicorns/{name}")
|
||||
async def read_unicorn(name: str):
|
||||
if name == "yolo":
|
||||
raise UnicornException(name=name)
|
||||
return {"unicorn_name": name}
|
||||
|
||||
23
docs/src/handling_errors/tutorial004.py
Normal file
23
docs/src/handling_errors/tutorial004.py
Normal file
@@ -0,0 +1,23 @@
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from fastapi.exceptions import RequestValidationError
|
||||
from starlette.exceptions import HTTPException as StarletteHTTPException
|
||||
from starlette.responses import PlainTextResponse
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.exception_handler(StarletteHTTPException)
|
||||
async def http_exception_handler(request, exc):
|
||||
return PlainTextResponse(str(exc.detail), status_code=exc.status_code)
|
||||
|
||||
|
||||
@app.exception_handler(RequestValidationError)
|
||||
async def validation_exception_handler(request, exc):
|
||||
return PlainTextResponse(str(exc), status_code=400)
|
||||
|
||||
|
||||
@app.get("/items/{item_id}")
|
||||
async def read_item(item_id: int):
|
||||
if item_id == 3:
|
||||
raise HTTPException(status_code=418, detail="Nope! I don't like 3.")
|
||||
return {"item_id": item_id}
|
||||
28
docs/src/handling_errors/tutorial005.py
Normal file
28
docs/src/handling_errors/tutorial005.py
Normal file
@@ -0,0 +1,28 @@
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from fastapi.exception_handlers import (
|
||||
http_exception_handler,
|
||||
request_validation_exception_handler,
|
||||
)
|
||||
from fastapi.exceptions import RequestValidationError
|
||||
from starlette.exceptions import HTTPException as StarletteHTTPException
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.exception_handler(StarletteHTTPException)
|
||||
async def custom_http_exception_handler(request, exc):
|
||||
print(f"OMG! An HTTP error!: {exc}")
|
||||
return await http_exception_handler(request, exc)
|
||||
|
||||
|
||||
@app.exception_handler(RequestValidationError)
|
||||
async def validation_exception_handler(request, exc):
|
||||
print(f"OMG! The client sent invalid data!: {exc}")
|
||||
return await request_validation_exception_handler(request, exc)
|
||||
|
||||
|
||||
@app.get("/items/{item_id}")
|
||||
async def read_item(item_id: int):
|
||||
if item_id == 3:
|
||||
raise HTTPException(status_code=418, detail="Nope! I don't like 3.")
|
||||
return {"item_id": item_id}
|
||||
53
docs/src/openapi_callbacks/tutorial001.py
Normal file
53
docs/src/openapi_callbacks/tutorial001.py
Normal file
@@ -0,0 +1,53 @@
|
||||
from fastapi import APIRouter, FastAPI
|
||||
from pydantic import BaseModel, HttpUrl
|
||||
from starlette.responses import JSONResponse
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
class Invoice(BaseModel):
|
||||
id: str
|
||||
title: str = None
|
||||
customer: str
|
||||
total: float
|
||||
|
||||
|
||||
class InvoiceEvent(BaseModel):
|
||||
description: str
|
||||
paid: bool
|
||||
|
||||
|
||||
class InvoiceEventReceived(BaseModel):
|
||||
ok: bool
|
||||
|
||||
|
||||
invoices_callback_router = APIRouter(default_response_class=JSONResponse)
|
||||
|
||||
|
||||
@invoices_callback_router.post(
|
||||
"{$callback_url}/invoices/{$request.body.id}",
|
||||
response_model=InvoiceEventReceived,
|
||||
)
|
||||
def invoice_notification(body: InvoiceEvent):
|
||||
pass
|
||||
|
||||
|
||||
@app.post("/invoices/", callbacks=invoices_callback_router.routes)
|
||||
def create_invoice(invoice: Invoice, callback_url: HttpUrl = None):
|
||||
"""
|
||||
Create an invoice.
|
||||
|
||||
This will (let's imagine) let the API user (some external developer) create an
|
||||
invoice.
|
||||
|
||||
And this path operation will:
|
||||
|
||||
* Send the invoice to the client.
|
||||
* Collect the money from the client.
|
||||
* Send a notification back to the API user (the external developer), as a callback.
|
||||
* At this point is that the API will somehow send a POST request to the
|
||||
external API with the notification of the invoice event
|
||||
(e.g. "payment successful").
|
||||
"""
|
||||
# Send the invoice, collect the money, send the notification (the callback)
|
||||
return {"msg": "Invoice received"}
|
||||
@@ -1,8 +1,24 @@
|
||||
from fastapi import FastAPI
|
||||
from fastapi.routing import APIRoute
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.get("/items/", include_in_schema=False)
|
||||
@app.get("/items/")
|
||||
async def read_items():
|
||||
return [{"item_id": "Foo"}]
|
||||
|
||||
|
||||
def use_route_names_as_operation_ids(app: FastAPI) -> None:
|
||||
"""
|
||||
Simplify operation IDs so that generated API clients have simpler function
|
||||
names.
|
||||
|
||||
Should be called only after all routes have been added.
|
||||
"""
|
||||
for route in app.routes:
|
||||
if isinstance(route, APIRoute):
|
||||
route.operation_id = route.name # in this case, 'read_items'
|
||||
|
||||
|
||||
use_route_names_as_operation_ids(app)
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
from fastapi import FastAPI
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.get("/items/", include_in_schema=False)
|
||||
async def read_items():
|
||||
return [{"item_id": "Foo"}]
|
||||
@@ -0,0 +1,30 @@
|
||||
from typing import Set
|
||||
|
||||
from fastapi import FastAPI
|
||||
from pydantic import BaseModel
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
class Item(BaseModel):
|
||||
name: str
|
||||
description: str = None
|
||||
price: float
|
||||
tax: float = None
|
||||
tags: Set[str] = []
|
||||
|
||||
|
||||
@app.post("/items/", response_model=Item, summary="Create an item")
|
||||
async def create_item(*, item: Item):
|
||||
"""
|
||||
Create an item with all the information:
|
||||
|
||||
- **name**: each item must have a name
|
||||
- **description**: a long description
|
||||
- **price**: required
|
||||
- **tax**: if the item doesn't have tax, you can omit this
|
||||
- **tags**: a set of unique tag strings for this item
|
||||
\f
|
||||
:param item: User input.
|
||||
"""
|
||||
return item
|
||||
8
docs/src/path_params/tutorial004.py
Normal file
8
docs/src/path_params/tutorial004.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from fastapi import FastAPI
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.get("/files/{file_path:path}")
|
||||
async def read_user_me(file_path: str):
|
||||
return {"file_path": file_path}
|
||||
21
docs/src/path_params/tutorial005.py
Normal file
21
docs/src/path_params/tutorial005.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from enum import Enum
|
||||
|
||||
from fastapi import FastAPI
|
||||
|
||||
|
||||
class ModelName(str, Enum):
|
||||
alexnet = "alexnet"
|
||||
resnet = "resnet"
|
||||
lenet = "lenet"
|
||||
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.get("/model/{model_name}")
|
||||
async def get_model(model_name: ModelName):
|
||||
if model_name == ModelName.alexnet:
|
||||
return {"model_name": model_name, "message": "Deep Learning FTW!"}
|
||||
if model_name.value == "lenet":
|
||||
return {"model_name": model_name, "message": "LeCNN all the images"}
|
||||
return {"model_name": model_name, "message": "Have some residuals"}
|
||||
@@ -6,5 +6,5 @@ fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"
|
||||
|
||||
|
||||
@app.get("/items/")
|
||||
async def read_item(skip: int = 0, limit: int = 100):
|
||||
async def read_item(skip: int = 0, limit: int = 10):
|
||||
return fake_items_db[skip : skip + limit]
|
||||
|
||||
11
docs/src/query_params/tutorial007.py
Normal file
11
docs/src/query_params/tutorial007.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import FastAPI
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.get("/items/{item_id}")
|
||||
async def read_user_item(item_id: str, limit: Optional[int] = None):
|
||||
item = {"item_id": item_id, "limit": limit}
|
||||
return item
|
||||
11
docs/src/query_params_str_validations/tutorial012.py
Normal file
11
docs/src/query_params_str_validations/tutorial012.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from typing import List
|
||||
|
||||
from fastapi import FastAPI, Query
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.get("/items/")
|
||||
async def read_items(q: List[str] = Query(["foo", "bar"])):
|
||||
query_items = {"q": q}
|
||||
return query_items
|
||||
9
docs/src/query_params_str_validations/tutorial013.py
Normal file
9
docs/src/query_params_str_validations/tutorial013.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from fastapi import FastAPI, Query
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.get("/items/")
|
||||
async def read_items(q: list = Query(None)):
|
||||
query_items = {"q": q}
|
||||
return query_items
|
||||
15
docs/src/response_change_status_code/tutorial001.py
Normal file
15
docs/src/response_change_status_code/tutorial001.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from fastapi import FastAPI
|
||||
from starlette.responses import Response
|
||||
from starlette.status import HTTP_201_CREATED
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
tasks = {"foo": "Listen to the Bar Fighters"}
|
||||
|
||||
|
||||
@app.put("/get-or-create-task/{task_id}", status_code=200)
|
||||
def get_or_create_task(task_id: str, response: Response):
|
||||
if task_id not in tasks:
|
||||
tasks[task_id] = "This didn't exist before"
|
||||
response.status_code = HTTP_201_CREATED
|
||||
return tasks[task_id]
|
||||
10
docs/src/response_cookies/tutorial002.py
Normal file
10
docs/src/response_cookies/tutorial002.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from fastapi import FastAPI
|
||||
from starlette.responses import Response
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.post("/cookie-and-object/")
|
||||
def create_cookie(response: Response):
|
||||
response.set_cookie(key="fakesession", value="fake-cookie-session-value")
|
||||
return {"message": "Come to the dark side, we have cookies"}
|
||||
@@ -6,12 +6,11 @@ app = FastAPI()
|
||||
|
||||
@app.get("/legacy/")
|
||||
def get_legacy_data():
|
||||
data = """
|
||||
<?xml version="1.0"?>
|
||||
data = """<?xml version="1.0"?>
|
||||
<shampoo>
|
||||
<Header>
|
||||
Apply shampoo here.
|
||||
<Header>
|
||||
</Header>
|
||||
<Body>
|
||||
You'll have to use soap here.
|
||||
</Body>
|
||||
|
||||
10
docs/src/response_headers/tutorial002.py
Normal file
10
docs/src/response_headers/tutorial002.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from fastapi import FastAPI
|
||||
from starlette.responses import Response
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.get("/headers-and-object/")
|
||||
def get_headers(response: Response):
|
||||
response.headers["X-Cat-Dog"] = "alone in the world"
|
||||
return {"message": "Hello World"}
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import Set
|
||||
from typing import List
|
||||
|
||||
from fastapi import FastAPI
|
||||
from pydantic import BaseModel
|
||||
@@ -11,9 +11,9 @@ class Item(BaseModel):
|
||||
description: str = None
|
||||
price: float
|
||||
tax: float = None
|
||||
tags: Set[str] = []
|
||||
tags: List[str] = []
|
||||
|
||||
|
||||
@app.post("/items/", response_model=Item)
|
||||
async def create_item(*, item: Item):
|
||||
async def create_item(item: Item):
|
||||
return item
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
from fastapi import FastAPI
|
||||
from pydantic import BaseModel
|
||||
from pydantic.types import EmailStr
|
||||
from pydantic import BaseModel, EmailStr
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
from fastapi import FastAPI
|
||||
from pydantic import BaseModel
|
||||
from pydantic.types import EmailStr
|
||||
from pydantic import BaseModel, EmailStr
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
26
docs/src/response_model/tutorial004.py
Normal file
26
docs/src/response_model/tutorial004.py
Normal file
@@ -0,0 +1,26 @@
|
||||
from typing import List
|
||||
|
||||
from fastapi import FastAPI
|
||||
from pydantic import BaseModel
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
class Item(BaseModel):
|
||||
name: str
|
||||
description: str = None
|
||||
price: float
|
||||
tax: float = 10.5
|
||||
tags: List[str] = []
|
||||
|
||||
|
||||
items = {
|
||||
"foo": {"name": "Foo", "price": 50.2},
|
||||
"bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
|
||||
"baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
|
||||
}
|
||||
|
||||
|
||||
@app.get("/items/{item_id}", response_model=Item, response_model_exclude_unset=True)
|
||||
async def read_item(item_id: str):
|
||||
return items[item_id]
|
||||
37
docs/src/response_model/tutorial005.py
Normal file
37
docs/src/response_model/tutorial005.py
Normal file
@@ -0,0 +1,37 @@
|
||||
from fastapi import FastAPI
|
||||
from pydantic import BaseModel
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
class Item(BaseModel):
|
||||
name: str
|
||||
description: str = None
|
||||
price: float
|
||||
tax: float = 10.5
|
||||
|
||||
|
||||
items = {
|
||||
"foo": {"name": "Foo", "price": 50.2},
|
||||
"bar": {"name": "Bar", "description": "The Bar fighters", "price": 62, "tax": 20.2},
|
||||
"baz": {
|
||||
"name": "Baz",
|
||||
"description": "There goes my baz",
|
||||
"price": 50.2,
|
||||
"tax": 10.5,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@app.get(
|
||||
"/items/{item_id}/name",
|
||||
response_model=Item,
|
||||
response_model_include={"name", "description"},
|
||||
)
|
||||
async def read_item_name(item_id: str):
|
||||
return items[item_id]
|
||||
|
||||
|
||||
@app.get("/items/{item_id}/public", response_model=Item, response_model_exclude={"tax"})
|
||||
async def read_item_public_data(item_id: str):
|
||||
return items[item_id]
|
||||
37
docs/src/response_model/tutorial006.py
Normal file
37
docs/src/response_model/tutorial006.py
Normal file
@@ -0,0 +1,37 @@
|
||||
from fastapi import FastAPI
|
||||
from pydantic import BaseModel
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
class Item(BaseModel):
|
||||
name: str
|
||||
description: str = None
|
||||
price: float
|
||||
tax: float = 10.5
|
||||
|
||||
|
||||
items = {
|
||||
"foo": {"name": "Foo", "price": 50.2},
|
||||
"bar": {"name": "Bar", "description": "The Bar fighters", "price": 62, "tax": 20.2},
|
||||
"baz": {
|
||||
"name": "Baz",
|
||||
"description": "There goes my baz",
|
||||
"price": 50.2,
|
||||
"tax": 10.5,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@app.get(
|
||||
"/items/{item_id}/name",
|
||||
response_model=Item,
|
||||
response_model_include=["name", "description"],
|
||||
)
|
||||
async def read_item_name(item_id: str):
|
||||
return items[item_id]
|
||||
|
||||
|
||||
@app.get("/items/{item_id}/public", response_model=Item, response_model_exclude=["tax"])
|
||||
async def read_item_public_data(item_id: str):
|
||||
return items[item_id]
|
||||
0
docs/src/sql_databases/sql_app/__init__.py
Normal file
0
docs/src/sql_databases/sql_app/__init__.py
Normal file
64
docs/src/sql_databases/sql_app/alt_main.py
Normal file
64
docs/src/sql_databases/sql_app/alt_main.py
Normal file
@@ -0,0 +1,64 @@
|
||||
from typing import List
|
||||
|
||||
from fastapi import Depends, FastAPI, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
from starlette.requests import Request
|
||||
from starlette.responses import Response
|
||||
|
||||
from . import crud, models, schemas
|
||||
from .database import SessionLocal, engine
|
||||
|
||||
models.Base.metadata.create_all(bind=engine)
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.middleware("http")
|
||||
async def db_session_middleware(request: Request, call_next):
|
||||
response = Response("Internal server error", status_code=500)
|
||||
try:
|
||||
request.state.db = SessionLocal()
|
||||
response = await call_next(request)
|
||||
finally:
|
||||
request.state.db.close()
|
||||
return response
|
||||
|
||||
|
||||
# Dependency
|
||||
def get_db(request: Request):
|
||||
return request.state.db
|
||||
|
||||
|
||||
@app.post("/users/", response_model=schemas.User)
|
||||
def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)):
|
||||
db_user = crud.get_user_by_email(db, email=user.email)
|
||||
if db_user:
|
||||
raise HTTPException(status_code=400, detail="Email already registered")
|
||||
return crud.create_user(db=db, user=user)
|
||||
|
||||
|
||||
@app.get("/users/", response_model=List[schemas.User])
|
||||
def read_users(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
|
||||
users = crud.get_users(db, skip=skip, limit=limit)
|
||||
return users
|
||||
|
||||
|
||||
@app.get("/users/{user_id}", response_model=schemas.User)
|
||||
def read_user(user_id: int, db: Session = Depends(get_db)):
|
||||
db_user = crud.get_user(db, user_id=user_id)
|
||||
if db_user is None:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
return db_user
|
||||
|
||||
|
||||
@app.post("/users/{user_id}/items/", response_model=schemas.Item)
|
||||
def create_item_for_user(
|
||||
user_id: int, item: schemas.ItemCreate, db: Session = Depends(get_db)
|
||||
):
|
||||
return crud.create_user_item(db=db, item=item, user_id=user_id)
|
||||
|
||||
|
||||
@app.get("/items/", response_model=List[schemas.Item])
|
||||
def read_items(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
|
||||
items = crud.get_items(db, skip=skip, limit=limit)
|
||||
return items
|
||||
36
docs/src/sql_databases/sql_app/crud.py
Normal file
36
docs/src/sql_databases/sql_app/crud.py
Normal file
@@ -0,0 +1,36 @@
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from . import models, schemas
|
||||
|
||||
|
||||
def get_user(db: Session, user_id: int):
|
||||
return db.query(models.User).filter(models.User.id == user_id).first()
|
||||
|
||||
|
||||
def get_user_by_email(db: Session, email: str):
|
||||
return db.query(models.User).filter(models.User.email == email).first()
|
||||
|
||||
|
||||
def get_users(db: Session, skip: int = 0, limit: int = 100):
|
||||
return db.query(models.User).offset(skip).limit(limit).all()
|
||||
|
||||
|
||||
def create_user(db: Session, user: schemas.UserCreate):
|
||||
fake_hashed_password = user.password + "notreallyhashed"
|
||||
db_user = models.User(email=user.email, hashed_password=fake_hashed_password)
|
||||
db.add(db_user)
|
||||
db.commit()
|
||||
db.refresh(db_user)
|
||||
return db_user
|
||||
|
||||
|
||||
def get_items(db: Session, skip: int = 0, limit: int = 100):
|
||||
return db.query(models.Item).offset(skip).limit(limit).all()
|
||||
|
||||
|
||||
def create_user_item(db: Session, item: schemas.ItemCreate, user_id: int):
|
||||
db_item = models.Item(**item.dict(), owner_id=user_id)
|
||||
db.add(db_item)
|
||||
db.commit()
|
||||
db.refresh(db_item)
|
||||
return db_item
|
||||
13
docs/src/sql_databases/sql_app/database.py
Normal file
13
docs/src/sql_databases/sql_app/database.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"
|
||||
# SQLALCHEMY_DATABASE_URL = "postgresql://user:password@postgresserver/db"
|
||||
|
||||
engine = create_engine(
|
||||
SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
|
||||
)
|
||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
|
||||
Base = declarative_base()
|
||||
55
docs/src/sql_databases/sql_app/main.py
Normal file
55
docs/src/sql_databases/sql_app/main.py
Normal file
@@ -0,0 +1,55 @@
|
||||
from typing import List
|
||||
|
||||
from fastapi import Depends, FastAPI, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from . import crud, models, schemas
|
||||
from .database import SessionLocal, engine
|
||||
|
||||
models.Base.metadata.create_all(bind=engine)
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
# Dependency
|
||||
def get_db():
|
||||
try:
|
||||
db = SessionLocal()
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@app.post("/users/", response_model=schemas.User)
|
||||
def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)):
|
||||
db_user = crud.get_user_by_email(db, email=user.email)
|
||||
if db_user:
|
||||
raise HTTPException(status_code=400, detail="Email already registered")
|
||||
return crud.create_user(db=db, user=user)
|
||||
|
||||
|
||||
@app.get("/users/", response_model=List[schemas.User])
|
||||
def read_users(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
|
||||
users = crud.get_users(db, skip=skip, limit=limit)
|
||||
return users
|
||||
|
||||
|
||||
@app.get("/users/{user_id}", response_model=schemas.User)
|
||||
def read_user(user_id: int, db: Session = Depends(get_db)):
|
||||
db_user = crud.get_user(db, user_id=user_id)
|
||||
if db_user is None:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
return db_user
|
||||
|
||||
|
||||
@app.post("/users/{user_id}/items/", response_model=schemas.Item)
|
||||
def create_item_for_user(
|
||||
user_id: int, item: schemas.ItemCreate, db: Session = Depends(get_db)
|
||||
):
|
||||
return crud.create_user_item(db=db, item=item, user_id=user_id)
|
||||
|
||||
|
||||
@app.get("/items/", response_model=List[schemas.Item])
|
||||
def read_items(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
|
||||
items = crud.get_items(db, skip=skip, limit=limit)
|
||||
return items
|
||||
26
docs/src/sql_databases/sql_app/models.py
Normal file
26
docs/src/sql_databases/sql_app/models.py
Normal file
@@ -0,0 +1,26 @@
|
||||
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from .database import Base
|
||||
|
||||
|
||||
class User(Base):
|
||||
__tablename__ = "users"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
email = Column(String, unique=True, index=True)
|
||||
hashed_password = Column(String)
|
||||
is_active = Column(Boolean, default=True)
|
||||
|
||||
items = relationship("Item", back_populates="owner")
|
||||
|
||||
|
||||
class Item(Base):
|
||||
__tablename__ = "items"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
title = Column(String, index=True)
|
||||
description = Column(String, index=True)
|
||||
owner_id = Column(Integer, ForeignKey("users.id"))
|
||||
|
||||
owner = relationship("User", back_populates="items")
|
||||
37
docs/src/sql_databases/sql_app/schemas.py
Normal file
37
docs/src/sql_databases/sql_app/schemas.py
Normal file
@@ -0,0 +1,37 @@
|
||||
from typing import List
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class ItemBase(BaseModel):
|
||||
title: str
|
||||
description: str = None
|
||||
|
||||
|
||||
class ItemCreate(ItemBase):
|
||||
pass
|
||||
|
||||
|
||||
class Item(ItemBase):
|
||||
id: int
|
||||
owner_id: int
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
|
||||
class UserBase(BaseModel):
|
||||
email: str
|
||||
|
||||
|
||||
class UserCreate(UserBase):
|
||||
password: str
|
||||
|
||||
|
||||
class User(UserBase):
|
||||
id: int
|
||||
is_active: bool
|
||||
items: List[Item] = []
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
@@ -1,76 +0,0 @@
|
||||
from fastapi import Depends, FastAPI
|
||||
from sqlalchemy import Boolean, Column, Integer, String, create_engine
|
||||
from sqlalchemy.ext.declarative import declarative_base, declared_attr
|
||||
from sqlalchemy.orm import Session, sessionmaker
|
||||
from starlette.requests import Request
|
||||
from starlette.responses import Response
|
||||
|
||||
# SQLAlchemy specific code, as with any other app
|
||||
SQLALCHEMY_DATABASE_URI = "sqlite:///./test.db"
|
||||
# SQLALCHEMY_DATABASE_URI = "postgresql://user:password@postgresserver/db"
|
||||
|
||||
engine = create_engine(
|
||||
SQLALCHEMY_DATABASE_URI, connect_args={"check_same_thread": False}
|
||||
)
|
||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
|
||||
|
||||
class CustomBase:
|
||||
# Generate __tablename__ automatically
|
||||
@declared_attr
|
||||
def __tablename__(cls):
|
||||
return cls.__name__.lower()
|
||||
|
||||
|
||||
Base = declarative_base(cls=CustomBase)
|
||||
|
||||
|
||||
class User(Base):
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
email = Column(String, unique=True, index=True)
|
||||
hashed_password = Column(String)
|
||||
is_active = Column(Boolean(), default=True)
|
||||
|
||||
|
||||
Base.metadata.create_all(bind=engine)
|
||||
|
||||
db_session = SessionLocal()
|
||||
|
||||
first_user = db_session.query(User).first()
|
||||
if not first_user:
|
||||
u = User(email="johndoe@example.com", hashed_password="notreallyhashed")
|
||||
db_session.add(u)
|
||||
db_session.commit()
|
||||
|
||||
db_session.close()
|
||||
|
||||
|
||||
# Utility
|
||||
def get_user(db_session: Session, user_id: int):
|
||||
return db_session.query(User).filter(User.id == user_id).first()
|
||||
|
||||
|
||||
# Dependency
|
||||
def get_db(request: Request):
|
||||
return request.state.db
|
||||
|
||||
|
||||
# FastAPI specific code
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.get("/users/{user_id}")
|
||||
def read_user(user_id: int, db: Session = Depends(get_db)):
|
||||
user = get_user(db, user_id=user_id)
|
||||
return user
|
||||
|
||||
|
||||
@app.middleware("http")
|
||||
async def db_session_middleware(request: Request, call_next):
|
||||
response = Response("Internal server error", status_code=500)
|
||||
try:
|
||||
request.state.db = SessionLocal()
|
||||
response = await call_next(request)
|
||||
finally:
|
||||
request.state.db.close()
|
||||
return response
|
||||
0
docs/src/websockets/__init__.py
Normal file
0
docs/src/websockets/__init__.py
Normal file
@@ -44,10 +44,9 @@ async def get():
|
||||
return HTMLResponse(html)
|
||||
|
||||
|
||||
@app.websocket_route("/ws")
|
||||
@app.websocket("/ws")
|
||||
async def websocket_endpoint(websocket: WebSocket):
|
||||
await websocket.accept()
|
||||
while True:
|
||||
data = await websocket.receive_text()
|
||||
await websocket.send_text(f"Message text was: {data}")
|
||||
await websocket.close()
|
||||
|
||||
78
docs/src/websockets/tutorial002.py
Normal file
78
docs/src/websockets/tutorial002.py
Normal file
@@ -0,0 +1,78 @@
|
||||
from fastapi import Cookie, Depends, FastAPI, Header
|
||||
from starlette.responses import HTMLResponse
|
||||
from starlette.status import WS_1008_POLICY_VIOLATION
|
||||
from starlette.websockets import WebSocket
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
html = """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Chat</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>WebSocket Chat</h1>
|
||||
<form action="" onsubmit="sendMessage(event)">
|
||||
<label>Item ID: <input type="text" id="itemId" autocomplete="off" value="foo"/></label>
|
||||
<button onclick="connect(event)">Connect</button>
|
||||
<br>
|
||||
<label>Message: <input type="text" id="messageText" autocomplete="off"/></label>
|
||||
<button>Send</button>
|
||||
</form>
|
||||
<ul id='messages'>
|
||||
</ul>
|
||||
<script>
|
||||
var ws = null;
|
||||
function connect(event) {
|
||||
var input = document.getElementById("itemId")
|
||||
ws = new WebSocket("ws://localhost:8000/items/" + input.value + "/ws");
|
||||
ws.onmessage = function(event) {
|
||||
var messages = document.getElementById('messages')
|
||||
var message = document.createElement('li')
|
||||
var content = document.createTextNode(event.data)
|
||||
message.appendChild(content)
|
||||
messages.appendChild(message)
|
||||
};
|
||||
}
|
||||
function sendMessage(event) {
|
||||
var input = document.getElementById("messageText")
|
||||
ws.send(input.value)
|
||||
input.value = ''
|
||||
event.preventDefault()
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
|
||||
@app.get("/")
|
||||
async def get():
|
||||
return HTMLResponse(html)
|
||||
|
||||
|
||||
async def get_cookie_or_client(
|
||||
websocket: WebSocket, session: str = Cookie(None), x_client: str = Header(None)
|
||||
):
|
||||
if session is None and x_client is None:
|
||||
await websocket.close(code=WS_1008_POLICY_VIOLATION)
|
||||
return session or x_client
|
||||
|
||||
|
||||
@app.websocket("/items/{item_id}/ws")
|
||||
async def websocket_endpoint(
|
||||
websocket: WebSocket,
|
||||
item_id: int,
|
||||
q: str = None,
|
||||
cookie_or_client: str = Depends(get_cookie_or_client),
|
||||
):
|
||||
await websocket.accept()
|
||||
while True:
|
||||
data = await websocket.receive_text()
|
||||
await websocket.send_text(
|
||||
f"Session Cookie or X-Client Header value is: {cookie_or_client}"
|
||||
)
|
||||
if q is not None:
|
||||
await websocket.send_text(f"Query parameter q is: {q}")
|
||||
await websocket.send_text(f"Message text was: {data}, for item ID: {item_id}")
|
||||
@@ -174,6 +174,11 @@ For example, you can add an additional media type of `image/png`, declaring that
|
||||
!!! note
|
||||
Notice that you have to return the image using a `FileResponse` directly.
|
||||
|
||||
!!! info
|
||||
Unless you specify a different media type explicitly in your `responses` parameter, FastAPI will assume the response has the same media type as the main response class (default `application/json`).
|
||||
|
||||
But if you have specified a custom response class with `None` as its media type, FastAPI will use `application/json` for any additional response that has an associated model.
|
||||
|
||||
## Combining information
|
||||
|
||||
You can also combine response information from multiple places, including the `response_model`, `status_code`, and `responses` parameters.
|
||||
|
||||
@@ -41,7 +41,7 @@ Later, for your production application, you might want to use a database server
|
||||
```
|
||||
|
||||
!!! tip
|
||||
If you where connecting to a different database (e.g. PostgreSQL), you would need to change the `DATABASE_URL`.
|
||||
If you were connecting to a different database (e.g. PostgreSQL), you would need to change the `DATABASE_URL`.
|
||||
|
||||
## Create the tables
|
||||
|
||||
|
||||
@@ -174,7 +174,6 @@ from app.routers import items, users
|
||||
|
||||
To learn more about Python Packages and Modules, read <a href="https://docs.python.org/3/tutorial/modules.html" target="_blank">the official Python documentation about Modules</a>.
|
||||
|
||||
|
||||
### Avoid name collisions
|
||||
|
||||
We are importing the submodule `items` directly, instead of importing just its variable `router`.
|
||||
@@ -216,7 +215,6 @@ It will include all the routes from that router as part of it.
|
||||
|
||||
So, behind the scenes, it will actually work as if everything was the same single app.
|
||||
|
||||
|
||||
!!! check
|
||||
You don't have to worry about performance when including routers.
|
||||
|
||||
@@ -248,7 +246,7 @@ We can also add a list of `tags` that will be applied to all the *path operation
|
||||
|
||||
And we can add predefined `responses` that will be included in all the *path operations* too.
|
||||
|
||||
And we can add a list of `dependencies` that will be added to all the *path operations* in the router and will be executed/solved for each request made to them.
|
||||
And we can add a list of `dependencies` that will be added to all the *path operations* in the router and will be executed/solved for each request made to them. Note that, much like dependencies in *path operation decorators*, no value will be passed to your *path operation function*.
|
||||
|
||||
```Python hl_lines="8 9 10 14 15 16 17 18 19 20"
|
||||
{!./src/bigger_applications/app/main.py!}
|
||||
@@ -295,7 +293,6 @@ The end result is that the item paths are now:
|
||||
|
||||
As we cannot just isolate them and "mount" them independently of the rest, the path operations are "cloned" (re-created), not included directly.
|
||||
|
||||
|
||||
## Check the automatic API docs
|
||||
|
||||
Now, run `uvicorn`, using the module `app.main` and the variable `app`:
|
||||
@@ -309,3 +306,11 @@ And open the docs at <a href="http://127.0.0.1:8000/docs" target="_blank">http:/
|
||||
You will see the automatic API docs, including the paths from all the submodules, using the correct paths (and prefixes) and the correct tags:
|
||||
|
||||
<img src="/img/tutorial/bigger-applications/image01.png">
|
||||
|
||||
## Include the same router multiple times with different `prefix`
|
||||
|
||||
You can also use `.include_router()` multiple times with the *same* router using different prefixes.
|
||||
|
||||
This could be useful, for example, to expose the same API under different prefixes, e.g. `/api/v1` and `/api/latest`.
|
||||
|
||||
This is an advanced usage that you might not really need, but it's there in case you do.
|
||||
|
||||
62
docs/tutorial/body-fields.md
Normal file
62
docs/tutorial/body-fields.md
Normal file
@@ -0,0 +1,62 @@
|
||||
The same way you can declare additional validation and metadata in path operation function parameters with `Query`, `Path` and `Body`, you can declare validation and metadata inside of Pydantic models using Pydantic's `Field`.
|
||||
|
||||
## Import `Field`
|
||||
|
||||
First, you have to import it:
|
||||
|
||||
```Python hl_lines="2"
|
||||
{!./src/body_fields/tutorial001.py!}
|
||||
```
|
||||
|
||||
!!! warning
|
||||
Notice that `Field` is imported directly from `pydantic`, not from `fastapi` as are all the rest (`Query`, `Path`, `Body`, etc).
|
||||
|
||||
|
||||
## Declare model attributes
|
||||
|
||||
You can then use `Field` with model attributes:
|
||||
|
||||
```Python hl_lines="9 10"
|
||||
{!./src/body_fields/tutorial001.py!}
|
||||
```
|
||||
|
||||
`Field` works the same way as `Query`, `Path` and `Body`, it has all the same parameters, etc.
|
||||
|
||||
!!! note "Technical Details"
|
||||
Actually, `Query`, `Path` and others you'll see next create objects of subclasses of a common `Param` class, which is itself a subclass of Pydantic's `FieldInfo` class.
|
||||
|
||||
And Pydantic's `Field` returns an instance of `FieldInfo` as well.
|
||||
|
||||
`Body` also returns objects of a subclass of `FieldInfo` directly. And there are others you will see later that are subclasses of the `Body` class.
|
||||
|
||||
Remember that when you import `Query`, `Path`, and others from `fastapi`, <a href="https://fastapi.tiangolo.com/tutorial/path-params-numeric-validations/#recap" target="_blank">those are actually functions that return classes of the same name</a>.
|
||||
|
||||
!!! tip
|
||||
Notice how each model's attribute with a type, default value and `Field` has the same structure as a path operation function's parameter, with `Field` instead of `Path`, `Query` and `Body`.
|
||||
|
||||
## JSON Schema extras
|
||||
|
||||
In `Field`, `Path`, `Query`, `Body` and others you'll see later, you can declare extra parameters apart from those described before.
|
||||
|
||||
Those parameters will be added as-is to the output JSON Schema.
|
||||
|
||||
If you know JSON Schema and want to add extra information apart from what we have discussed here, you can pass that as extra keyword arguments.
|
||||
|
||||
!!! warning
|
||||
Have in mind that extra parameters passed won't add any validation, only annotation, for documentation purposes.
|
||||
|
||||
For example, you can use that functionality to pass a <a href="http://json-schema.org/latest/json-schema-validation.html#rfc.section.8.5" target="_blank">JSON Schema example</a> field to a body request JSON Schema:
|
||||
|
||||
```Python hl_lines="20 21 22 23 24 25"
|
||||
{!./src/body_fields/tutorial002.py!}
|
||||
```
|
||||
|
||||
And it would look in the `/docs` like this:
|
||||
|
||||
<img src="/img/tutorial/body-fields/image01.png">
|
||||
|
||||
## Recap
|
||||
|
||||
You can use Pydantic's `Field` to declare extra validations and metadata for model attributes.
|
||||
|
||||
You can also use the extra keyword arguments to pass additional JSON Schema metadata.
|
||||
@@ -200,6 +200,35 @@ You couldn't get this kind of editor support if you where working directly with
|
||||
|
||||
But you don't have to worry about them either, incoming dicts are converted automatically and your output is converted automatically to JSON too.
|
||||
|
||||
## Bodies of arbitrary `dict`s
|
||||
|
||||
You can also declare a body as a `dict` with keys of some type and values of other type.
|
||||
|
||||
Without having to know beforehand what are the valid field/attribute names (as would be the case with Pydantic models).
|
||||
|
||||
This would be useful if you want to receive keys that you don't already know.
|
||||
|
||||
---
|
||||
|
||||
Other useful case is when you want to have keys of other type, e.g. `int`.
|
||||
|
||||
That's what we are going to see here.
|
||||
|
||||
In this case, you would accept any `dict` as long as it has `int` keys with `float` values:
|
||||
|
||||
```Python hl_lines="15"
|
||||
{!./src/body_nested_models/tutorial009.py!}
|
||||
```
|
||||
|
||||
!!! tip
|
||||
Have in mind that JSON only supports `str` as keys.
|
||||
|
||||
But Pydantic has automatic data conversion.
|
||||
|
||||
This means that, even though your API clients can only send strings as keys, as long as those strings contain pure integers, Pydantic will convert them and validate them.
|
||||
|
||||
And the `dict` you receive as `weights` will actually have `int` keys and `float` values.
|
||||
|
||||
## Recap
|
||||
|
||||
With **FastAPI** you have the maximum flexibility provided by Pydantic models, while keeping your code simple, short and elegant.
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
The same way you can declare additional validation and metadata in path operation function parameters with `Query`, `Path` and `Body`, you can declare validation and metadata inside of Pydantic models using `Schema`.
|
||||
|
||||
## Import Schema
|
||||
|
||||
First, you have to import it:
|
||||
|
||||
```Python hl_lines="2"
|
||||
{!./src/body_schema/tutorial001.py!}
|
||||
```
|
||||
|
||||
!!! warning
|
||||
Notice that `Schema` is imported directly from `pydantic`, not from `fastapi` as are all the rest (`Query`, `Path`, `Body`, etc).
|
||||
|
||||
|
||||
## Declare model attributes
|
||||
|
||||
You can then use `Schema` with model attributes:
|
||||
|
||||
```Python hl_lines="9 10"
|
||||
{!./src/body_schema/tutorial001.py!}
|
||||
```
|
||||
|
||||
`Schema` works the same way as `Query`, `Path` and `Body`, it has all the same parameters, etc.
|
||||
|
||||
!!! note "Technical Details"
|
||||
Actually, `Query`, `Path` and others you'll see next are subclasses of a common `Param` which is itself a subclass of Pydantic's `Schema`.
|
||||
|
||||
`Body` is also a subclass of `Schema` directly. And there are others you will see later that are subclasses of `Body`.
|
||||
|
||||
But remember that when you import `Query`, `Path` and others from `fastapi`, <a href="https://fastapi.tiangolo.com/tutorial/path-params-numeric-validations/#recap" target="_blank">those are actually functions that return classes of the same name</a>.
|
||||
|
||||
!!! tip
|
||||
Notice how each model's attribute with a type, default value and `Schema` has the same structure as a path operation function's parameter, with `Schema` instead of `Path`, `Query` and `Body`.
|
||||
|
||||
## Schema extras
|
||||
|
||||
In `Schema`, `Path`, `Query`, `Body` and others you'll see later, you can declare extra parameters apart from those described before.
|
||||
|
||||
Those parameters will be added as-is to the output JSON Schema.
|
||||
|
||||
If you know JSON Schema and want to add extra information apart from what we have discussed here, you can pass that as extra keyword arguments.
|
||||
|
||||
!!! warning
|
||||
Have in mind that extra parameters passed won't add any validation, only annotation, for documentation purposes.
|
||||
|
||||
For example, you can use that functionality to pass a <a href="http://json-schema.org/latest/json-schema-validation.html#rfc.section.8.5" target="_blank">JSON Schema example</a> field to a body request JSON Schema:
|
||||
|
||||
```Python hl_lines="20 21 22 23 24 25"
|
||||
{!./src/body_schema/tutorial002.py!}
|
||||
```
|
||||
|
||||
And it would look in the `/docs` like this:
|
||||
|
||||
<img src="/img/tutorial/body-schema/image01.png">
|
||||
|
||||
## Recap
|
||||
|
||||
You can use Pydantic's `Schema` to declare extra validations and metadata for model attributes.
|
||||
|
||||
You can also use the extra keyword arguments to pass additional JSON Schema metadata.
|
||||
97
docs/tutorial/body-updates.md
Normal file
97
docs/tutorial/body-updates.md
Normal file
@@ -0,0 +1,97 @@
|
||||
## Update replacing with `PUT`
|
||||
|
||||
To update an item you can use the [HTTP `PUT`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PUT) operation.
|
||||
|
||||
You can use the `jsonable_encoder` to convert the input data to data that can be stored as JSON (e.g. with a NoSQL database). For example, converting `datetime` to `str`.
|
||||
|
||||
```Python hl_lines="30 31 32 33 34 35"
|
||||
{!./src/body_updates/tutorial001.py!}
|
||||
```
|
||||
|
||||
`PUT` is used to receive data that should replace the existing data.
|
||||
|
||||
### Warning about replacing
|
||||
|
||||
That means that if you want to update the item `bar` using `PUT` with a body containing:
|
||||
|
||||
```Python
|
||||
{
|
||||
"name": "Barz",
|
||||
"price": 3,
|
||||
"description": None,
|
||||
}
|
||||
```
|
||||
|
||||
because it doesn't include the already stored attribute `"tax": 20.2`, the input model would take the default value of `"tax": 10.5`.
|
||||
|
||||
And the data would be saved with that "new" `tax` of `10.5`.
|
||||
|
||||
## Partial updates with `PATCH`
|
||||
|
||||
You can also use the [HTTP `PATCH`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PATCH) operation to *partially* update data.
|
||||
|
||||
This means that you can send only the data that you want to update, leaving the rest intact.
|
||||
|
||||
!!! Note
|
||||
`PATCH` is less commonly used and known than `PUT`.
|
||||
|
||||
And many teams use only `PUT`, even for partial updates.
|
||||
|
||||
You are **free** to use them however you want, **FastAPI** doesn't impose any restrictions.
|
||||
|
||||
But this guide shows you, more or less, how they are intended to be used.
|
||||
|
||||
### Using Pydantic's `exclude_unset` parameter
|
||||
|
||||
If you want to receive partial updates, it's very useful to use the parameter `exclude_unset` in Pydantic's model's `.dict()`.
|
||||
|
||||
Like `item.dict(exclude_unset=True)`.
|
||||
|
||||
That would generate a `dict` with only the data that was set when creating the `item` model, excluding default values.
|
||||
|
||||
Then you can use this to generate a `dict` with only the data that was set (sent in the request), omitting default values:
|
||||
|
||||
```Python hl_lines="34"
|
||||
{!./src/body_updates/tutorial002.py!}
|
||||
```
|
||||
|
||||
### Using Pydantic's `update` parameter
|
||||
|
||||
Now, you can create a copy of the existing model using `.copy()`, and pass the `update` parameter with a `dict` containing the data to update.
|
||||
|
||||
Like `stored_item_model.copy(update=update_data)`:
|
||||
|
||||
```Python hl_lines="35"
|
||||
{!./src/body_updates/tutorial002.py!}
|
||||
```
|
||||
|
||||
### Partial updates recap
|
||||
|
||||
In summary, to apply partial updates you would:
|
||||
|
||||
* (Optionally) use `PATCH` instead of `PUT`.
|
||||
* Retrieve the stored data.
|
||||
* Put that data in a Pydantic model.
|
||||
* Generate a `dict` without default values from the input model (using `exclude_unset`).
|
||||
* This way you can update only the values actually set by the user, instead of overriding values already stored with default values in your model.
|
||||
* Create a copy of the stored model, updating it's attributes with the received partial updates (using the `update` parameter).
|
||||
* Convert the copied model to something that can be stored in your DB (for example, using the `jsonable_encoder`).
|
||||
* This is comparable to using the model's `.dict()` method again, but it makes sure (and converts) the values to data types that can be converted to JSON, for example, `datetime` to `str`.
|
||||
* Save the data to your DB.
|
||||
* Return the updated model.
|
||||
|
||||
```Python hl_lines="30 31 32 33 34 35 36 37"
|
||||
{!./src/body_updates/tutorial002.py!}
|
||||
```
|
||||
|
||||
!!! tip
|
||||
You can actually use this same technique with an HTTP `PUT` operation.
|
||||
|
||||
But the example here uses `PATCH` because it was created for these use cases.
|
||||
|
||||
!!! note
|
||||
Notice that the input model is still validated.
|
||||
|
||||
So, if you want to receive partial updates that can omit all the attributes, you need to have a model with all the attributes marked as optional (with default values or `None`).
|
||||
|
||||
To distinguish from the models with all optional values for **updates** and models with required values for **creation**, you can use the ideas described in <a href="https://fastapi.tiangolo.com/tutorial/extra-models/" target="_blank">Extra Models</a>.
|
||||
100
docs/tutorial/custom-request-and-route.md
Normal file
100
docs/tutorial/custom-request-and-route.md
Normal file
@@ -0,0 +1,100 @@
|
||||
In some cases, you may want to override the logic used by the `Request` and `APIRoute` classes.
|
||||
|
||||
In particular, this may be a good alternative to logic in a middleware.
|
||||
|
||||
For example, if you want to read or manipulate the request body before it is processed by your application.
|
||||
|
||||
!!! danger
|
||||
This is an "advanced" feature.
|
||||
|
||||
If you are just starting with **FastAPI** you might want to skip this section.
|
||||
|
||||
## Use cases
|
||||
|
||||
Some use cases include:
|
||||
|
||||
* Converting non-JSON request bodies to JSON (e.g. [`msgpack`](https://msgpack.org/index.html)).
|
||||
* Decompressing gzip-compressed request bodies.
|
||||
* Automatically logging all request bodies.
|
||||
* Accessing the request body in an exception handler.
|
||||
|
||||
## Handling custom request body encodings
|
||||
|
||||
Let's see how to make use of a custom `Request` subclass to decompress gzip requests.
|
||||
|
||||
And an `APIRoute` subclass to use that custom request class.
|
||||
|
||||
### Create a custom `GzipRequest` class
|
||||
|
||||
First, we create a `GzipRequest` class, which will overwrite the `Request.body()` method to decompress the body in the presence of an appropriate header.
|
||||
|
||||
If there's no `gzip` in the header, it will not try to decompress the body.
|
||||
|
||||
That way, the same route class can handle gzip compressed or uncompressed requests.
|
||||
|
||||
```Python hl_lines="10 11 12 13 14 15 16 17"
|
||||
{!./src/custom_request_and_route/tutorial001.py!}
|
||||
```
|
||||
|
||||
### Create a custom `GzipRoute` class
|
||||
|
||||
Next, we create a custom subclass of `fastapi.routing.APIRoute` that will make use of the `GzipRequest`.
|
||||
|
||||
This time, it will overwrite the method `APIRoute.get_route_handler()`.
|
||||
|
||||
This method returns a function. And that function is what will receive a request and return a response.
|
||||
|
||||
Here we use it to create a `GzipRequest` from the original request.
|
||||
|
||||
```Python hl_lines="20 21 22 23 24 25 26 27 28"
|
||||
{!./src/custom_request_and_route/tutorial001.py!}
|
||||
```
|
||||
|
||||
!!! note "Technical Details"
|
||||
A `Request` has a `request.scope` attribute, that's just a Python `dict` containing the metadata related to the request.
|
||||
|
||||
A `Request` also has a `request.receive`, that's a function to "receive" the body of the request.
|
||||
|
||||
The `scope` `dict` and `receive` function are both part of the ASGI specification.
|
||||
|
||||
And those two things, `scope` and `receive`, are what is needed to create a new `Request` instance.
|
||||
|
||||
To learn more about the `Request` check <a href="https://www.starlette.io/requests/" target="_blank">Starlette's docs about Requests</a>.
|
||||
|
||||
The only thing the function returned by `GzipRequest.get_route_handler` does differently is convert the `Request` to a `GzipRequest`.
|
||||
|
||||
Doing this, our `GzipRequest` will take care of decompressing the data (if necessary) before passing it to our *path operations*.
|
||||
|
||||
After that, all of the processing logic is the same.
|
||||
|
||||
But because of our changes in `GzipRequest.body`, the request body will be automatically decompressed when it is loaded by **FastAPI** when needed.
|
||||
|
||||
## Accessing the request body in an exception handler
|
||||
|
||||
We can also use this same approach to access the request body in an exception handler.
|
||||
|
||||
All we need to do is handle the request inside a `try`/`except` block:
|
||||
|
||||
```Python hl_lines="15 17"
|
||||
{!./src/custom_request_and_route/tutorial002.py!}
|
||||
```
|
||||
|
||||
If an exception occurs, the`Request` instance will still be in scope, so we can read and make use of the request body when handling the error:
|
||||
|
||||
```Python hl_lines="18 19 20"
|
||||
{!./src/custom_request_and_route/tutorial002.py!}
|
||||
```
|
||||
|
||||
## Custom `APIRoute` class in a router
|
||||
|
||||
You can also set the `route_class` parameter of an `APIRouter`:
|
||||
|
||||
```Python hl_lines="25"
|
||||
{!./src/custom_request_and_route/tutorial003.py!}
|
||||
```
|
||||
|
||||
In this example, the *path operations* under the `router` will use the custom `TimedRoute` class, and will have an extra `X-Response-Time` header in the response with the time it took to generate the response:
|
||||
|
||||
```Python hl_lines="15 16 17 18 19"
|
||||
{!./src/custom_request_and_route/tutorial003.py!}
|
||||
```
|
||||
@@ -15,6 +15,9 @@ The contents that you return from your *path operation function* will be put ins
|
||||
|
||||
And if that `Response` has a JSON media type (`application/json`), like is the case with the `JSONResponse` and `UJSONResponse`, the data you return will be automatically converted (and filtered) with any Pydantic `response_model` that you declared in the *path operation decorator*.
|
||||
|
||||
!!! note
|
||||
If you use a response class with no media type, FastAPI will expect your response to have no content, so it will not document the response format in its generated OpenAPI docs.
|
||||
|
||||
## Use `UJSONResponse`
|
||||
|
||||
For example, if you are squeezing performance, you can install and use `ujson` and set the response to be Starlette's `UJSONResponse`.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
!!! danger
|
||||
!!! warning
|
||||
This is, more or less, an "advanced" chapter.
|
||||
|
||||
If you are just starting with **FastAPI** you might want to skip this chapter and come back to it later.
|
||||
@@ -22,7 +22,7 @@ Not the class itself (which is already a callable), but an instance of that clas
|
||||
To do that, we declare a method `__call__`:
|
||||
|
||||
```Python hl_lines="10"
|
||||
{!./src/dependencies/tutorial007.py!}
|
||||
{!./src/dependencies/tutorial011.py!}
|
||||
```
|
||||
|
||||
In this case, this `__call__` is what **FastAPI** will use to check for additional parameters and sub-dependencies, and this is what will be called to pass a value to the parameter in your *path operation function* later.
|
||||
@@ -32,7 +32,7 @@ In this case, this `__call__` is what **FastAPI** will use to check for addition
|
||||
And now, we can use `__init__` to declare the parameters of the instance that we can use to "parameterize" the dependency:
|
||||
|
||||
```Python hl_lines="7"
|
||||
{!./src/dependencies/tutorial007.py!}
|
||||
{!./src/dependencies/tutorial011.py!}
|
||||
```
|
||||
|
||||
In this case, **FastAPI** won't ever touch or care about `__init__`, we will use it directly in our code.
|
||||
@@ -42,7 +42,7 @@ In this case, **FastAPI** won't ever touch or care about `__init__`, we will use
|
||||
We could create an instance of this class with:
|
||||
|
||||
```Python hl_lines="16"
|
||||
{!./src/dependencies/tutorial007.py!}
|
||||
{!./src/dependencies/tutorial011.py!}
|
||||
```
|
||||
|
||||
And that way we are able to "parameterize" our dependency, that now has `"bar"` inside of it, as the attribute `checker.fixed_content`.
|
||||
@@ -60,7 +60,7 @@ checker(q="somequery")
|
||||
...and pass whatever that returns as the value of the dependency in our path operation function as the parameter `fixed_content_included`:
|
||||
|
||||
```Python hl_lines="20"
|
||||
{!./src/dependencies/tutorial007.py!}
|
||||
{!./src/dependencies/tutorial011.py!}
|
||||
```
|
||||
|
||||
!!! tip
|
||||
|
||||
@@ -2,7 +2,7 @@ Before diving deeper into the **Dependency Injection** system, let's upgrade the
|
||||
|
||||
## A `dict` from the previous example
|
||||
|
||||
In the previous example, we where returning a `dict` from our dependency ("dependable"):
|
||||
In the previous example, we are returning a `dict` from our dependency ("dependable"):
|
||||
|
||||
```Python hl_lines="7"
|
||||
{!./src/dependencies/tutorial001.py!}
|
||||
|
||||
153
docs/tutorial/dependencies/dependencies-with-yield.md
Normal file
153
docs/tutorial/dependencies/dependencies-with-yield.md
Normal file
@@ -0,0 +1,153 @@
|
||||
# Dependencies with `yield`
|
||||
|
||||
FastAPI supports dependencies that do some <abbr title='sometimes also called "exit", "cleanup", "teardown", "close", "context managers", ...'>extra steps after finishing</abbr>.
|
||||
|
||||
To do this, use `yield` instead of `return`, and write the extra steps after.
|
||||
|
||||
!!! tip
|
||||
Make sure to use `yield` one single time.
|
||||
|
||||
!!! info
|
||||
For this to work, you need to use **Python 3.7** or above, or in **Python 3.6**, install the "backports":
|
||||
|
||||
```bash
|
||||
pip install async-exit-stack async-generator
|
||||
```
|
||||
|
||||
This installs <a href="https://github.com/sorcio/async_exit_stack" target="_blank">async-exit-stack</a> and <a href="https://github.com/python-trio/async_generator" target="_blank">async-generator</a>.
|
||||
|
||||
!!! note "Technical Details"
|
||||
Any function that is valid to use with:
|
||||
|
||||
* <a href="https://docs.python.org/3/library/contextlib.html#contextlib.contextmanager" target="_blank">`@contextlib.contextmanager`</a> or
|
||||
* <a href="https://docs.python.org/3/library/contextlib.html#contextlib.asynccontextmanager" target="_blank">`@contextlib.asynccontextmanager`</a>
|
||||
|
||||
would be valid to use as a **FastAPI** dependency.
|
||||
|
||||
In fact, FastAPI uses those two decorators internally.
|
||||
|
||||
## A database dependency with `yield`
|
||||
|
||||
For example, you could use this to create a database session and close it after finishing.
|
||||
|
||||
Only the code prior to and including the `yield` statement is executed before sending a response:
|
||||
|
||||
```Python hl_lines="2 3 4"
|
||||
{!./src/dependencies/tutorial007.py!}
|
||||
```
|
||||
|
||||
The yielded value is what is injected into *path operations* and other dependencies:
|
||||
|
||||
```Python hl_lines="4"
|
||||
{!./src/dependencies/tutorial007.py!}
|
||||
```
|
||||
|
||||
The code following the `yield` statement is executed after the response has been delivered:
|
||||
|
||||
```Python hl_lines="5 6"
|
||||
{!./src/dependencies/tutorial007.py!}
|
||||
```
|
||||
|
||||
!!! tip
|
||||
You can use `async` or normal functions.
|
||||
|
||||
**FastAPI** will do the right thing with each, the same as with normal dependencies.
|
||||
|
||||
## A dependency with `yield` and `try`
|
||||
|
||||
If you use a `try` block in a dependency with `yield`, you'll receive any exception that was thrown when using the dependency.
|
||||
|
||||
For example, if some code at some point in the middle, in another dependency or in a *path operation*, made a database transaction "rollback" or create any other error, you will receive the exception in your dependency.
|
||||
|
||||
So, you can look for that specific exception inside the dependency with `except SomeException`.
|
||||
|
||||
In the same way, you can use `finally` to make sure the exit steps are executed, no matter if there was an exception or not.
|
||||
|
||||
```Python hl_lines="3 5"
|
||||
{!./src/dependencies/tutorial007.py!}
|
||||
```
|
||||
|
||||
## Sub-dependencies with `yield`
|
||||
|
||||
You can have sub-dependencies and "trees" of sub-dependencies of any size and shape, and any or all of them can use `yield`.
|
||||
|
||||
**FastAPI** will make sure that the "exit code" in each dependency with `yield` is run in the correct order.
|
||||
|
||||
For example, `dependency_c` can have a dependency on `dependency_b`, and `dependency_b` on `dependency_a`:
|
||||
|
||||
```Python hl_lines="4 12 20"
|
||||
{!./src/dependencies/tutorial008.py!}
|
||||
```
|
||||
|
||||
And all of them can use `yield`.
|
||||
|
||||
In this case `dependency_c`, to execute its exit code, needs the value from `dependency_b` (here named `dep_b`) to still be available.
|
||||
|
||||
And, in turn, `dependency_b` needs the value from `dependency_a` (here named `dep_a`) to be available for its exit code.
|
||||
|
||||
```Python hl_lines="16 17 24 25"
|
||||
{!./src/dependencies/tutorial008.py!}
|
||||
```
|
||||
|
||||
The same way, you could have dependencies with `yield` and `return` mixed.
|
||||
|
||||
And you could have a single dependency that requires several other dependencies with `yield`, etc.
|
||||
|
||||
You can have any combinations of dependencies that you want.
|
||||
|
||||
**FastAPI** will make sure everything is run in the correct order.
|
||||
|
||||
!!! note "Technical Details"
|
||||
This works thanks to Python's <a href="https://docs.python.org/3/library/contextlib.html" target="_blank">Context Managers</a>.
|
||||
|
||||
**FastAPI** uses them internally to achieve this.
|
||||
|
||||
## Context Managers
|
||||
|
||||
### What are "Context Managers"
|
||||
|
||||
"Context Managers" are any of those Python objects that you can use in a `with` statement.
|
||||
|
||||
For example, <a href="https://docs.python.org/3/tutorial/inputoutput.html#reading-and-writing-files" target="_blank">you can use `with` to read a file</a>:
|
||||
|
||||
```Python
|
||||
with open("./somefile.txt") as f:
|
||||
contents = f.read()
|
||||
print(contents)
|
||||
```
|
||||
|
||||
Underneath, the `open("./somefile.txt")` returns an object that is a called a "Context Manager".
|
||||
|
||||
When the `with` block finishes, it makes sure to close the file, even if there were exceptions.
|
||||
|
||||
When you create a dependency with `yield`, **FastAPI** will internally convert it to a context manager, and combine it with some other related tools.
|
||||
|
||||
### Using context managers in dependencies with `yield`
|
||||
|
||||
!!! warning
|
||||
This is, more or less, an "advanced" idea.
|
||||
|
||||
If you are just starting with **FastAPI** you might want to skip it for now.
|
||||
|
||||
In Python, you can create context managers by <a href="https://docs.python.org/3/reference/datamodel.html#context-managers" target="_blank">creating a class with two methods: `__enter__()` and `__exit__()`</a>.
|
||||
|
||||
You can also use them with **FastAPI** dependencies with `yield` by using
|
||||
`with` or `async with` statements inside of the dependency function:
|
||||
|
||||
```Python hl_lines="1 2 3 4 5 6 7 8 9 13"
|
||||
{!./src/dependencies/tutorial010.py!}
|
||||
```
|
||||
|
||||
!!! tip
|
||||
Another way to create a context manager is with:
|
||||
|
||||
* <a href="https://docs.python.org/3/library/contextlib.html#contextlib.contextmanager" target="_blank">`@contextlib.contextmanager`</a> or
|
||||
* <a href="https://docs.python.org/3/library/contextlib.html#contextlib.asynccontextmanager" target="_blank">`@contextlib.asynccontextmanager`</a>
|
||||
|
||||
using them to decorate a function with a single `yield`.
|
||||
|
||||
That's what **FastAPI** uses internally for dependencies with `yield`.
|
||||
|
||||
But you don't have to use the decorators for FastAPI dependencies (and you shouldn't).
|
||||
|
||||
FastAPI will do it for you internally.
|
||||
@@ -17,14 +17,12 @@ This is very useful when you need to:
|
||||
|
||||
All these, while minimizing code repetition.
|
||||
|
||||
|
||||
## First Steps
|
||||
|
||||
Let's see a very simple example. It will be so simple that it is not very useful, for now.
|
||||
|
||||
But this way we can focus on how the **Dependency Injection** system works.
|
||||
|
||||
|
||||
### Create a dependency, or "dependable"
|
||||
|
||||
Let's first focus on the dependency.
|
||||
@@ -151,7 +149,6 @@ The simplicity of the dependency injection system makes **FastAPI** compatible w
|
||||
* response data injection systems
|
||||
* etc.
|
||||
|
||||
|
||||
## Simple and Powerful
|
||||
|
||||
Although the hierarchical dependency injection system is very simple to define and use, it's still very powerful.
|
||||
|
||||
@@ -11,6 +11,7 @@ You could create a first dependency ("dependable") like:
|
||||
```Python hl_lines="6 7"
|
||||
{!./src/dependencies/tutorial005.py!}
|
||||
```
|
||||
|
||||
It declares an optional query parameter `q` as a `str`, and then it just returns it.
|
||||
|
||||
This is quite simple (not very useful), but will help us focus on how the sub-dependencies work.
|
||||
@@ -43,6 +44,18 @@ Then we can use the dependency with:
|
||||
|
||||
But **FastAPI** will know that it has to solve `query_extractor` first, to pass the results of that to `query_or_cookie_extractor` while calling it.
|
||||
|
||||
## Using the same dependency multiple times
|
||||
|
||||
If one of your dependencies is declared multiple times for the same *path operation*, for example, multiple dependencies have a common sub-dependency, **FastAPI** will know to call that sub-dependency only once per request.
|
||||
|
||||
And it will save the returned value in a <abbr title="A utility/system to store computed/generated values, to re-use them instead of computing them again.">"cache"</abbr> and pass it to all the "dependants" that need it in that specific request, instead of calling the dependency multiple times for the same request.
|
||||
|
||||
In an advanced scenario where you know you need the dependency to be called at every step (possibly multiple times) in the same request instead of using the "cached" value, you can set the parameter `use_cache=False` when using `Depends`:
|
||||
|
||||
```Python hl_lines="1"
|
||||
async def needy_dependency(fresh_value: str = Depends(get_value, use_cache=False)):
|
||||
return {"fresh_value": fresh_value}
|
||||
```
|
||||
|
||||
## Recap
|
||||
|
||||
@@ -54,7 +67,7 @@ But still, it is very powerful, and allows you to declare arbitrarily deeply nes
|
||||
|
||||
!!! tip
|
||||
All this might not seem as useful with these simple examples.
|
||||
|
||||
|
||||
But you will see how useful it is in the chapters about **security**.
|
||||
|
||||
And you will also see the amounts of code it will save you.
|
||||
|
||||
@@ -88,3 +88,156 @@ Now you can replace the `.openapi()` method with your new function.
|
||||
Once you go to <a href="http://127.0.0.1:8000/redoc" target="_blank">http://127.0.0.1:8000/redoc</a> you will see that you are using your custom logo (in this example, **FastAPI**'s logo):
|
||||
|
||||
<img src="/img/tutorial/extending-openapi/image01.png">
|
||||
|
||||
## Self-hosting JavaScript and CSS for docs
|
||||
|
||||
The API docs use **Swagger UI** and **ReDoc**, and each of those need some JavaScript and CSS files.
|
||||
|
||||
By default, those files are served from a <abbr title="Content Delivery Network: A service, normally composed of several servers, that provides static files, like JavaScript and CSS. It's commonly used to serve those files from the server closer to the client, improving performance.">CDN</abbr>.
|
||||
|
||||
But it's possible to customize it, you can set a specific CDN, or serve the files yourself.
|
||||
|
||||
That's useful, for example, if you need your app to keep working even while offline, without open Internet access, or in a local network.
|
||||
|
||||
Here you'll see how to serve those files yourself, in the same FastAPI app, and configure the docs to use them.
|
||||
|
||||
### Project file structure
|
||||
|
||||
Let's say your project file structure looks like this:
|
||||
|
||||
```
|
||||
.
|
||||
├── app
|
||||
│ ├── __init__.py
|
||||
│ ├── main.py
|
||||
```
|
||||
|
||||
Now create a directory to store those static files.
|
||||
|
||||
Your new file structure could look like this:
|
||||
|
||||
```
|
||||
.
|
||||
├── app
|
||||
│ ├── __init__.py
|
||||
│ ├── main.py
|
||||
└── static/
|
||||
```
|
||||
|
||||
### Download the files
|
||||
|
||||
Download the static files needed for the docs and put them on that `static/` directory.
|
||||
|
||||
You can probably right-click each link and select an option similar to `Save link as...`.
|
||||
|
||||
**Swagger UI** uses the files:
|
||||
|
||||
* <a href="https://cdn.jsdelivr.net/npm/swagger-ui-dist@3/swagger-ui-bundle.js">`swagger-ui-bundle.js`</a>
|
||||
* <a href="https://cdn.jsdelivr.net/npm/swagger-ui-dist@3/swagger-ui.css" target="_blank">`swagger-ui.css`</a>
|
||||
|
||||
And **ReDoc** uses the file:
|
||||
|
||||
* <a href="https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js" target="_blank">`redoc.standalone.js`</a>
|
||||
|
||||
After that, your file structure could look like:
|
||||
|
||||
```
|
||||
.
|
||||
├── app
|
||||
│ ├── __init__.py
|
||||
│ ├── main.py
|
||||
└── static
|
||||
├── redoc.standalone.js
|
||||
├── swagger-ui-bundle.js
|
||||
└── swagger-ui.css
|
||||
```
|
||||
|
||||
### Install `aiofiles`
|
||||
|
||||
Now you need to install `aiofiles`:
|
||||
|
||||
```bash
|
||||
pip install aiofiles
|
||||
```
|
||||
|
||||
### Serve the static files
|
||||
|
||||
* Import `StaticFiles` from Starlette.
|
||||
* "Mount" it the same way you would <a href="https://fastapi.tiangolo.com/tutorial/sub-applications-proxy/" target="_blank">mount a Sub-Application</a>.
|
||||
|
||||
```Python hl_lines="7 11"
|
||||
{!./src/extending_openapi/tutorial002.py!}
|
||||
```
|
||||
|
||||
### Test the static files
|
||||
|
||||
Start your application and go to <a href="http://127.0.0.1:8000/static/redoc.standalone.js" target="_blank">http://127.0.0.1:8000/static/redoc.standalone.js</a>.
|
||||
|
||||
You should see a very long JavaScript file for **ReDoc**.
|
||||
|
||||
It could start with something like:
|
||||
|
||||
```JavaScript
|
||||
/*!
|
||||
* ReDoc - OpenAPI/Swagger-generated API Reference Documentation
|
||||
* -------------------------------------------------------------
|
||||
* Version: "2.0.0-rc.18"
|
||||
* Repo: https://github.com/Redocly/redoc
|
||||
*/
|
||||
!function(e,t){"object"==typeof exports&&"object"==typeof m
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
That confirms that you are being able to serve static files from your app, and that you placed the static files for the docs in the correct place.
|
||||
|
||||
Now we can configure the app to use those static files for the docs.
|
||||
|
||||
### Disable the automatic docs
|
||||
|
||||
The first step is to disable the automatic docs, as those use the CDN by default.
|
||||
|
||||
To disable them, set their URLs to `None` when creating your `FastAPI` app:
|
||||
|
||||
```Python hl_lines="9"
|
||||
{!./src/extending_openapi/tutorial002.py!}
|
||||
```
|
||||
|
||||
### Include the custom docs
|
||||
|
||||
Now you can create the *path operations* for the custom docs.
|
||||
|
||||
You can re-use FastAPI's internal functions to create the HTML pages for the docs, and pass them the needed arguments:
|
||||
|
||||
* `openapi_url`: the URL where the HTML page for the docs can get the OpenAPI schema for your API. You can use here the attribute `app.openapi_url`.
|
||||
* `title`: the title of your API.
|
||||
* `oauth2_redirect_url`: you can use `app.swagger_ui_oauth2_redirect_url` here to use the default.
|
||||
* `swagger_js_url`: the URL where the HTML for your Swagger UI docs can get the **JavaScript** file. This is the one that your own app is now serving.
|
||||
* `swagger_css_url`: the URL where the HTML for your Swagger UI docs can get the **CSS** file. This is the one that your own app is now serving.
|
||||
|
||||
And similarly for ReDoc...
|
||||
|
||||
```Python hl_lines="2 3 4 5 6 14 15 16 17 18 19 20 21 22 25 26 27 30 31 32 33 34 35 36"
|
||||
{!./src/extending_openapi/tutorial002.py!}
|
||||
```
|
||||
|
||||
!!! tip
|
||||
The *path operation* for `swagger_ui_redirect` is a helper for when you use OAuth2.
|
||||
|
||||
If you integrate your API with an OAuth2 provider, you will be able to authenticate and come back to the API docs with the acquired credentials. And interact with it using the real OAuth2 authentication.
|
||||
|
||||
Swagger UI will handle it behind the scenes for you, but it needs this "redirect" helper.
|
||||
|
||||
### Create a *path operation* to test it
|
||||
|
||||
Now, to be able to test that everything works, create a path operation:
|
||||
|
||||
```Python hl_lines="39 40 41"
|
||||
{!./src/extending_openapi/tutorial002.py!}
|
||||
```
|
||||
|
||||
### Test it
|
||||
|
||||
Now, you should be able to disconnect your WiFi, go to your docs at <a href="http://127.0.0.1:8000/docs" target="_blank">http://127.0.0.1:8000/docs</a>, and reload the page.
|
||||
|
||||
And even without Internet, you would be able to see the docs for your API and interact with it.
|
||||
|
||||
@@ -15,7 +15,7 @@ This is especially the case for user models, because:
|
||||
|
||||
Here's a general idea of how the models could look like with their password fields and the places where they are used:
|
||||
|
||||
```Python hl_lines="8 10 15 21 23 32 34 39 40"
|
||||
```Python hl_lines="7 9 14 20 22 27 28 31 32 33 38 39"
|
||||
{!./src/extra_models/tutorial001.py!}
|
||||
```
|
||||
|
||||
@@ -148,7 +148,7 @@ All the data conversion, validation, documentation, etc. will still work as norm
|
||||
|
||||
That way, we can declare just the differences between the models (with plaintext `password`, with `hashed_password` and without password):
|
||||
|
||||
```Python hl_lines="8 14 15 18 19 22 23"
|
||||
```Python hl_lines="7 13 14 17 18 21 22"
|
||||
{!./src/extra_models/tutorial002.py!}
|
||||
```
|
||||
|
||||
@@ -174,6 +174,18 @@ For that, use the standard Python `typing.List`:
|
||||
{!./src/extra_models/tutorial004.py!}
|
||||
```
|
||||
|
||||
## Response with arbitrary `dict`
|
||||
|
||||
You can also declare a response using a plain arbitrary `dict`, declaring just the type of the keys and values, without using a Pydantic model.
|
||||
|
||||
This is useful if you don't know the valid field/attribute names (that would be needed for a Pydantic model) beforehand.
|
||||
|
||||
In this case, you can use `typing.Dict`:
|
||||
|
||||
```Python hl_lines="1 8"
|
||||
{!./src/extra_models/tutorial005.py!}
|
||||
```
|
||||
|
||||
## Recap
|
||||
|
||||
Use multiple Pydantic models and inherit freely for each case.
|
||||
|
||||
@@ -37,7 +37,7 @@ Open your browser at <a href="http://127.0.0.1:8000" target="_blank">http://127.
|
||||
You will see the JSON response as:
|
||||
|
||||
```JSON
|
||||
{"hello": "world"}
|
||||
{"message": "Hello World"}
|
||||
```
|
||||
|
||||
### Interactive API docs
|
||||
|
||||
@@ -4,9 +4,9 @@ This client could be a browser with a frontend, the code from someone else, an I
|
||||
|
||||
You could need to tell that client that:
|
||||
|
||||
* He doesn't have enough privileges for that operation.
|
||||
* He doesn't have access to that resource.
|
||||
* The item he was trying to access doesn't exist.
|
||||
* The client doesn't have enough privileges for that operation.
|
||||
* The client doesn't have access to that resource.
|
||||
* The item the client was trying to access doesn't exist.
|
||||
* etc.
|
||||
|
||||
In these cases, you would normally return an **HTTP status code** in the range of **400** (from 400 to 499).
|
||||
@@ -68,7 +68,7 @@ But if the client requests `http://example.com/items/bar` (a non-existent `item_
|
||||
|
||||
They are handled automatically by **FastAPI** and converted to JSON.
|
||||
|
||||
### Adding custom headers
|
||||
## Add custom headers
|
||||
|
||||
There are some situations in where it's useful to be able to add custom headers to the HTTP error. For example, for some types of security.
|
||||
|
||||
@@ -76,24 +76,138 @@ You probably won't need to use it directly in your code.
|
||||
|
||||
But in case you needed it for an advanced scenario, you can add custom headers:
|
||||
|
||||
|
||||
```Python hl_lines="14"
|
||||
{!./src/handling_errors/tutorial002.py!}
|
||||
```
|
||||
|
||||
### Installing custom handlers
|
||||
## Install custom exception handlers
|
||||
|
||||
If you need to add other custom exception handlers, or override the default one (that sends the errors as JSON), you can use <a href="https://www.starlette.io/exceptions/" target="_blank">the same exception utilities from Starlette</a>.
|
||||
You can add custom exception handlers with <a href="https://www.starlette.io/exceptions/" target="_blank">the same exception utilities from Starlette</a>.
|
||||
|
||||
For example, you could override the default exception handler with:
|
||||
Let's say you have a custom exception `UnicornException` that you (or a library you use) might `raise`.
|
||||
|
||||
```Python hl_lines="2 3 8 9 10"
|
||||
And you want to handle this exception globally with FastAPI.
|
||||
|
||||
You could add a custom exception handler with `@app.exception_handler()`:
|
||||
|
||||
```Python hl_lines="6 7 8 14 15 16 17 18 24"
|
||||
{!./src/handling_errors/tutorial003.py!}
|
||||
```
|
||||
|
||||
...this would make it return "plain text" responses with the errors, instead of JSON responses.
|
||||
Here, if you request `/unicorns/yolo`, the *path operation* will `raise` a `UnicornException`.
|
||||
|
||||
!!! info
|
||||
Note that in this example we set the exception handler with Starlette's `HTTPException` instead of FastAPI's `HTTPException`.
|
||||
But it will be handled by the `unicorn_exception_handler`.
|
||||
|
||||
This would ensure that if you use a plug-in or any other third-party tool that raises Starlette's `HTTPException` directly, it will be caught by your exception handler.
|
||||
So, you will receive a clean error, with an HTTP status code of `418` and a JSON content of:
|
||||
|
||||
```JSON
|
||||
{"message": "Oops! yolo did something. There goes a rainbow..."}
|
||||
```
|
||||
|
||||
## Override the default exception handlers
|
||||
|
||||
**FastAPI** has some default exception handlers.
|
||||
|
||||
These handlers are in charge or returning the default JSON responses when you `raise` an `HTTPException` and when the request has invalid data.
|
||||
|
||||
You can override these exception handlers with your own.
|
||||
|
||||
### Override request validation exceptions
|
||||
|
||||
When a request contains invalid data, **FastAPI** internally raises a `RequestValidationError`.
|
||||
|
||||
And it also includes a default exception handler for it.
|
||||
|
||||
To override it, import the `RequestValidationError` and use it with `@app.exception_handler(RequestValidationError)` to decorate the exception handler.
|
||||
|
||||
The exception handler will receive a `Request` and the exception.
|
||||
|
||||
```Python hl_lines="2 14 15 16"
|
||||
{!./src/handling_errors/tutorial004.py!}
|
||||
```
|
||||
|
||||
Now, if you go to `/items/foo`, instead of getting the default JSON error with:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"loc": [
|
||||
"path",
|
||||
"item_id"
|
||||
],
|
||||
"msg": "value is not a valid integer",
|
||||
"type": "type_error.integer"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
you will get a text version, with:
|
||||
|
||||
```
|
||||
1 validation error
|
||||
path -> item_id
|
||||
value is not a valid integer (type=type_error.integer)
|
||||
```
|
||||
|
||||
#### `RequestValidationError` vs `ValidationError`
|
||||
|
||||
!!! warning
|
||||
These are technical details that you might skip if it's not important for you now.
|
||||
|
||||
`RequestValidationError` is a sub-class of Pydantic's <a href="https://pydantic-docs.helpmanual.io/#error-handling" target="_blank">`ValidationError`</a>.
|
||||
|
||||
**FastAPI** uses it so that, if you use a Pydantic model in `response_model`, and your data has an error, you will see the error in your log.
|
||||
|
||||
But the client/user will not see it. Instead, the client will receive an "Internal Server Error" with a HTTP status code `500`.
|
||||
|
||||
It should be this way because if you have a Pydantic `ValidationError` in your *response* or anywhere in your code (not in the client's *request*), it's actually a bug in your code.
|
||||
|
||||
And while you fix it, your clients/users shouldn't have access to internal information about the error, as that could expose a security vulnerability.
|
||||
|
||||
### Override the `HTTPException` error handler
|
||||
|
||||
The same way, you can override the `HTTPException` handler.
|
||||
|
||||
For example, you could want to return a plain text response instead of JSON for these errors:
|
||||
|
||||
```Python hl_lines="1 3 9 10 11 22"
|
||||
{!./src/handling_errors/tutorial004.py!}
|
||||
```
|
||||
|
||||
#### FastAPI's `HTTPException` vs Starlette's `HTTPException`
|
||||
|
||||
**FastAPI** has its own `HTTPException`.
|
||||
|
||||
And **FastAPI**'s `HTTPException` error class inherits from Starlette's `HTTPException` error class.
|
||||
|
||||
The only difference, is that **FastAPI**'s `HTTPException` allows you to add headers to be included in the response.
|
||||
|
||||
This is needed/used internally for OAuth 2.0 and some security utilities.
|
||||
|
||||
So, you can keep raising **FastAPI**'s `HTTPException` as normally in your code.
|
||||
|
||||
But when you register an exception handler, you should register it for Starlette's `HTTPException`.
|
||||
|
||||
This way, if any part of Starlette's internal code, or a Starlette extension or plug-in, raises an `HTTPException`, your handler will be able to catch handle it.
|
||||
|
||||
In this example, to be able to have both `HTTPException`s in the same code, Starlette's exceptions is renamed to `StarletteHTTPException`:
|
||||
|
||||
```Python
|
||||
from starlette.exceptions import HTTPException as StarletteHTTPException
|
||||
```
|
||||
|
||||
### Re-use **FastAPI**'s exception handlers
|
||||
|
||||
You could also just want to use the exception somehow, but then use the same default exception handlers from **FastAPI**.
|
||||
|
||||
You can import and re-use the default exception handlers from `fastapi.exception_handlers`:
|
||||
|
||||
```Python hl_lines="2 3 4 5 15 21"
|
||||
{!./src/handling_errors/tutorial005.py!}
|
||||
```
|
||||
|
||||
In this example, you are just `print`ing the error with a very expressive notification.
|
||||
|
||||
But you get the idea, you can use the exception and then just re-use the default exception handlers.
|
||||
|
||||
186
docs/tutorial/openapi-callbacks.md
Normal file
186
docs/tutorial/openapi-callbacks.md
Normal file
@@ -0,0 +1,186 @@
|
||||
You could create an API with a *path operation* that could trigger a request to an *external API* created by someone else (probably the same developer that would be *using* your API).
|
||||
|
||||
The process that happens when your API app calls the *external API* is named a "callback". Because the software that the external developer wrote sends a request to your API and then your API *calls back*, sending a request to an *external API* (that was probably created by the same developer).
|
||||
|
||||
In this case, you could want to document how that external API *should* look like. What *path operation* it should have, what body it should expect, what response it should return, etc.
|
||||
|
||||
## An app with callbacks
|
||||
|
||||
Let's see all this with an example.
|
||||
|
||||
Imagine you develop an app that allows creating invoices.
|
||||
|
||||
These invoices will have an `id`, `title` (optional), `customer`, and `total`.
|
||||
|
||||
The user of your API (an external developer) will create an invoice in your API with a POST request.
|
||||
|
||||
Then your API will (let's imagine):
|
||||
|
||||
* Send the invoice to some customer of the external developer.
|
||||
* Collect the money.
|
||||
* Send a notification back to the API user (the external developer).
|
||||
* This will be done by sending a POST request (from *your API*) to some *external API* provided by that external developer (this is the "callback").
|
||||
|
||||
## The normal **FastAPI** app
|
||||
|
||||
Let's first see how the normal API app would look like before adding the callback.
|
||||
|
||||
It will have a *path operation* that will receive an `Invoice` body, and a query parameter `callback_url` that will contain the URL for the callback.
|
||||
|
||||
This part is pretty normal, most of the code is probably already familiar to you:
|
||||
|
||||
```Python hl_lines="8 9 10 11 12 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54"
|
||||
{!./src/openapi_callbacks/tutorial001.py!}
|
||||
```
|
||||
|
||||
!!! tip
|
||||
The `callback_url` query parameter uses a Pydantic <a href="https://pydantic-docs.helpmanual.io/usage/types/#urls" target="_blank">URL</a> type.
|
||||
|
||||
The only new thing is the `callbacks=messages_callback_router.routes` as an argument to the *path operation decorator*. We'll see what that is next.
|
||||
|
||||
## Documenting the callback
|
||||
|
||||
The actual callback code will depend heavily on your own API app.
|
||||
|
||||
And it will probably vary a lot from one app to the next.
|
||||
|
||||
It could be just one or two lines of code, like:
|
||||
|
||||
```Python
|
||||
callback_url = "https://example.com/api/v1/invoices/events/"
|
||||
requests.post(callback_url, json={"description": "Invoice paid", "paid": True})
|
||||
```
|
||||
|
||||
But possibly the most important part of the callback is making sure that your API user (the external developer) implements the *external API* correctly, according to the data that *your API* is going to send in the request body of the callback, etc.
|
||||
|
||||
So, what we will do next is add the code to document how that *external API* should look like to receive the callback from *your API*.
|
||||
|
||||
That documentation will show up in the Swagger UI at `/docs` in your API, and it will let external developers know how to build the *external API*.
|
||||
|
||||
This example doesn't implement the callback itself (that could be just a line of code), only the documentation part.
|
||||
|
||||
!!! tip
|
||||
The actual callback is just an HTTP request.
|
||||
|
||||
When implementing the callback yourself, you could use something like <a href="https://www.encode.io/httpx/" target="_blank">HTTPX</a> or <a href="https://requests.readthedocs.io/" target="_blank">Requests</a>.
|
||||
|
||||
## Write the callback documentation code
|
||||
|
||||
This code won't be executed in your app, we only need it to *document* how that *external API* should look like.
|
||||
|
||||
But, you already know how to easily create automatic documentation for an API with **FastAPI**.
|
||||
|
||||
So we are going to use that same knowledge to document how the *external API* should look like... by creating the *path operation(s)* that the external API should implement (the ones your API will call).
|
||||
|
||||
!!! tip
|
||||
When writing the code to document a callback, it might be useful to imagine that you are that *external developer*. And that you are currently implementing the *external API*, not *your API*.
|
||||
|
||||
Temporarily adopting this point of view (of the *external developer*) can help you feel like it's more obvious where to put the parameters, the Pydantic model for the body, for the response, etc. for that *external API*.
|
||||
|
||||
### Create a callback `APIRouter`
|
||||
|
||||
First create a new `APIRouter` that will contain one or more callbacks.
|
||||
|
||||
This router will never be added to an actual `FastAPI` app (i.e. it will never be passed to `app.include_router(...)`).
|
||||
|
||||
Because of that, you need to declare what will be the `default_response_class`, and set it to `JSONResponse`.
|
||||
|
||||
!!! Note "Technical Details"
|
||||
The `response_class` is normally set by the `FastAPI` app during the call to `app.include_router(some_router)`.
|
||||
|
||||
But as we are never calling `app.include_router(some_router)`, we need to set the `default_response_class` during creation of the `APIRouter`.
|
||||
|
||||
```Python hl_lines="3 24"
|
||||
{!./src/openapi_callbacks/tutorial001.py!}
|
||||
```
|
||||
|
||||
### Create the callback *path operation*
|
||||
|
||||
To create the callback *path operation* use the same `APIRouter` you created above.
|
||||
|
||||
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="15 16 17 20 21 27 28 29 30 31 32"
|
||||
{!./src/openapi_callbacks/tutorial001.py!}
|
||||
```
|
||||
|
||||
There are 2 main differences from a normal *path operation*:
|
||||
|
||||
* It doesn't need to have any actual code, because your app will never call this code. It's only used to document the *external API*. So, the function could just have `pass`.
|
||||
* The *path* can contain an <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#key-expression" target="_blank">OpenAPI 3 expression</a> (see more below) where it can use variables with parameters and parts of the original request sent to *your API*.
|
||||
|
||||
### The callback path expression
|
||||
|
||||
The callback *path* can have an <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#key-expression" target="_blank">OpenAPI 3 expression</a> that can contain parts of the original request sent to *your API*.
|
||||
|
||||
In this case, it's the `str`:
|
||||
|
||||
```Python
|
||||
"{$callback_url}/invoices/{$request.body.id}"
|
||||
```
|
||||
|
||||
So, if your API user (the external developer) sends a request to *your API* to:
|
||||
|
||||
```
|
||||
https://yourapi.com/invoices/?callback_url=https://www.external.org/events
|
||||
```
|
||||
|
||||
with a JSON body of:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"id": "2expen51ve",
|
||||
"customer": "Mr. Richie Rich",
|
||||
"total": "9999"
|
||||
}
|
||||
```
|
||||
|
||||
Then *your API* will process the invoice, and at some point later, send a callback request to the `callback_url` (the *external API*):
|
||||
|
||||
```
|
||||
https://www.external.org/events/invoices/2expen51ve
|
||||
```
|
||||
|
||||
with a JSON body containing something like:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"description": "Payment celebration",
|
||||
"paid": true
|
||||
}
|
||||
```
|
||||
|
||||
and it would expect a response from that *external API* with a JSON body like:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"ok": true
|
||||
}
|
||||
```
|
||||
|
||||
!!! tip
|
||||
Notice how the callback URL used contains the URL received as a query parameter in `callback_url` (`https://www.external.org/events`) and also the invoice `id` from inside of the JSON body (`2expen51ve`).
|
||||
|
||||
### Add the callback router
|
||||
|
||||
At this point you have the *callback path operation(s)* needed (the one(s) that the *external developer* should implement in the *external API*) in the callback router you created above.
|
||||
|
||||
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="35"
|
||||
{!./src/openapi_callbacks/tutorial001.py!}
|
||||
```
|
||||
|
||||
!!! tip
|
||||
Notice that you are not passing the router itself (`invoices_callback_router`) to `callback=`, but the attribute `.routes`, as in `invoices_callback_router.routes`.
|
||||
|
||||
### Check the docs
|
||||
|
||||
Now you can start your app with Uvicorn and go to <a href="http://127.0.0.1:8000/docs" target="_blank">http://127.0.0.1:8000/docs</a>.
|
||||
|
||||
You will see your docs including a "Callback" section for your *path operation* that shows how the *external API* should look like:
|
||||
|
||||
<img src="/img/tutorial/openapi-callbacks/image01.png">
|
||||
@@ -11,10 +11,40 @@ You would have to make sure that it is unique for each operation.
|
||||
{!./src/path_operation_advanced_configuration/tutorial001.py!}
|
||||
```
|
||||
|
||||
### Using the *path operation function* name as the operationId
|
||||
|
||||
If you want to use your APIs' function names as `operationId`s, you can iterate over all of them and override each *path operation's* `operation_id` using their `APIRoute.name`.
|
||||
|
||||
You should do it after adding all your *path operations*.
|
||||
|
||||
```Python hl_lines="2 12 13 14 15 16 17 18 19 20 21 24"
|
||||
{!./src/path_operation_advanced_configuration/tutorial002.py!}
|
||||
```
|
||||
|
||||
!!! tip
|
||||
If you manually call `app.openapi()`, you should update the `operationId`s before that.
|
||||
|
||||
!!! warning
|
||||
If you do this, you have to make sure each one of your *path operation functions* has a unique name.
|
||||
|
||||
Even if they are in different modules (Python files).
|
||||
|
||||
## Exclude from OpenAPI
|
||||
|
||||
To exclude a path operation from the generated OpenAPI schema (and thus, from the automatic documentation systems), use the parameter `include_in_schema` and set it to `False`;
|
||||
|
||||
```Python hl_lines="6"
|
||||
{!./src/path_operation_advanced_configuration/tutorial002.py!}
|
||||
{!./src/path_operation_advanced_configuration/tutorial003.py!}
|
||||
```
|
||||
|
||||
## Advanced description from docstring
|
||||
|
||||
You can limit the lines used from the docstring of a *path operation function* for OpenAPI.
|
||||
|
||||
Adding an `\f` (an escaped "form feed" character) causes **FastAPI** to truncate the output used for OpenAPI at this point.
|
||||
|
||||
It won't show up in the documentation, but other tools (such as Sphinx) will be able to use the rest.
|
||||
|
||||
```Python hl_lines="19 20 21 22 23 24 25 26 27 28 29"
|
||||
{!./src/path_operation_advanced_configuration/tutorial004.py!}
|
||||
```
|
||||
|
||||
@@ -35,7 +35,7 @@ If you run this example and open your browser at <a href="http://127.0.0.1:8000/
|
||||
|
||||
!!! check
|
||||
Notice that the value your function received (and returned) is `3`, as a Python `int`, not a string `"3"`.
|
||||
|
||||
|
||||
So, with that type declaration, **FastAPI** gives you automatic request <abbr title="converting the string that comes from an HTTP request into Python data">"parsing"</abbr>.
|
||||
|
||||
## Data validation
|
||||
@@ -61,12 +61,11 @@ because the path parameter `item_id` had a value of `"foo"`, which is not an `in
|
||||
|
||||
The same error would appear if you provided a `float` instead of an int, as in: <a href="http://127.0.0.1:8000/items/4.2" target="_blank">http://127.0.0.1:8000/items/4.2</a>
|
||||
|
||||
|
||||
!!! check
|
||||
So, with the same Python type declaration, **FastAPI** gives you data validation.
|
||||
|
||||
Notice that the error also clearly states exactly the point where the validation didn't pass.
|
||||
|
||||
Notice that the error also clearly states exactly the point where the validation didn't pass.
|
||||
|
||||
This is incredibly helpful while developing and debugging code that interacts with your API.
|
||||
|
||||
## Documentation
|
||||
@@ -96,8 +95,7 @@ All the data validation is performed under the hood by <a href="https://pydantic
|
||||
|
||||
You can use the same type declarations with `str`, `float`, `bool` and many other complex data types.
|
||||
|
||||
These are explored in the next chapters of the tutorial.
|
||||
|
||||
Several of these are explored in the next chapters of the tutorial.
|
||||
|
||||
## Order matters
|
||||
|
||||
@@ -115,6 +113,111 @@ Because path operations are evaluated in order, you need to make sure that the p
|
||||
|
||||
Otherwise, the path for `/users/{user_id}` would match also for `/users/me`, "thinking" that it's receiving a parameter `user_id` with a value of `"me"`.
|
||||
|
||||
## Predefined values
|
||||
|
||||
If you have a *path operation* that receives a *path parameter*, but you want the possible valid *path parameter* values to be predefined, you can use a standard Python <abbr title="Enumeration">`Enum`</abbr>.
|
||||
|
||||
### Create an `Enum` class
|
||||
|
||||
Import `Enum` and create a sub-class that inherits from `str` and from `Enum`.
|
||||
|
||||
By inheriting from `str` the API docs will be able to know that the values must be of type `string` and will be able to render correctly.
|
||||
|
||||
And create class attributes with fixed values, those fixed values will be the available valid values:
|
||||
|
||||
```Python hl_lines="1 6 7 8 9"
|
||||
{!./src/path_params/tutorial005.py!}
|
||||
```
|
||||
|
||||
!!! info
|
||||
<a href="https://docs.python.org/3/library/enum.html" target="_blank">Enumerations (or enums) are available in Python</a> since version 3.4.
|
||||
|
||||
!!! tip
|
||||
If you are wondering, "AlexNet", "ResNet", and "LeNet" are just names of Machine Learning <abbr title="Technically, Deep Learning model architectures">models</abbr>.
|
||||
|
||||
### Declare a *path parameter*
|
||||
|
||||
Then create a *path parameter* with a type annotation using the enum class you created (`ModelName`):
|
||||
|
||||
```Python hl_lines="16"
|
||||
{!./src/path_params/tutorial005.py!}
|
||||
```
|
||||
|
||||
### Check the docs
|
||||
|
||||
Because the available values for the *path parameter* are specified, the interactive docs can show them nicely:
|
||||
|
||||
<img src="/img/tutorial/path-params/image03.png">
|
||||
|
||||
### Working with Python *enumerations*
|
||||
|
||||
The value of the *path parameter* will be an *enumeration member*.
|
||||
|
||||
#### Compare *enumeration members*
|
||||
|
||||
You can compare it with the *enumeration member* in your created enum `ModelName`:
|
||||
|
||||
```Python hl_lines="17"
|
||||
{!./src/path_params/tutorial005.py!}
|
||||
```
|
||||
|
||||
#### Get the *enumeration value*
|
||||
|
||||
You can get the actual value (a `str` in this case) using `model_name.value`, or in general, `your_enum_member.value`:
|
||||
|
||||
```Python hl_lines="19"
|
||||
{!./src/path_params/tutorial005.py!}
|
||||
```
|
||||
|
||||
!!! tip
|
||||
You could also access the value `"lenet"` with `ModelName.lenet.value`.
|
||||
|
||||
#### Return *enumeration members*
|
||||
|
||||
You can return *enum members* from your *path operation*, even nested in a JSON body (e.g. a `dict`).
|
||||
|
||||
They will be converted to their corresponding values before returning them to the client:
|
||||
|
||||
```Python hl_lines="18 20 21"
|
||||
{!./src/path_params/tutorial005.py!}
|
||||
```
|
||||
|
||||
## Path parameters containing paths
|
||||
|
||||
Let's say you have a *path operation* with a path `/files/{file_path}`.
|
||||
|
||||
But you need `file_path` itself to contain a *path*, like `home/johndoe/myfile.txt`.
|
||||
|
||||
So, the URL for that file would be something like: `/files/home/johndoe/myfile.txt`.
|
||||
|
||||
### OpenAPI support
|
||||
|
||||
OpenAPI doesn't support a way to declare a *path parameter* to contain a *path* inside, as that could lead to scenarios that are difficult to test and define.
|
||||
|
||||
Nevertheless, you can still do it in **FastAPI**, using one of the internal tools from Starlette.
|
||||
|
||||
And the docs would still work, although not adding any documentation telling that the parameter should contain a path.
|
||||
|
||||
### Path convertor
|
||||
|
||||
Using an option directly from Starlette you can declare a *path parameter* containing a *path* using a URL like:
|
||||
|
||||
```
|
||||
/files/{file_path:path}
|
||||
```
|
||||
|
||||
In this case, the name of the parameter is `file_path`, and the last part, `:path`, tells it that the parameter should match any *path*.
|
||||
|
||||
So, you can use it with:
|
||||
|
||||
```Python hl_lines="6"
|
||||
{!./src/path_params/tutorial004.py!}
|
||||
```
|
||||
|
||||
!!! tip
|
||||
You could need the parameter to contain `/home/johndoe/myfile.txt`, with a leading slash (`/`).
|
||||
|
||||
In that case, the URL would be: `/files//home/johndoe/myfile.txt`, with a double slash (`//`) between `files` and `home`.
|
||||
|
||||
## Recap
|
||||
|
||||
@@ -127,4 +230,4 @@ With **FastAPI**, by using short, intuitive and standard Python type declaration
|
||||
|
||||
And you only have to declare them once.
|
||||
|
||||
That's probably the main visible advantage of **FastAPI** compared to alternative frameworks (apart from the raw performance).
|
||||
That's probably the main visible advantage of **FastAPI** compared to alternative frameworks (apart from the raw performance).
|
||||
|
||||
@@ -12,7 +12,6 @@ The query parameter `q` is of type `str`, and by default is `None`, so it is opt
|
||||
|
||||
We are going to enforce that even though `q` is optional, whenever it is provided, it **doesn't exceed a length of 50 characters**.
|
||||
|
||||
|
||||
### Import `Query`
|
||||
|
||||
To achieve that, first import `Query` from `fastapi`:
|
||||
@@ -29,7 +28,7 @@ And now use it as the default value of your parameter, setting the parameter `ma
|
||||
{!./src/query_params_str_validations/tutorial002.py!}
|
||||
```
|
||||
|
||||
As we have to replace the default value `None` with `Query(None)`, the first parameter to `Query` serves the same purpose of defining that default value.
|
||||
As we have to replace the default value `None` with `Query(None)`, the first parameter to `Query` serves the same purpose of defining that default value.
|
||||
|
||||
So:
|
||||
|
||||
@@ -41,7 +40,7 @@ q: str = Query(None)
|
||||
|
||||
```Python
|
||||
q: str = None
|
||||
```
|
||||
```
|
||||
|
||||
But it declares it explicitly as being a query parameter.
|
||||
|
||||
@@ -53,7 +52,6 @@ q: str = Query(None, max_length=50)
|
||||
|
||||
This will validate the data, show a clear error when the data is not valid, and document the parameter in the OpenAPI schema path operation.
|
||||
|
||||
|
||||
## Add more validations
|
||||
|
||||
You can also add a parameter `min_length`:
|
||||
@@ -119,7 +117,7 @@ So, when you need to declare a value as required while using `Query`, you can us
|
||||
{!./src/query_params_str_validations/tutorial006.py!}
|
||||
```
|
||||
|
||||
!!! info
|
||||
!!! info
|
||||
If you hadn't seen that `...` before: it is a a special single value, it is <a href="https://docs.python.org/3/library/constants.html#Ellipsis" target="_blank">part of Python and is called "Ellipsis"</a>.
|
||||
|
||||
This will let **FastAPI** know that this parameter is required.
|
||||
@@ -156,11 +154,48 @@ So, the response to that URL would be:
|
||||
!!! tip
|
||||
To declare a query parameter with a type of `list`, like in the example above, you need to explicitly use `Query`, otherwise it would be interpreted as a request body.
|
||||
|
||||
|
||||
The interactive API docs will update accordingly, to allow multiple values:
|
||||
|
||||
<img src="/img/tutorial/query-params-str-validations/image02.png">
|
||||
|
||||
### Query parameter list / multiple values with defaults
|
||||
|
||||
And you can also define a default `list` of values if none are provided:
|
||||
|
||||
```Python hl_lines="9"
|
||||
{!./src/query_params_str_validations/tutorial012.py!}
|
||||
```
|
||||
|
||||
If you go to:
|
||||
|
||||
```
|
||||
http://localhost:8000/items/
|
||||
```
|
||||
|
||||
the default of `q` will be: `["foo", "bar"]` and your response will be:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"q": [
|
||||
"foo",
|
||||
"bar"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### Using `list`
|
||||
|
||||
You can also use `list` directly instead of `List[str]`:
|
||||
|
||||
```Python hl_lines="7"
|
||||
{!./src/query_params_str_validations/tutorial013.py!}
|
||||
```
|
||||
|
||||
!!! note
|
||||
Have in mind that in this case, FastAPI won't check the contents of the list.
|
||||
|
||||
For example, `List[int]` would check (and document) that the contents of the list are integers. But `list` alone wouldn't.
|
||||
|
||||
## Declare more metadata
|
||||
|
||||
You can add more information about the parameter.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user