Compare commits

...

110 Commits

Author SHA1 Message Date
Sebastián Ramírez
6a274d18b4 🔖 Release 0.12.1, fix responses in include_router 2019-04-05 20:08:36 +04:00
Sebastián Ramírez
62626b0175 📝 Update release notes 2019-04-05 20:08:00 +04:00
Sebastián Ramírez
c8df3ae57c 🐛 Fix handling additional responses in include_router (#140) 2019-04-05 20:06:40 +04:00
Sebastián Ramírez
6f7f9268f6 📝 Update release notes 2019-04-05 16:24:04 +04:00
Matthew McLeod
50653e205f 📝 Fix typo in SQL tutorial (#138) 2019-04-05 16:22:33 +04:00
Sebastián Ramírez
50a280b17b 📝 Update release notes 2019-04-05 16:21:17 +04:00
Mostapha Sadeghipour Roudsari
c1da3b38a3 📝 fix typos in nested models and OAuth2 with JWT (#127) 2019-04-05 16:08:59 +04:00
Sebastián Ramírez
e68a68c97c 🔖 Release 0.12.0, add additional responses 2019-04-05 14:35:01 +04:00
Sebastián Ramírez
907e613ff2 🔧 Update test-conv-html.sh to allow extra params 2019-04-05 14:29:36 +04:00
Sebastián Ramírez
f0fc2fad2c 📝 Update release notes 2019-04-05 14:28:30 +04:00
Sebastián Ramírez
ad471307e2 Additional Responses (#97)
Add additional responses to OpenAPI, including Pydantic models or schemas directly, custom status codes, media types, extending `response_model`, etc.
2019-04-05 14:18:28 +04:00
Sebastián Ramírez
2bd775988f Add/refactor addditional responses, tests, docs 2019-04-05 13:54:00 +04:00
Sebastián Ramírez
c59ddc8a24 🔖 Release 0.11.0 2019-04-03 15:51:44 +04:00
Sebastián Ramírez
378b39bbbc 📝 Update release notes 2019-04-03 15:49:58 +04:00
Sebastián Ramírez
37e0306517 📝 Update default error response in SQL tutorial 2019-04-03 15:49:40 +04:00
Sebastián Ramírez
fad3a9e1dc Add auto_error to security utils (#134)
to allow them to be optional, also allowing the declaration of multiple security schemes
2019-04-03 15:44:52 +04:00
Sebastián Ramírez
b35b0a9a90 📝 Update release notes 2019-03-31 22:05:03 +04:00
Alex Iribarren
1426b6200a 🗃️ Close the DB even if exceptions are raised (#89)
* Close the DB even if exceptions are raised

* 📝 Add note about closing DB in finally
2019-03-31 22:01:32 +04:00
Sebastián Ramírez
40e5f3764e 📝 Update release notes 2019-03-31 20:56:52 +04:00
Alif Jahan
e5c75807ce 🔥 removed duplicate dependency in pyproject.toml (#128) 2019-03-31 20:55:12 +04:00
Sebastián Ramírez
deff2b6678 🔖 Release 0.10.3 2019-03-30 21:54:00 +04:00
Sebastián Ramírez
7c572fdb3a 📝 Update release notes 2019-03-30 21:32:24 +04:00
Daniel Hahler
ae970638cf 👷 Set Travis to use dist=xenial and Python 3.7 instead of 3.7-dev (#92) 2019-03-30 21:30:31 +04:00
Sebastián Ramírez
deae92bba1 📝 Update release notes 2019-03-30 21:09:47 +04:00
yihuang
f806ba642a 🔥 Remove repeated param declaration (#123) 2019-03-30 21:07:41 +04:00
Sebastián Ramírez
5a3cf863da 📝 Update release notes 2019-03-30 19:55:01 +04:00
Sebastián Ramírez
dd6ab23b62 Add docs/tests extending OpenAPI (#126) 2019-03-30 19:53:44 +04:00
Sebastián Ramírez
0449499188 📝 Update release notes 2019-03-30 18:32:23 +04:00
The Gitter Badger
4dc7b32861 📝 Add a Gitter chat badge and links (#117)
* Add Gitter badge

* 📝 Add links and badges to Gitter chat
2019-03-30 18:30:02 +04:00
Sebastián Ramírez
08d849d5c5 📝 Update release notes 2019-03-29 19:06:02 +04:00
James Saunders
714e68b5f0 📝 Add note in response model docs: why not return type annotations (#109)
* Update response model documentation to explain design choice

Closes #101

* 📝 Update note about return function type annotation
2019-03-29 19:02:53 +04:00
Sebastián Ramírez
3d4f59f35a 📝 Udpate release notes 2019-03-29 18:43:40 +04:00
Stratos Gerakakis
3ce2920fef 🐛 fix name of shutdown_event in docs (#105)
Fix name copy/paste name error in docs source for startup/shutdown events.
2019-03-29 18:39:57 +04:00
Sebastián Ramírez
825f397918 🔖 Release 0.10.2 2019-03-29 15:17:34 +04:00
Sebastián Ramírez
b390e32372 📝 Update release notes 2019-03-29 15:16:56 +04:00
Sebastián Ramírez
b7d184363f 🐛 Fix JSON Schema of additional properties (#121)
#87
2019-03-29 15:15:49 +04:00
Sebastián Ramírez
2ddb804940 📝 Update release notes 2019-03-25 23:48:27 +04:00
Sebastián Ramírez
a2c9f666b5 📝 Add note about Celery in background tasks 2019-03-25 23:47:25 +04:00
Sebastián Ramírez
1594222e39 📝 Update Release Notes 2019-03-25 23:28:36 +04:00
Sebastián Ramírez
dc1e94d05f Document and test union and list response models (#108) 2019-03-25 23:28:09 +04:00
Sebastián Ramírez
b0f7961b65 🔖 Release 0.10.1: support for encode/databases 2019-03-25 22:21:59 +04:00
Sebastián Ramírez
1c2ecbb89a Add docs and tests for encode/databases (#107)
*  Add docs and tests for encode/databases

*  Add testing-only dependency, databases
2019-03-25 22:17:31 +04:00
Sebastián Ramírez
5a6e47bd49 🔖 Release 0.10.0: BackgroundTasks and websockets fix 2019-03-24 23:37:37 +04:00
Sebastián Ramírez
58872dca74 📝 Udpate release notes 2019-03-24 23:36:57 +04:00
Sebastián Ramírez
9b04593260 Add support for BackgroundTasks parameters (#103)
*  Add support for BackgroundTasks parameters

* 🐛 Fix type declaration in dependencies

* 🐛 Fix coverage of util in tests
2019-03-24 23:33:35 +04:00
euri10
6d77e2ac5f Add websocket to APIRouter (#100)
* Add websocket to APIRouter

* Restore upstream/master Pipfile.lock (git checkout upstream/master -- Pipfile.lock)

* Added tests for router with a prefix
2019-03-24 23:18:20 +04:00
Sebastián Ramírez
b16ca54c30 📝 Update Release Notes 2019-03-24 12:46:13 +04:00
Sebastián Ramírez
834723cf2c Add events docs and tests (#99) 2019-03-24 12:45:46 +04:00
Mohammed
eda9b28338 files formatting 2019-03-23 13:10:45 +03:00
Mohammed
7514ac6fb0 100% test coverage 2019-03-23 13:01:53 +03:00
Mohammed
25fb4239cc increase test coverage 2019-03-23 01:13:09 +03:00
Mohammed
65568065e0 Remove extra code. 2019-03-23 00:47:32 +03:00
Mohammed
95679ca5e6 Fix: adding additional_responses on .include_router() 2019-03-23 00:37:10 +03:00
Mohammed
84a300ef84 Formatting according to guide 2019-03-22 22:54:48 +03:00
Mohammed
c6d28c8209 Accept Multiple Additional Responses 2019-03-22 22:50:47 +03:00
Mohammed
3984e9b8ac Additional Responses test 2019-03-22 22:40:46 +03:00
Mohammed
aa0bca7bb2 Additional Responses implementation 2019-03-22 22:40:07 +03:00
Sebastián Ramírez
9778542ba6 🔖 Release 0.9.1, multi value/duplicate query/header 2019-03-22 21:52:37 +04:00
Sebastián Ramírez
34c34c68d2 📝 Update release notes 2019-03-22 21:51:36 +04:00
Sebastián Ramírez
c64f8346ae Multi-value query parameters and duplicate headers (#95)
* 📝 Document multi-value query parameters

*  Document and test multiple query values

*  Document receiving duplicate headers
2019-03-22 21:47:54 +04:00
Sebastián Ramírez
4f852878d6 🔖 Release 0.9.0, compatible with latest Pydantic 2019-03-22 16:29:33 +04:00
Sebastián Ramírez
59bc4b7d69 📝 Update links in help section 2019-03-22 16:28:28 +04:00
Sebastián Ramírez
3cae2ccbae 📝 Fix link from deployment to bigger applications 2019-03-21 20:35:49 +04:00
Matt Hegarty
e21ba7646a ✏️ typo: fist_name -> first_name (#72) 2019-03-21 18:50:47 +04:00
Sebastián Ramírez
10498fcfbd 📝 Update release notes 2019-03-21 18:48:17 +04:00
Sebastián Ramírez
3f7b7837fb 🔀 Merge PR #72 2019-03-21 18:46:02 +04:00
Sebastián Ramírez
1c26e77a66 📝 Update release notes 2019-03-21 18:40:59 +04:00
Sebastián Ramírez
108c2f3c0e ⬆️ Update Pydantic to 0.21.0 (#90)
* ⬆️ Upgrade Pydantic and others (isort), update docs after changes by isort

* 🎨 Format with newest isort, update type hints in jsonable_encoder

* 🔧 Update test script, to avoid Pydantic type errors

* ⬆️ Update pyproject.toml with latest Pydantic
2019-03-21 18:40:29 +04:00
Sebastián Ramírez
f2fd948ce3 📝 Update release notes 2019-03-21 18:10:08 +04:00
Sebastián Ramírez
b269655b7f 📝 Add docs for application configuration (OpenAPI) 2019-03-21 18:08:10 +04:00
Sebastián Ramírez
a174f01901 🔖 Release version 0.8.0 2019-03-16 21:24:26 +04:00
Sebastián Ramírez
9b76ad1870 ✏️ Fix typos in README 2019-03-16 21:23:35 +04:00
Sebastián Ramírez
f1c367aead 📝 Update docs - Release Notes 2019-03-16 21:21:56 +04:00
Sebastián Ramírez
8291c664b9 🔀 Merge origin master with release notes 2019-03-16 21:20:54 +04:00
euri10
e8472ebbd1 🔧 Make scripts executable (#76) 2019-03-16 21:19:13 +04:00
Sebastián Ramírez
f4391e2a87 📝 Add tags parameter to Release Notes 2019-03-16 21:17:27 +04:00
euri10
11c755bee3 Add tags parameter to app.include_router (#55) 2019-03-16 21:15:08 +04:00
Sebastián Ramírez
35054a450c 📝 Update release notes 2019-03-09 22:12:00 +04:00
Sebastián Ramírez
da60de33c1 📝 Update Uvicorn docs with new --reload option (#74) 2019-03-09 22:10:25 +04:00
Sebastián Ramírez
c0758dfe71 📝 Update release-notes with isort changes 2019-03-09 15:04:47 +04:00
Sebastián Ramírez
1112ac7538 ⬆️ Update imports and scripts for new isort versions (#75) 2019-03-09 15:04:13 +04:00
Sebastián Ramírez
ac2b18bf40 🔖 Release 0.7.1, Raspberry Pi deployment and docs 2019-03-04 20:07:11 +04:00
Sebastián Ramírez
b89a24448b 📝 Update release notes 2019-03-04 20:06:24 +04:00
Zaar Hai
e76216dd26 Clarification about possible performance hit (#64)
* Furether technical details towards #33.

* 📝 Update note about previous async frameworks
2019-03-04 20:04:16 +04:00
Sebastián Ramírez
123d778a0c 📝 Add instructions for Docker on Raspberry Pi 2019-03-04 19:37:46 +04:00
Sebastián Ramírez
829ad209a6 📝 Update benchmarks link 2019-03-04 17:29:53 +04:00
Sebastián Ramírez
b15a65c37e 📝 Update release notes 2019-03-04 11:18:45 +04:00
Sebastián Ramírez
0eed798aac 👷 Limit Docker trigger to branch master 2019-03-04 11:17:56 +04:00
Sebastián Ramírez
2caca42b9e 👷 Trigger Docker images build on Travis (#65) 2019-03-04 11:12:21 +04:00
Sebastián Ramírez
7658d0af16 📝 Clarify uploadfile async method calls 2019-03-04 11:07:15 +04:00
Sebastián Ramírez
c14ec50f73 🔖 Release 0.7.0, with support for UploadFile 2019-03-03 21:06:42 +04:00
Sebastián Ramírez
6b6ea0da2e 📝 Update release notes with UploadFile 2019-03-03 21:05:58 +04:00
Sebastián Ramírez
0b9fe62a10 Add support for UploadFile class annotations (#63)
*  Add support for UploadFile annotations

* 📝 Update File upload docs with FileUpload class

*  Add tests for UploadFile support

* 📝 Update UploadFile docs
2019-03-03 20:52:37 +04:00
Sebastián Ramírez
1f03e85f06 🔖 Release 0.6.4 2019-03-02 22:33:48 +04:00
Sebastián Ramírez
b98bf178a6 📝 Update release notes with WebSockets 2019-03-02 21:51:01 +04:00
Sebastián Ramírez
bbd2198fa2 Add docs for WebSockets (#62) 2019-03-02 21:45:15 +04:00
Sebastián Ramírez
e2723e8480 📝 Update release notes 2019-03-02 20:00:27 +04:00
Sebastián Ramírez
1896153d58 ✏️ Fix typos 2019-03-02 19:54:52 +04:00
Sebastián Ramírez
770b4421f9 📝 Add History, Design and Future to docs 2019-03-02 19:54:15 +04:00
Sebastián Ramírez
e89aacbdf7 📝 Add link to Python docs in debugging section 2019-03-02 17:56:30 +04:00
Sebastián Ramírez
cf25291650 📝 Update release notes 2019-03-02 17:55:07 +04:00
Sebastián Ramírez
13772fbd11 📝 Add note about bigger applications in Docker 2019-03-02 17:52:24 +04:00
Sebastián Ramírez
1d69b6f480 📝 Update release notes 2019-03-02 17:44:48 +04:00
Sebastián Ramírez
01d6aa3dd1 📝 Add docs for debugging 2019-03-02 17:40:01 +04:00
Sebastián Ramírez
74db8ddf9b 📝 Update release notes 2019-03-02 13:53:16 +04:00
Sebastián Ramírez
819b3b2516 📝 Add technical details about async def handling (#61)
#33
2019-03-02 13:48:06 +04:00
Sebastián Ramírez
76fb2879ed ✏️ Fix typo in release notes 2019-03-02 13:02:06 +04:00
Sebastián Ramírez
daaf654868 🔖 Release 0.6.3: favicons in docs 2019-02-24 01:49:04 +04:00
Sebastián Ramírez
6e0553b4cf 📝 Update release notes, favicons 2019-02-24 01:32:39 +04:00
Sebastián Ramírez
8e1ecaf221 💄 Add FastAPI favicons to docs (#53) 2019-02-24 01:31:50 +04:00
143 changed files with 4989 additions and 378 deletions

1
.gitignore vendored
View File

@@ -11,3 +11,4 @@ site
coverage.xml
.netlify
test.db
log.txt

View File

@@ -1,10 +1,12 @@
dist: xenial
language: python
cache: pip
python:
- "3.6"
- "3.7-dev"
- "3.7"
install:
- pip install flit
@@ -15,3 +17,9 @@ script:
after_script:
- bash <(curl -s https://codecov.io/bash)
deploy:
provider: script
script: bash scripts/trigger-docker.sh
on:
branch: master

View File

@@ -22,10 +22,12 @@ ujson = "*"
flake8 = "*"
python-multipart = "*"
sqlalchemy = "*"
uvicorn = "*"
[packages]
starlette = "==0.11.1"
pydantic = "==0.18.2"
pydantic = "==0.21.0"
databases = {extras = ["sqlite"],version = "*"}
[requires]
python_version = "3.6"

362
Pipfile.lock generated
View File

@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "6b55a2dcce8b6bd5a1be8f170acb18478149218a01d1b026981a6297800cd3fa"
"sha256": "24b3b7b88d3cbe671ddbe296e64c15f8558f0e5d5df977200119872a363aac13"
},
"pipfile-spec": 6,
"requires": {
@@ -16,6 +16,37 @@
]
},
"default": {
"aiocontextvars": {
"hashes": [
"sha256:1e0ff5837c8b01c36a1107acdd0baf7853ebdf6c9fc43e8e311f4be37ac2038a",
"sha256:6ff7aee14f549d52f0446cbb84d0deddcd3fc677bcf8fbc2ce13f5756d2064dc"
],
"markers": "python_version < '3.7'",
"version": "==0.2.1"
},
"aiosqlite": {
"hashes": [
"sha256:af4fed9e778756fa0ffffc7a8b14c4d7b1a57155dc5669f18e45107313f6019e"
],
"version": "==0.9.0"
},
"contextvars": {
"hashes": [
"sha256:2341042e1c03a271813e07dba29b6b60fa85c1005ea5ed1638a076cf50b4d625"
],
"markers": "python_version < '3.7'",
"version": "==2.3"
},
"databases": {
"extras": [
"sqlite"
],
"hashes": [
"sha256:4a0f15669c390a04b439972426350c0ae921ddc08c42bd54f125eb2fb86ee728"
],
"index": "pypi",
"version": "==0.2.0"
},
"dataclasses": {
"hashes": [
"sha256:454a69d788c7fda44efd71e259be79577822f5e3f53f029a22d08004e951dc9f",
@@ -24,13 +55,35 @@
"markers": "python_version < '3.7'",
"version": "==0.6"
},
"immutables": {
"hashes": [
"sha256:1e4f4513254ef11e0230a558ee0dcb4551b914993c330005d15338da595d3750",
"sha256:228e38dc7a810ba4ff88909908ac47f840e5dc6c4c0da6b25009c626a9ae771c",
"sha256:2ae88fbfe1d04f4e5859c924e97313edf70e72b4f19871bf329b96a67ede9ba0",
"sha256:2d32b61c222cba1dd11f0faff67c7fb6204ef1982454e1b5b001d4b79966ef17",
"sha256:35af186bfac5b62522fdf2cab11120d7b0547f405aa399b6a1e443cf5f5e318c",
"sha256:63023fa0cceedc62e0d1535cd4ca7a1f6df3120a6d8e5c34e89037402a6fd809",
"sha256:6bf5857f42a96331fd0929c357dc0b36a72f339f3b6acaf870b149c96b141f69",
"sha256:7bb1590024a032c7a57f79faf8c8ff5e91340662550d2980e0177f67e66e9c9c",
"sha256:7c090687d7e623d4eca22962635b5e1a1ee2d6f9a9aca2f3fb5a184a1ffef1f2",
"sha256:bc36a0a8749881eebd753f696b081bd51145e4d77291d671d2e2f622e5b65d2f",
"sha256:d9fc6a236018d99af6453ead945a6bb55f98d14b1801a2c229dd993edc753a00"
],
"version": "==0.6"
},
"pydantic": {
"hashes": [
"sha256:9f023811b6cefd203c5fd8fd15a4152f04e79e531b8f676ab1244dfe06ce8024",
"sha256:edbb08b561feda505374c0f25e4b54466a0a0c702ed6b2efaabdc3890d1a82e7"
"sha256:93fa585402e7c8c01623ea8af6ca23363e8b4c6a020b7a2de9e99fa29d642d50",
"sha256:eb441dd50779347a450494c437db3ecbb13c1f3854497df879662782af516c5c"
],
"index": "pypi",
"version": "==0.18.2"
"version": "==0.21.0"
},
"sqlalchemy": {
"hashes": [
"sha256:781fb7b9d194ed3fc596b8f0dd4623ff160e3e825dd8c15472376a438c19598b"
],
"version": "==1.3.1"
},
"starlette": {
"hashes": [
@@ -57,10 +110,10 @@
},
"attrs": {
"hashes": [
"sha256:10cbf6e27dbce8c30807caf056c8eb50917e0eaafe86347671b57254006c3e69",
"sha256:ca4be454458f9dec299268d472aaa5a11f67a4ff70093396e1ceae9c76cf4bbb"
"sha256:69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79",
"sha256:f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399"
],
"version": "==18.2.0"
"version": "==19.1.0"
},
"autoflake": {
"hashes": [
@@ -86,11 +139,11 @@
},
"black": {
"hashes": [
"sha256:817243426042db1d36617910df579a54f1afd659adb96fc5032fcf4b36209739",
"sha256:e030a9a28f542debc08acceb273f228ac422798e5215ba2a791a6ddeaaca22a5"
"sha256:09a9dcb7c46ed496a9850b76e4e825d6049ecd38b611f1224857a79bd985a8cf",
"sha256:68950ffd4d9169716bcb8719a56c07a2f4485354fec061cdd5910aa07369731c"
],
"index": "pypi",
"version": "==18.9b0"
"version": "==19.3b0"
},
"bleach": {
"hashes": [
@@ -101,10 +154,10 @@
},
"certifi": {
"hashes": [
"sha256:47f9c83ef4c0c621eaef743f133f09fa8a74a9b75f037e8624f83bd1b6626cb7",
"sha256:993f830721089fef441cdfeb4b2c8c9df86f0c63239f06bd025a76a7daddb033"
"sha256:59b7658e26ca9c7339e00f8f4636cdfe59d34fa37b9b04f6f9e9926b3cece1a5",
"sha256:b26104d6835d1f5e49452a26eb2ff87fe7090b89dfcaee5ea2212697e1e1d7ae"
],
"version": "==2018.11.29"
"version": "==2019.3.9"
},
"chardet": {
"hashes": [
@@ -156,10 +209,10 @@
},
"decorator": {
"hashes": [
"sha256:33cd704aea07b4c28b3eb2c97d288a06918275dac0ecebdaf1bc8a48d98adb9e",
"sha256:cabb249f4710888a2fc0e13e9a16c343d932033718ff62e1e9bc93a9d3a9122b"
"sha256:86156361c50488b84a3f148056ea716ca587df2f0de1d34750d35c21312725de",
"sha256:f069f3a01830ca754ba5258fde2278454a0b5b79e0d7f5c13b3b97e57d4acff6"
],
"version": "==4.3.2"
"version": "==4.4.0"
},
"defusedxml": {
"hashes": [
@@ -199,11 +252,11 @@
},
"flake8": {
"hashes": [
"sha256:6d8c66a65635d46d54de59b027a1dda40abbe2275b3164b634835ac9c13fd048",
"sha256:6eab21c6e34df2c05416faa40d0c59963008fff29b6f0ccfe8fa28152ab3e383"
"sha256:859996073f341f2670741b51ec1e67a01da142831aa1fdc6242dbf88dffbe661",
"sha256:a796a115208f5c03b18f332f7c11729812c8c3ded6c46319c59b53efd3819da8"
],
"index": "pypi",
"version": "==3.7.6"
"version": "==3.7.7"
},
"flit": {
"hashes": [
@@ -213,6 +266,19 @@
"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",
@@ -229,11 +295,11 @@
},
"ipython": {
"hashes": [
"sha256:06de667a9e406924f97781bda22d5d76bfb39762b678762d86a466e63f65dc39",
"sha256:5d3e020a6b5f29df037555e5c45ab1088d6a7cf3bd84f47e0ba501eeb0c3ec82"
"sha256:b038baa489c38f6d853a3cfc4c635b0cda66f2864d136fe8f40c1a6e334e2a6b",
"sha256:f5102c1cd67e399ec8ea66bcebe6e3968ea25a8977e53f012963e5affeb1fe38"
],
"markers": "python_version >= '3.3'",
"version": "==7.3.0"
"version": "==7.4.0"
},
"ipython-genutils": {
"hashes": [
@@ -251,19 +317,18 @@
},
"isort": {
"hashes": [
"sha256:1153601da39a25b14ddc54955dbbacbb6b2d19135386699e2ad58517953b34af",
"sha256:b9c40e9750f3d77e6e4d441d8b0266cf555e7cdabdcff33c4fd06366ca761ef8",
"sha256:ec9ef8f4a9bc6f71eec99e1806bfa2de401650d996c59330782b89a5555c1497"
"sha256:08f8e3f0f0b7249e9fad7e5c41e2113aba44969798a26452ee790c06f155d4ec",
"sha256:4e9e9c4bd1acd66cf6c36973f29b031ec752cbfd991c69695e4e259f9a756927"
],
"index": "pypi",
"version": "==4.3.4"
"version": "==4.3.16"
},
"jedi": {
"hashes": [
"sha256:571702b5bd167911fe9036e5039ba67f820d6502832285cde8c881ab2b2149fd",
"sha256:c8481b5e59d34a5c7c42e98f6625e633f6ef59353abea6437472c7ec2093f191"
"sha256:2bb0603e3506f708e792c7f4ad8fc2a7a9d9c2d292a358fbbd58da531695595b",
"sha256:2c6bcd9545c7d6440951b12b44d373479bf18123a401a52025cf98563fbd826c"
],
"version": "==0.13.2"
"version": "==0.13.3"
},
"jinja2": {
"hashes": [
@@ -274,10 +339,10 @@
},
"jsonschema": {
"hashes": [
"sha256:683fe7ed58763ea0be572de5aad47cd3cc1297640916f9a8ccd222b287da7d2f",
"sha256:b42d7a292addb57370e6260bcbadb77e00a899fe6ec998c453f45893c41c658b"
"sha256:0c0a81564f181de3212efa2d17de1910f8732fa1b71c42266d983cd74304e20d",
"sha256:a5f6559964a3851f59040d3b961de5e68e70971afb88ba519d27e6a039efff1a"
],
"version": "==3.0.0b3"
"version": "==3.0.1"
},
"jupyter": {
"hashes": [
@@ -332,36 +397,36 @@
},
"markupsafe": {
"hashes": [
"sha256:048ef924c1623740e70204aa7143ec592504045ae4429b59c30054cb31e3c432",
"sha256:130f844e7f5bdd8e9f3f42e7102ef1d49b2e6fdf0d7526df3f87281a532d8c8b",
"sha256:19f637c2ac5ae9da8bfd98cef74d64b7e1bb8a63038a3505cd182c3fac5eb4d9",
"sha256:1b8a7a87ad1b92bd887568ce54b23565f3fd7018c4180136e1cf412b405a47af",
"sha256:1c25694ca680b6919de53a4bb3bdd0602beafc63ff001fea2f2fc16ec3a11834",
"sha256:1f19ef5d3908110e1e891deefb5586aae1b49a7440db952454b4e281b41620cd",
"sha256:1fa6058938190ebe8290e5cae6c351e14e7bb44505c4a7624555ce57fbbeba0d",
"sha256:31cbb1359e8c25f9f48e156e59e2eaad51cd5242c05ed18a8de6dbe85184e4b7",
"sha256:3e835d8841ae7863f64e40e19477f7eb398674da6a47f09871673742531e6f4b",
"sha256:4e97332c9ce444b0c2c38dd22ddc61c743eb208d916e4265a2a3b575bdccb1d3",
"sha256:525396ee324ee2da82919f2ee9c9e73b012f23e7640131dd1b53a90206a0f09c",
"sha256:52b07fbc32032c21ad4ab060fec137b76eb804c4b9a1c7c7dc562549306afad2",
"sha256:52ccb45e77a1085ec5461cde794e1aa037df79f473cbc69b974e73940655c8d7",
"sha256:5c3fbebd7de20ce93103cb3183b47671f2885307df4a17a0ad56a1dd51273d36",
"sha256:5e5851969aea17660e55f6a3be00037a25b96a9b44d2083651812c99d53b14d1",
"sha256:5edfa27b2d3eefa2210fb2f5d539fbed81722b49f083b2c6566455eb7422fd7e",
"sha256:7d263e5770efddf465a9e31b78362d84d015cc894ca2c131901a4445eaa61ee1",
"sha256:83381342bfc22b3c8c06f2dd93a505413888694302de25add756254beee8449c",
"sha256:857eebb2c1dc60e4219ec8e98dfa19553dae33608237e107db9c6078b1167856",
"sha256:98e439297f78fca3a6169fd330fbe88d78b3bb72f967ad9961bcac0d7fdd1550",
"sha256:bf54103892a83c64db58125b3f2a43df6d2cb2d28889f14c78519394feb41492",
"sha256:d9ac82be533394d341b41d78aca7ed0e0f4ba5a2231602e2f05aa87f25c51672",
"sha256:e982fe07ede9fada6ff6705af70514a52beb1b2c3d25d4e873e82114cf3c5401",
"sha256:edce2ea7f3dfc981c4ddc97add8a61381d9642dc3273737e756517cc03e84dd6",
"sha256:efdc45ef1afc238db84cb4963aa689c0408912a0239b0721cb172b4016eb31d6",
"sha256:f137c02498f8b935892d5c0172560d7ab54bc45039de8805075e19079c639a9c",
"sha256:f82e347a72f955b7017a39708a3667f106e6ad4d10b25f237396a7115d8ed5fd",
"sha256:fb7c206e01ad85ce57feeaaa0bf784b97fa3cad0d4a5737bc5295785f5c613a1"
"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.0"
"version": "==1.1.1"
},
"mccabe": {
"hashes": [
@@ -387,11 +452,11 @@
},
"mkdocs-material": {
"hashes": [
"sha256:63c49a7020e5d187d5adcd441b259e0b81ad418599b22e2c2574b419ed833851",
"sha256:90a240f268f182a96098490d35bb75d5efc86b2f67d63a82b8750da20a72ef60"
"sha256:0b394aa034b25a09a5874ae2a6ccc426fd81f5764e0991217b169e31cb0c1c0e",
"sha256:f5bb80a2c16d045d380edb2c5b05636af1bb709cb859bfaa9d01063a11df803f"
],
"index": "pypi",
"version": "==4.0.1"
"version": "==4.1.0"
},
"more-itertools": {
"hashes": [
@@ -432,10 +497,10 @@
},
"notebook": {
"hashes": [
"sha256:3ab2db8bc10e6edbd264c3c4b800bee276c99818386ee0c146d98d7e6bcf0a67",
"sha256:d908673a4010787625c8952e91a22adf737db031f2aa0793ad92f6558918a74a"
"sha256:18a98858c0331fb65a60f2ebb6439f8c0c4defd14ca363731b6cabc7f61624b4",
"sha256:cc027a62be0f7756e0ef3d2d98458c4d7f4b3566449fb1a05891207f5bd9a1bf"
],
"version": "==5.7.4"
"version": "==5.7.6"
},
"pandocfilters": {
"hashes": [
@@ -467,24 +532,24 @@
},
"pluggy": {
"hashes": [
"sha256:8ddc32f03971bfdf900a81961a48ccf2fb677cf7715108f85295c67405798616",
"sha256:980710797ff6a041e9a73a5787804f848996ecaa6f8a1b1e08224a5894f2074a"
"sha256:19ecf9ce9db2fce065a7a0586e07cfb4ac8614fe96edf628a264b1c70116cf8f",
"sha256:84d306a647cc805219916e62aab89caa97a33a1dd8c342e87a37f91073cd4746"
],
"version": "==0.8.1"
"version": "==0.9.0"
},
"prometheus-client": {
"hashes": [
"sha256:e8c11ff5ca53de6c3d91e1510500611cafd1d247a937ec6c588a0a7cc3bef93c"
"sha256:1b38b958750f66f208bcd9ab92a633c0c994d8859c831f7abc1f46724fcee490"
],
"version": "==0.5.0"
"version": "==0.6.0"
},
"prompt-toolkit": {
"hashes": [
"sha256:88002cc618cacfda8760c4539e76c3b3f148ecdb7035a3d422c7ecdc90c2a3ba",
"sha256:c6655a12e9b08edb8cf5aeab4815fd1e1bdea4ad73d3bbf269cf2e0c4eb75d5e",
"sha256:df5835fb8f417aa55e5cafadbaeb0cf630a1e824aad16989f9f0493e679ec010"
"sha256:11adf3389a996a6d45cc277580d0d53e8a5afd281d0c9ec71b28e6f121463780",
"sha256:2519ad1d8038fd5fc8e770362237ad0364d16a7650fb5724af6997ed5515e3c1",
"sha256:977c6583ae813a37dc1c2e1b715892461fcbdaa57f6fc62f33a528c4886c8f55"
],
"version": "==2.0.8"
"version": "==2.0.9"
},
"ptyprocess": {
"hashes": [
@@ -496,10 +561,10 @@
},
"py": {
"hashes": [
"sha256:bf92637198836372b520efcba9e020c330123be8ce527e535d185ed4b6f45694",
"sha256:e76826342cefe3c3d5f7e8ee4316b80d1dd8a300781612ddbc765c17ba25a6c6"
"sha256:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa",
"sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53"
],
"version": "==1.7.0"
"version": "==1.8.0"
},
"pycodestyle": {
"hashes": [
@@ -510,10 +575,10 @@
},
"pyflakes": {
"hashes": [
"sha256:5e8c00e30c464c99e0b501dc160b13a14af7f27d4dffb529c556e30a159e231d",
"sha256:f277f9ca3e55de669fba45b7393a1449009cff5a37d1af10ebb76c52765269cd"
"sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0",
"sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2"
],
"version": "==2.1.0"
"version": "==2.1.1"
},
"pygments": {
"hashes": [
@@ -531,17 +596,17 @@
},
"pyrsistent": {
"hashes": [
"sha256:07f7ae71291af8b0dbad8c2ab630d8223e4a8c4e10fc37badda158c02e753acf"
"sha256:3ca82748918eb65e2d89f222b702277099aca77e34843c5eb9d52451173970e2"
],
"version": "==0.14.10"
"version": "==0.14.11"
},
"pytest": {
"hashes": [
"sha256:067a1d4bf827ffdd56ad21bd46674703fce77c5957f6c1eef731f6146bfcef1c",
"sha256:9687049d53695ad45cf5fdc7bbd51f0c49f1ea3ecfc4b7f3fde7501b541f17f4"
"sha256:592eaa2c33fae68c7d75aacf042efc9f77b27c08a6224a4f59beab8d9a420523",
"sha256:ad3ad5c450284819ecde191a654c09b0ec72257a2c711b9633d677c71c9850c4"
],
"index": "pypi",
"version": "==4.3.0"
"version": "==4.3.1"
},
"pytest-cov": {
"hashes": [
@@ -573,33 +638,49 @@
},
"pyyaml": {
"hashes": [
"sha256:254bf6fda2b7c651837acb2c718e213df29d531eebf00edb54743d10bcb694eb",
"sha256:3108529b78577327d15eec243f0ff348a0640b0c3478d67ad7f5648f93bac3e2",
"sha256:3c17fb92c8ba2f525e4b5f7941d850e7a48c3a59b32d331e2502a3cdc6648e76",
"sha256:8d6d96001aa7f0a6a4a95e8143225b5d06e41b1131044913fecb8f85a125714b",
"sha256:c8a88edd93ee29ede719080b2be6cb2333dfee1dccba213b422a9c8e97f2967b"
"sha256:1adecc22f88d38052fb787d959f003811ca858b799590a5eaa70e63dca50308c",
"sha256:436bc774ecf7c103814098159fbb84c2715d25980175292c648f2da143909f95",
"sha256:460a5a4248763f6f37ea225d19d5c205677d8d525f6a83357ca622ed541830c2",
"sha256:5a22a9c84653debfbf198d02fe592c176ea548cccce47553f35f466e15cf2fd4",
"sha256:7a5d3f26b89d688db27822343dfa25c599627bc92093e788956372285c6298ad",
"sha256:9372b04a02080752d9e6f990179a4ab840227c6e2ce15b95e1278456664cf2ba",
"sha256:a5dcbebee834eaddf3fa7366316b880ff4062e4bcc9787b78c7fbb4a26ff2dd1",
"sha256:aee5bab92a176e7cd034e57f46e9df9a9862a71f8f37cad167c6fc74c65f5b4e",
"sha256:c51f642898c0bacd335fc119da60baae0824f2cde95b0330b56c0553439f0673",
"sha256:c68ea4d3ba1705da1e0d85da6684ac657912679a649e8868bd850d2c299cce13",
"sha256:e23d0cc5299223dcc37885dae624f382297717e459ea24053709675a976a3e19"
],
"version": "==4.2b4"
"version": "==5.1"
},
"pyzmq": {
"hashes": [
"sha256:15f0bf7cd80020f165635595e197603aedb37fddf4164ad5ae226afc43242f7b",
"sha256:1756dc72e192c670490e38c788c3a35f901adc74ee436e5131d5a3e85fdd7dc6",
"sha256:1d1eb490da54679d724b08ef3ee04530849023670c4ba7e400ed2cdf906720c4",
"sha256:228402625796821f08706f58cc42a3c51c9897d723550babaefe4feec2b6dacc",
"sha256:3928534fa00a2aabfcfdb439c08ba37fbe99ab0cf57776c8db8d2b73a51693ba",
"sha256:3d2a295b1086d450981f73d3561ac204a0cc9c8ded386a4a34327d918f3b1d0a",
"sha256:4fd8621a309db6ec23ef1369f43cdf7a9b0dc217d8ff9ca4095a6e932b379bda",
"sha256:54fe55a1694ffe608c8e4c5183e83cab7a91f3e5c84bd6f188868d6676c12aba",
"sha256:618887be4ad754228c0cbba7631f6574608b4430fe93974e6322324f1304fdac",
"sha256:69130efb6efa936de601cb135a8a4eec1caccd4ea2b784237145ff4075c2d3ae",
"sha256:6e7f78eeac82140bde7e60e975c6e6b1b678a4dd377782ab63319c1c78bf3aa1",
"sha256:6ee760cdb84e43574da6b3f2f1fc1251e8acf87253900d28a06451c5f5de39e9",
"sha256:97cb1b7cd2c46e87b0a26651eccd2bbb8c758035efd1635ebb81ac36aa76a88c",
"sha256:abfa774dbadacc849121ed92eae05189d226daab583388b499472e1bbb17ef69",
"sha256:b30c339eb58355f51f4f54dd61d785f1ff58c86bca1c3a5916977631d121867b"
"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.0"
"version": "==18.0.1"
},
"qtconsole": {
"hashes": [
@@ -632,10 +713,9 @@
},
"sqlalchemy": {
"hashes": [
"sha256:7dede29f121071da9873e7b8c98091874617858e790dc364ffaab4b09d81216c"
"sha256:781fb7b9d194ed3fc596b8f0dd4623ff160e3e825dd8c15472376a438c19598b"
],
"index": "pypi",
"version": "==1.3.0b3"
"version": "==1.3.1"
},
"terminado": {
"hashes": [
@@ -660,9 +740,15 @@
},
"tornado": {
"hashes": [
"sha256:d3b719a0cb7094e2b1ca94b31f4b601639fa7ad01a548a1a2ccdd6cbdfd56671"
"sha256:1174dcb84d08887b55defb2cda1986faeeea715fff189ef3dc44cce99f5fca6b",
"sha256:2613fab506bd2aedb3722c8c64c17f8f74f4070afed6eea17f20b2115e445aec",
"sha256:44b82bc1146a24e5b9853d04c142576b4e8fa7a92f2e30bc364a85d1f75c4de2",
"sha256:457fcbee4df737d2defc181b9073758d73f54a6cfc1f280533ff48831b39f4a8",
"sha256:49603e1a6e24104961497ad0c07c799aec1caac7400a6762b687e74c8206677d",
"sha256:8c2f40b99a8153893793559919a355d7b74649a11e59f411b0b0a1793e160bc0",
"sha256:e1d897889c3b5a829426b7d52828fb37b28bc181cd598624e65c8be40ee3f7fa"
],
"version": "==6.0b1"
"version": "==6.0.2"
},
"traitlets": {
"hashes": [
@@ -709,6 +795,28 @@
],
"version": "==1.24.1"
},
"uvicorn": {
"hashes": [
"sha256:d700b65169820fc260f39402b7f966c178691daaa40cb376cad99d7cd737f772"
],
"index": "pypi",
"version": "==0.7.0b1"
},
"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",
@@ -723,6 +831,32 @@
],
"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",

View File

@@ -14,6 +14,9 @@
<a href="https://pypi.org/project/fastapi" target="_blank">
<img src="https://badge.fury.io/py/fastapi.svg" alt="Package version">
</a>
<a href="https://gitter.im/tiangolo/fastapi?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge" target="_blank">
<img src="https://badges.gitter.im/tiangolo/fastapi.svg" alt="Join the chat at https://gitter.im/tiangolo/fastapi">
</a>
</p>
---
@@ -31,10 +34,10 @@ The key features are:
* **Fast**: Very high performance, on par with **NodeJS** and **Go** (thanks to Starlette and Pydantic). [One of the fastest Python frameworks available](#performance).
* **Fast to code**: Increase the speed to develop features by about 200% to 300% *.
* **Less bugs**: Reduce about 40% of human (developer) induced errors. *
* **Fewer bugs**: Reduce about 40% of human (developer) induced errors. *
* **Intuitive**: Great editor support. <abbr title="also known as auto-complete, autocompletion, IntelliSense">Completion</abbr> everywhere. Less time debugging.
* **Easy**: Designed to be easy to use and learn. Less time reading docs.
* **Short**: Minimize code duplication. Multiple features from each parameter declaration. Less bugs.
* **Short**: Minimize code duplication. Multiple features from each parameter declaration. Fewer bugs.
* **Robust**: Get production-ready code. With automatic interactive documentation.
* **Standards-based**: Based on (and fully compatible with) the open standards for APIs: <a href="https://github.com/OAI/OpenAPI-Specification" target="_blank">OpenAPI</a> (previously known as Swagger) and <a href="http://json-schema.org/" target="_blank">JSON Schema</a>.
@@ -116,17 +119,17 @@ If you don't know, check the _"In a hurry?"_ section about <a href="https://fast
Run the server with:
```bash
uvicorn main:app --debug
uvicorn main:app --reload
```
<details markdown="1">
<summary>About the command <code>uvicorn main:app --debug</code>...</summary>
<summary>About the command <code>uvicorn main:app --reload</code>...</summary>
The command `uvicorn main:app` refers to:
* `main`: the file `main.py` (the Python "module").
* `app`: the object created inside of `main.py` with the line `app = FastAPI()`.
* `--debug`: make the server restart after code changes. Only do this for development.
* `--reload`: make the server restart after code changes. Only do this for development.
</details>
@@ -199,7 +202,7 @@ def create_item(item_id: int, item: Item):
return {"item_name": item.name, "item_id": item_id}
```
The server should reload automatically (because you added `--debug` to the `uvicorn` command above).
The server should reload automatically (because you added `--reload` to the `uvicorn` command above).
### Interactive API docs upgrade
@@ -344,7 +347,7 @@ For a more complete example including more features, see the <a href="https://fa
## Performance
Independent TechEmpower benchmarks show **FastAPI** applications running under Uvicorn as <a href="https://www.techempower.com/benchmarks/#section=test&runid=a979de55-980d-4721-a46f-77298b3f3923&hw=ph&test=fortune&l=zijzen-7" target="_blank">one of the fastest Python frameworks available</a>, only below Starlette and Uvicorn themselves (used internally by FastAPI). (*)
Independent TechEmpower benchmarks show **FastAPI** applications running under Uvicorn as <a href="https://www.techempower.com/benchmarks/#section=test&runid=7464e520-0dc2-473d-bd34-dbdfd7e85911&hw=ph&test=query&l=zijzen-7" target="_blank">one of the fastest Python frameworks available</a>, only below Starlette and Uvicorn themselves (used internally by FastAPI). (*)
To understand more about it, see the section <a href="https://fastapi.tiangolo.com/benchmarks/" target="_blank">Benchmarks</a>.

View File

@@ -123,7 +123,7 @@ There are several Flask REST frameworks, but after investing the time and work i
### <a href="https://marshmallow.readthedocs.io/en/3.0/" target="_blank">Marshmallow</a>
One of the main features needed by API systems is data "<abbr title="also called marshalling, convertion">serialization</abbr>" which is taking data from the code (Python) and converting it into something that can be sent through the network. For example, converting an object containing data from a database into a JSON object. Converting `datetime` objects into strings, etc.
One of the main features needed by API systems is data "<abbr title="also called marshalling, conversion">serialization</abbr>" which is taking data from the code (Python) and converting it into something that can be sent through the network. For example, converting an object containing data from a database into a JSON object. Converting `datetime` objects into strings, etc.
Another big feature needed by APIs is data validation, making sure that the data is valid, given certain parameters. For example, that some field is an `int`, and not some random string. This is especially useful for incoming data.
@@ -365,7 +365,7 @@ It is the recommended server for Starlette and **FastAPI**.
!!! check "**FastAPI** recommends it as"
The main web server to run **FastAPI** applications.
You can combine it with Gunicorn, to have an asynchronous multiprocess server.
You can combine it with Gunicorn, to have an asynchronous multi-process server.
Check more details in the <a href="/deployment/" target="_blank">Deployment</a> section.

View File

@@ -329,7 +329,7 @@ So, about the egg and the chicken, how do you call the first `async` function?
If you are working with **FastAPI** you don't have to worry about that, because that "first" function will be your path operation function, and FastAPI will know how to do the right thing.
But if you want to use `async` / `await` without FastAPI, <a href="https://docs.python.org/3/library/asyncio-task.html#coroutine" target="_blank">check the official Python docs</a>
But if you want to use `async` / `await` without FastAPI, <a href="https://docs.python.org/3/library/asyncio-task.html#coroutine" target="_blank">check the official Python docs</a>.
### Other forms of asynchronous code
@@ -362,3 +362,43 @@ Let's see the same phrase from above:
That should make more sense now.
All that is what powers FastAPI (through Starlette) and what makes it have such an impressive performance.
## Very Technical Details
!!! warning
You can probably skip this.
These are very technical details of how **FastAPI** works underneath.
If you have quite some technical knowledge (co-routines, threads, blocking, etc) and are curious about how FastAPI handles `async def` vs normal `def`, go ahead.
### Path operation functions
When you declare a *path operation function* with normal `def` instead of `async def`, it is run in an external threadpool that is then awaited, instead of being called directly (as it would block the server).
If you are coming from another async framework that does not work in the way described above and you are used to define trivial compute-only *path operation functions* with plain `def` for a tiny performance gain (about 100 nanoseconds), please note that in **FastAPI** the effect would be quite opposite. In these cases, it's better to use `async def` unless your *path operation functions* use code that performs blocking <abbr title="Input/Output: disk reading or writing, network communications.">IO</abbr>.
Still, in both situations, chances are that **FastAPI** will <a href="https://fastapi.tiangolo.com/#performance" target="_blank">still be faster</a> than (or at least comparable to) your previous framework.
### Dependencies
The same applies for dependencies. If a dependency is a standard `def` function instead of `async def`, it is run in the external threadpool.
### Sub-dependencies
You can have multiple dependencies and sub-dependencies requiring each other (as parameters of the function definitions), some of them might be created with `async def` and some with normal `def`. It would still work, and the ones created with normal `def` would be called on an external thread instead of being "awaited".
### Other utility functions
Any other utility function that you call directly can be created with normal `def` or `async def` and FastAPI won't affect the way you call it.
This is in contrast to the functions that FastAPI calls for you: *path operation functions* and dependencies.
If your utility function is a normal function with `def`, it will be called directly (as you write it in your code), not in a threadpool, if the function is created with `async def` then you should await for that function when you call it in your code.
---
Again, these are very technical details that would probably be useful if you came searching for them.
Otherwise, you should be good with the guidelines from the section above: <a href="#in-a-hurry">In a hurry?</a>.

View File

@@ -74,7 +74,7 @@ All the documentation is in Markdown format in the directory `./docs`.
Many of the tutorials have blocks of code.
In most of the cases, these blocks of code are actual complete applicactions that can be run as is.
In most of the cases, these blocks of code are actual complete applications that can be run as is.
In fact, those blocks of code are not written inside the Markdown, they are Python files in the `./docs/src/` directory.
@@ -106,7 +106,7 @@ That way, you can edit the documentation/source files and see the changes live.
And if you run the examples with, e.g.:
```bash
uvicorn tutorial001:app --debug
uvicorn tutorial001:app --reload
```
as Uvicorn by default will use the port `8000`, the documentation on port `8008` won't clash.

View File

@@ -26,7 +26,7 @@ But you can still change and update all the configurations with environment vari
To see all the configurations and options, go to the Docker image page: <a href="https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker" target="_blank">tiangolo/uvicorn-gunicorn-fastapi</a>.
### Build your Image
### Create a `Dockerfile`
* Go to your project directory.
* Create a `Dockerfile` with:
@@ -37,6 +37,37 @@ FROM tiangolo/uvicorn-gunicorn-fastapi:python3.7
COPY ./app /app
```
#### Bigger Applications
If you followed the section about creating <a href="https://fastapi.tiangolo.com/tutorial/bigger-applications/" target="_blank">Bigger Applications with Multiple Files
</a>, your `Dockerfile` might instead look like:
```Dockerfile
FROM tiangolo/uvicorn-gunicorn-fastapi:python3.7
COPY ./app /app/app
```
#### Raspberry Pi and other architectures
If you are running Docker in a Raspberry Pi (that has an ARM processor) or any other architecture, you can create a `Dockerfile` from scratch, based on a Python base image (that is multi-architecture) and use Uvicorn alone.
In this case, your `Dockerfile` could look like:
```Dockerfile
FROM python:3.7
RUN pip install fastapi uvicorn
EXPOSE 80
COPY ./app /app
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]
```
### Create the **FastAPI** Code
* Create an `app` directory and enter in it.
* Create a `main.py` file with:
@@ -65,6 +96,8 @@ def read_item(item_id: int, q: str = None):
└── Dockerfile
```
### Build the Docker image
* Go to the project directory (in where your `Dockerfile` is, containing your `app` directory).
* Build your FastAPI image:
@@ -72,6 +105,8 @@ def read_item(item_id: int, q: str = None):
docker build -t myimage .
```
### Start the Docker container
* Run a container based on your image:
```bash
@@ -145,7 +180,7 @@ Now, from a developer's perspective, here are several things to have in mind whi
* It goes encrypted, but the encrypted contents are the same HTTP protocol.
It is a common practice to have one program/HTTP server runing 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 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>.
### Let's Encrypt
@@ -204,7 +239,7 @@ You can deploy **FastAPI** directly without Docker too.
You just need to install <a href="https://www.uvicorn.org/" target="_blank">Uvicorn</a> (or any other ASGI server).
And run your application the same way you have done in the tutorials, but without the `--debug` option, e.g.:
And run your application the same way you have done in the tutorials, but without the `--reload` option, e.g.:
```bash
uvicorn main:app --host 0.0.0.0 --port 80

View File

@@ -71,7 +71,7 @@ my_second_user: User = User(**second_user_data)
### Editor support
All the framework was designed to be easy and intuitive to use, all the decisons where tested on multiple editors even before starting development, to ensure the best development experience.
All the framework was designed to be easy and intuitive to use, all the decisions where tested on multiple editors even before starting development, to ensure the best development experience.
In the last Python developer survey it was clear <a href="https://www.jetbrains.com/research/python-developers-survey-2017/#tools-and-features" target="_blank">that the most used feature is "autocompletion"</a>.
@@ -89,7 +89,7 @@ Here's how your editor might help you:
![editor support](img/pycharm-completion.png)
You will get completion in code you might even consider imposible before. As for example, the `price` key inside a JSON body (that could have been nested) that comes from a request.
You will get completion in code you might even consider impossible before. As for example, the `price` key inside a JSON body (that could have been nested) that comes from a request.
No more typing the wrong key names, coming back and forth between docs, or scrolling up and down to find if you finally used `username` or `user_name`.
@@ -201,4 +201,4 @@ With **FastAPI** you get all of **Pydantic**'s features (as FastAPI is based on
* You can have deeply **nested JSON** objects and have them all validated and annotated.
* **Extendible**:
* Pydantic allows custom data types to be defined or you can extend validation with methods on a model decorated with the validator decorator.
* 100% test coverage.
* 100% test coverage.

View File

@@ -24,10 +24,19 @@ There you can select "Releases only".
Doing it, you will receive notifications (in your email) whenever there's a new release (a new version) of **FastAPI** with bug fixes and new features.
## Join the chat
<a href="https://gitter.im/tiangolo/fastapi?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge" target="_blank">
<img src="https://badges.gitter.im/tiangolo/fastapi.svg" alt="Join the chat at https://gitter.im/tiangolo/fastapi">
</a>
Join the chat on Gitter: <a href="https://gitter.im/tiangolo/fastapi" target="_blank">https://gitter.im/tiangolo/fastapi</a>.
There you can ask quick questions, help others, share ideas, etc.
## Connect with the author
You can connect with me (Sebastián Ramírez / `tiangolo`), the author.
You can connect with <a href="https://tiangolo.com" target="_blank">me (Sebastián Ramírez / `tiangolo`)</a>, the author.
You can:

View File

@@ -0,0 +1,83 @@
Some time ago, <a href="https://github.com/tiangolo/fastapi/issues/3#issuecomment-454956920" target="_blank">a **FastAPI** user asked</a>:
> Whats the history of this project? It seems to have come from nowhere to awesome in a few weeks [...]
Here's a little bit of that history.
## Alternatives
I have been creating APIs with complex requirements for several years (Machine Learning, distributed systems, asynchronous jobs, NoSQL databases, etc), leading several teams of developers.
As part of that, I needed to investigate, test and use many alternatives.
The history of **FastAPI** is in great part the history of its predecessors.
As said in the section <a href="https://fastapi.tiangolo.com/alternatives/" target="_blank">Alternatives</a>:
<blockquote markdown="1">
**FastAPI** wouldn't exist if not for the previous work of others.
There have been many tools created before that have helped inspire its creation.
I have been avoiding the creation of a new framework for several years. First I tried to solve all the features covered by **FastAPI** using many different frameworks, plug-ins, and tools.
But at some point, there was no other option than creating something that provided all these features, taking the best ideas from previous tools, and combining them in the best way possible, using language features that weren't even available before (Python 3.6+ type hints).
</blockquote>
## Investigation
By using all the previous alternatives I had the chance to learn from all of them, take ideas, and combine them in the best way I could find for myself and the teams of developers I have worked with.
For example, it was clear that ideally it should be based on standard Python type hints.
Also, the best approach was to use already existing standards.
So, before even starting to code **FastAPI**, I spent several months studying the specs for OpenAPI, JSON Schema, OAuth2, etc. Understanding their relationship, overlap, and differences.
## Design
Then I spent some time designing the developer "API" I wanted to have as a user (as a developer using FastAPI).
I tested several ideas in the most popular Python editors: PyCharm, VS Code, Jedi based editors.
By the last <a href="https://www.jetbrains.com/research/python-developers-survey-2018/#development-tools" target="_blank">Python Developer Survey</a>, that covers about 80% of the users.
It means that **FastAPI** was specifically tested with the editors used by 80% of the Python developers. And as most of the other editors tend to work similarly, all its benefits should work for virtually all editors.
That way I could find the best ways to reduce code duplication as much as possible, to have completion everywhere, type and error checks, etc.
All in a way that provided the best development experience for all the developers.
## Requirements
After testing several alternatives, I decided that I was going to use <a href="https://pydantic-docs.helpmanual.io/" target="_blank">**Pydantic**</a> for its advantages.
Then I contributed to it, to make it fully compliant with JSON Schema, to support different ways to define constraint declarations, and to improve editor support (type checks, autocompletion) based on the tests in several editors.
During the development, I also contributed to <a href="https://www.starlette.io/" target="_blank">**Starlette**</a>, the other key requirement.
## Development
By the time I started creating **FastAPI** itself, most of the pieces were already in place, the design was defined, the requirements and tools were ready, and the knowledge about the standards and specifications was clear and fresh.
## Future
By this point, it's already clear that **FastAPI** with its ideas is being useful for many people.
It is being chosen over previous alternatives for suiting many use cases better.
Many developers and teams already depend on **FastAPI** for their projects (including me and my team).
But still, there are many improvements and features to come.
**FastAPI** has a great future ahead.
And <a href="https://fastapi.tiangolo.com/help-fastapi/" target="_blank">your help</a> is greatly appreciated.

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 76 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@@ -14,6 +14,9 @@
<a href="https://pypi.org/project/fastapi" target="_blank">
<img src="https://badge.fury.io/py/fastapi.svg" alt="Package version">
</a>
<a href="https://gitter.im/tiangolo/fastapi?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge" target="_blank">
<img src="https://badges.gitter.im/tiangolo/fastapi.svg" alt="Join the chat at https://gitter.im/tiangolo/fastapi">
</a>
</p>
---
@@ -31,10 +34,10 @@ The key features are:
* **Fast**: Very high performance, on par with **NodeJS** and **Go** (thanks to Starlette and Pydantic). [One of the fastest Python frameworks available](#performance).
* **Fast to code**: Increase the speed to develop features by about 200% to 300% *.
* **Less bugs**: Reduce about 40% of human (developer) induced errors. *
* **Fewer bugs**: Reduce about 40% of human (developer) induced errors. *
* **Intuitive**: Great editor support. <abbr title="also known as auto-complete, autocompletion, IntelliSense">Completion</abbr> everywhere. Less time debugging.
* **Easy**: Designed to be easy to use and learn. Less time reading docs.
* **Short**: Minimize code duplication. Multiple features from each parameter declaration. Less bugs.
* **Short**: Minimize code duplication. Multiple features from each parameter declaration. Fewer bugs.
* **Robust**: Get production-ready code. With automatic interactive documentation.
* **Standards-based**: Based on (and fully compatible with) the open standards for APIs: <a href="https://github.com/OAI/OpenAPI-Specification" target="_blank">OpenAPI</a> (previously known as Swagger) and <a href="http://json-schema.org/" target="_blank">JSON Schema</a>.
@@ -116,17 +119,17 @@ If you don't know, check the _"In a hurry?"_ section about <a href="https://fast
Run the server with:
```bash
uvicorn main:app --debug
uvicorn main:app --reload
```
<details markdown="1">
<summary>About the command <code>uvicorn main:app --debug</code>...</summary>
<summary>About the command <code>uvicorn main:app --reload</code>...</summary>
The command `uvicorn main:app` refers to:
* `main`: the file `main.py` (the Python "module").
* `app`: the object created inside of `main.py` with the line `app = FastAPI()`.
* `--debug`: make the server restart after code changes. Only do this for development.
* `--reload`: make the server restart after code changes. Only do this for development.
</details>
@@ -199,7 +202,7 @@ def create_item(item_id: int, item: Item):
return {"item_name": item.name, "item_id": item_id}
```
The server should reload automatically (because you added `--debug` to the `uvicorn` command above).
The server should reload automatically (because you added `--reload` to the `uvicorn` command above).
### Interactive API docs upgrade
@@ -344,7 +347,7 @@ For a more complete example including more features, see the <a href="https://fa
## Performance
Independent TechEmpower benchmarks show **FastAPI** applications running under Uvicorn as <a href="https://www.techempower.com/benchmarks/#section=test&runid=a979de55-980d-4721-a46f-77298b3f3923&hw=ph&test=fortune&l=zijzen-7" target="_blank">one of the fastest Python frameworks available</a>, only below Starlette and Uvicorn themselves (used internally by FastAPI). (*)
Independent TechEmpower benchmarks show **FastAPI** applications running under Uvicorn as <a href="https://www.techempower.com/benchmarks/#section=test&runid=7464e520-0dc2-473d-bd34-dbdfd7e85911&hw=ph&test=query&l=zijzen-7" target="_blank">one of the fastest Python frameworks available</a>, only below Starlette and Uvicorn themselves (used internally by FastAPI). (*)
To understand more about it, see the section <a href="https://fastapi.tiangolo.com/benchmarks/" target="_blank">Benchmarks</a>.

View File

@@ -29,7 +29,7 @@ John Doe
The function does the following:
* Takes a `fist_name` and `last_name`.
* Takes a `first_name` and `last_name`.
* Converts the first letter of each one to upper case with `title()`.
* <abbr title="Puts them together, as one. With the contents of one after the other.">Concatenates</abbr> them with a space in the middle.
@@ -49,7 +49,7 @@ But then you have to call "that method that converts the first letter to upper c
Was it `upper`? Was it `uppercase`? `first_uppercase`? `capitalize`?
Then, you try with the old programer's friend, editor autocompletion.
Then, you try with the old programmer's friend, editor autocompletion.
You type the first parameter of the function, `first_name`, then a dot (`.`) and then hit `Ctrl+Space` to trigger the completion.

View File

@@ -1,4 +1,123 @@
## Next
## Next release
## 0.12.1
* Fix bug: handling additional `responses` in `APIRouter.include_router()`. PR <a href="https://github.com/tiangolo/fastapi/pull/140" target="_blank">#140</a>.
* Fix typo in SQL tutorial. PR <a href="https://github.com/tiangolo/fastapi/pull/138" target="_blank">#138</a> by <a href="https://github.com/mostaphaRoudsari" target="_blank">@mostaphaRoudsari</a>.
* Fix typos in section about nested models and OAuth2 with JWT. PR <a href="https://github.com/tiangolo/fastapi/pull/127" target="_blank">#127</a> by <a href="https://github.com/mmcloud" target="_blank">@mmcloud</a>.
## 0.12.0
* Add additional `responses` parameter to *path operation decorators* to extend responses in OpenAPI (and API docs).
* It also allows extending existing responses generated from `response_model`, declare other media types (like images), etc.
* The new documentation is here: <a href="https://fastapi.tiangolo.com/tutorial/additional-responses/" target="_blank">Additional Responses</a>.
* `responses` can also be added to `.include_router()`, the updated docs are here: <a href="https://fastapi.tiangolo.com/tutorial/bigger-applications/#add-some-custom-tags-and-responses" target="_blank">Bigger Applications</a>.
* PR <a href="https://github.com/tiangolo/fastapi/pull/97" target="_blank">#97</a> originally initiated by <a href="https://github.com/barsi" target="_blank">@barsi</a>.
* Update `scripts/test-cov-html.sh` to allow passing extra parameters like `-vv`, for development.
## 0.11.0
* Add `auto_error` parameter to security utility functions. Allowing them to be optional. Also allowing to have multiple alternative security schemes that are then checked in a single dependency instead of each one verifying and returning the error to the client automatically when not satisfied. PR <a href="https://github.com/tiangolo/fastapi/pull/134" target="_blank">#134</a>.
* Update <a href="https://fastapi.tiangolo.com/tutorial/sql-databases/#create-a-middleware-to-handle-sessions" target="_blank">SQL Tutorial</a> to close database sessions even when there are exceptions. PR <a href="https://github.com/tiangolo/fastapi/pull/89" target="_blank">#89</a> by <a href="https://github.com/alexiri" target="_blank">@alexiri</a>.
* Fix duplicate dependency in `pyproject.toml`. PR <a href="https://github.com/tiangolo/fastapi/pull/128" target="_blank">#128</a> by <a href="https://github.com/zxalif" target="_blank">@zxalif</a>.
## 0.10.3
* Add Gitter chat, badge, links, etc. <a href="https://gitter.im/tiangolo/fastapi" target="_blank">https://gitter.im/tiangolo/fastapi
</a>. PR <a href="https://github.com/tiangolo/fastapi/pull/117" target="_blank">#117</a>.
* Add docs about <a href="https://fastapi.tiangolo.com/tutorial/extending-openapi/" target="_blank">Extending OpenAPI</a>. PR <a href="https://github.com/tiangolo/fastapi/pull/126" target="_blank">#126</a>.
* Make Travis run Ubuntu Xenial (newer version) and Python 3.7 instead of Python 3.7-dev. PR <a href="https://github.com/tiangolo/fastapi/pull/92" target="_blank">#92</a> by <a href="https://github.com/blueyed" target="_blank">@blueyed</a>.
* Fix duplicated param variable creation. PR <a href="https://github.com/tiangolo/fastapi/pull/123" target="_blank">#123</a> by <a href="https://github.com/yihuang" target="_blank">@yihuang</a>.
* Add note in <a href="https://fastapi.tiangolo.com/tutorial/response-model/" target="_blank">Response Model docs</a> about why using a function parameter instead of a function return type annotation. PR <a href="https://github.com/tiangolo/fastapi/pull/109" target="_blank">#109</a> by <a href="https://github.com/JHSaunders" target="_blank">@JHSaunders</a>.
* Fix event docs (startup/shutdown) function name. PR <a href="https://github.com/tiangolo/fastapi/pull/105" target="_blank">#105</a> by <a href="https://github.com/stratosgear" target="_blank">@stratosgear</a>.
## 0.10.2
* Fix OpenAPI (JSON Schema) for declarations of Python `Union` (JSON Schema `additionalProperties`). PR <a href="https://github.com/tiangolo/fastapi/pull/121" target="_blank">#121</a>.
* Update <a href="https://fastapi.tiangolo.com/tutorial/background-tasks/" target="_blank">Background Tasks</a> with a note on Celery.
* Document response models using unions and lists, updated at: <a href="https://fastapi.tiangolo.com/tutorial/extra-models/" target="_blank">Extra Models</a>. PR <a href="https://github.com/tiangolo/fastapi/pull/108" target="_blank">#108</a>.
## 0.10.1
* Add docs and tests for <a href="https://github.com/encode/databases" target="_blank">encode/databases</a>. New docs at: <a href="https://fastapi.tiangolo.com/tutorial/async-sql-databases/" target="_blank">Async SQL (Relational) Databases</a>. PR <a href="https://github.com/tiangolo/fastapi/pull/107" target="_blank">#107</a>.
## 0.10.0
* Add support for Background Tasks in *path operation functions* and dependencies. New documentation about <a href="https://fastapi.tiangolo.com/tutorial/background-tasks/" target="_blank">Background Tasks is here</a>. PR <a href="https://github.com/tiangolo/fastapi/pull/103" target="_blank">#103</a>.
* Add support for `.websocket_route()` in `APIRouter`. PR <a href="https://github.com/tiangolo/fastapi/pull/100" target="_blank">#100</a> by <a href="https://github.com/euri10" target="_blank">@euri10</a>.
* New docs section about <a href="https://fastapi.tiangolo.com/tutorial/events/" target="_blank">Events: startup - shutdown</a>. PR <a href="https://github.com/tiangolo/fastapi/pull/99" target="_blank">#99</a>.
## 0.9.1
* Document receiving <a href="https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#query-parameter-list-multiple-values" target="_blank">Multiple values with the same query parameter</a> and <a href="https://fastapi.tiangolo.com/tutorial/header-params/#duplicate-headers" target="_blank">Duplicate headers</a>. PR <a href="https://github.com/tiangolo/fastapi/pull/95" target="_blank">#95</a>.
## 0.9.0
* Upgrade compatible Pydantic version to `0.21.0`. PR <a href="https://github.com/tiangolo/fastapi/pull/90" target="_blank">#90</a>.
* Add documentation for: <a href="https://fastapi.tiangolo.com/tutorial/application-configuration/" target="_blank">Application Configuration</a>.
* Fix typo in docs. PR <a href="https://github.com/tiangolo/fastapi/pull/76" target="_blank">#76</a> by <a href="https://github.com/matthewhegarty" target="_blank">@matthewhegarty</a>.
* Fix link in "Deployment" to "Bigger Applications".
## 0.8.0
* Make development scripts executable. PR <a href="https://github.com/tiangolo/fastapi/pull/76" target="_blank">#76</a> by <a href="https://github.com/euri10" target="_blank">@euri10</a>.
* Add support for adding `tags` in `app.include_router()`. PR <a href="https://github.com/tiangolo/fastapi/pull/55" target="_blank">#55</a> by <a href="https://github.com/euri10" target="_blank">@euri10</a>. Documentation updated in the section: <a href="https://fastapi.tiangolo.com/tutorial/bigger-applications/" target="_blank">Bigger Applications</a>.
* Update docs related to Uvicorn to use new `--reload` option from version `0.5.x`. PR <a href="https://github.com/tiangolo/fastapi/pull/74" target="_blank">#74</a>.
* Update `isort` imports and scripts to be compatible with newer versions. PR <a href="https://github.com/tiangolo/fastapi/pull/75" target="_blank">#75</a>.
## 0.7.1
* Update <a href="https://fastapi.tiangolo.com/async/#path-operation-functions" target="_blank">technical details about `async def` handling</a> with respect to previous frameworks. PR <a href="https://github.com/tiangolo/fastapi/pull/64" target="_blank">#64</a> by <a href="https://github.com/haizaar" target="_blank">@haizaar</a>.
* Add <a href="https://fastapi.tiangolo.com/deployment/#raspberry-pi-and-other-architectures" target="_blank">deployment documentation for Docker in Raspberry Pi</a> and other architectures.
* Trigger Docker images build on Travis CI automatically. PR <a href="https://github.com/tiangolo/fastapi/pull/65" target="_blank">#65</a>.
## 0.7.0
* Add support for `UploadFile` in `File` parameter annotations.
* This includes a file-like interface.
* Here's the updated documentation for declaring <a href="https://fastapi.tiangolo.com/tutorial/request-files/#file-parameters-with-uploadfile" target="_blank"> `File` parameters with `UploadFile`</a>.
* And here's the updated documentation for using <a href="https://fastapi.tiangolo.com/tutorial/request-forms-and-files/" target="_blank">`Form` parameters mixed with `File` parameters, supporting `bytes` and `UploadFile`</a> at the same time.
* PR <a href="https://github.com/tiangolo/fastapi/pull/63" target="_blank">#63</a>.
## 0.6.4
* Add <a href="https://fastapi.tiangolo.com/async/#very-technical-details" target="_blank">technical details about `async def` handling to docs</a>. PR <a href="https://github.com/tiangolo/fastapi/pull/61" target="_blank">#61</a>.
* Add docs for <a href="https://fastapi.tiangolo.com/tutorial/debugging/" target="_blank">Debugging FastAPI applications in editors</a>.
* Clarify <a href="https://fastapi.tiangolo.com/deployment/#bigger-applications" target="_blank">Bigger Applications deployed with Docker</a>.
* Fix typos in docs.
* Add section about <a href="https://fastapi.tiangolo.com/history-design-future/" target="_blank">History, Design and Future</a>.
* Add docs for using <a href="https://fastapi.tiangolo.com/tutorial/websockets/" target="_blank">WebSockets with **FastAPI**</a>. PR <a href="https://github.com/tiangolo/fastapi/pull/62" target="_blank">#62</a>.
## 0.6.3
* Add Favicons to docs. PR <a href="https://github.com/tiangolo/fastapi/pull/53" target="_blank">#53</a>.
## 0.6.2
@@ -40,7 +159,7 @@
## 0.4.0
* Add `openapi_prefix`, support for reverse proxy and mounting sub-applicaitons. See the docs at <a href="https://fastapi.tiangolo.com/tutorial/sub-applications-proxy/" target="_blank">https://fastapi.tiangolo.com/tutorial/sub-applications-proxy/</a>: <a href="https://github.com/tiangolo/fastapi/pull/26" target="_blank">#26</a> by <a href="https://github.com/kabirkhan" target="_blank">@kabirkhan</a>.
* Add `openapi_prefix`, support for reverse proxy and mounting sub-applications. See the docs at <a href="https://fastapi.tiangolo.com/tutorial/sub-applications-proxy/" target="_blank">https://fastapi.tiangolo.com/tutorial/sub-applications-proxy/</a>: <a href="https://github.com/tiangolo/fastapi/pull/26" target="_blank">#26</a> by <a href="https://github.com/kabirkhan" target="_blank">@kabirkhan</a>.
* Update <a href="https://fastapi.tiangolo.com/tutorial/sql-databases/" target="_blank">docs/tutorial for SQLAlchemy</a> including note about *DB Browser for SQLite*.

View File

@@ -0,0 +1,23 @@
from fastapi import FastAPI
from pydantic import BaseModel
from starlette.responses import JSONResponse
class Item(BaseModel):
id: str
value: str
class Message(BaseModel):
message: str
app = FastAPI()
@app.get("/items/{item_id}", response_model=Item, responses={404: {"model": Message}})
async def read_item(item_id: str):
if item_id == "foo":
return {"id": "foo", "value": "there goes my hero"}
else:
return JSONResponse(status_code=404, content={"message": "Item not found"})

View File

@@ -0,0 +1,28 @@
from fastapi import FastAPI
from pydantic import BaseModel
from starlette.responses import FileResponse
class Item(BaseModel):
id: str
value: str
app = FastAPI()
@app.get(
"/items/{item_id}",
response_model=Item,
responses={
200: {
"content": {"image/png": {}},
"description": "Return the JSON item or an image.",
}
},
)
async def read_item(item_id: str, img: bool = None):
if img:
return FileResponse("image.png", media_type="image/png")
else:
return {"id": "foo", "value": "there goes my hero"}

View File

@@ -0,0 +1,37 @@
from fastapi import FastAPI
from pydantic import BaseModel
from starlette.responses import JSONResponse
class Item(BaseModel):
id: str
value: str
class Message(BaseModel):
message: str
app = FastAPI()
@app.get(
"/items/{item_id}",
response_model=Item,
responses={
404: {"model": Message, "description": "The item was not found"},
200: {
"description": "Item requested by ID",
"content": {
"application/json": {
"example": {"id": "bar", "value": "The bar tenders"}
}
},
},
},
)
async def read_item(item_id: str):
if item_id == "foo":
return {"id": "foo", "value": "there goes my hero"}
else:
return JSONResponse(status_code=404, content={"message": "Item not found"})

View File

@@ -0,0 +1,30 @@
from fastapi import FastAPI
from pydantic import BaseModel
from starlette.responses import FileResponse
class Item(BaseModel):
id: str
value: str
responses = {
404: {"description": "Item not found"},
302: {"description": "The item was moved"},
403: {"description": "Not enough privileges"},
}
app = FastAPI()
@app.get(
"/items/{item_id}",
response_model=Item,
responses={**responses, 200: {"content": {"image/png": {}}}},
)
async def read_item(item_id: str, img: bool = None):
if img:
return FileResponse("image.png", media_type="image/png")
else:
return {"id": "foo", "value": "there goes my hero"}

View File

@@ -1,8 +1,6 @@
from fastapi import FastAPI
app = FastAPI(
title="My Super Project", version="2.5.0", openapi_url="/api/v1/openapi.json"
)
app = FastAPI(openapi_url="/api/v1/openapi.json")
@app.get("/items/")

View File

@@ -1,12 +1,6 @@
from fastapi import FastAPI
app = FastAPI(
title="My Super Project",
version="2.5.0",
openapi_url="/api/v1/openapi.json",
docs_url="/api/v1/docs",
redoc_url=None,
)
app = FastAPI(docs_url="/documentation", redoc_url=None)
@app.get("/items/")

View File

@@ -0,0 +1,65 @@
from typing import List
import databases
import sqlalchemy
from fastapi import FastAPI
from pydantic import BaseModel
# SQLAlchemy specific code, as with any other app
DATABASE_URL = "sqlite:///./test.db"
# DATABASE_URL = "postgresql://user:password@postgresserver/db"
database = databases.Database(DATABASE_URL)
metadata = sqlalchemy.MetaData()
notes = sqlalchemy.Table(
"notes",
metadata,
sqlalchemy.Column("id", sqlalchemy.Integer, primary_key=True),
sqlalchemy.Column("text", sqlalchemy.String),
sqlalchemy.Column("completed", sqlalchemy.Boolean),
)
engine = sqlalchemy.create_engine(
DATABASE_URL, connect_args={"check_same_thread": False}
)
metadata.create_all(engine)
class NoteIn(BaseModel):
text: str
completed: bool
class Note(BaseModel):
id: int
text: str
completed: bool
app = FastAPI()
@app.on_event("startup")
async def startup():
await database.connect()
@app.on_event("shutdown")
async def shutdown():
await database.disconnect()
@app.get("/notes/", response_model=List[Note])
async def read_notes():
query = notes.select()
return await database.fetch_all(query)
@app.post("/notes/", response_model=Note)
async def create_note(note: NoteIn):
query = notes.insert().values(text=note.text, completed=note.completed)
last_record_id = await database.execute(query)
return {**note.dict(), "id": last_record_id}

View File

@@ -0,0 +1,15 @@
from fastapi import BackgroundTasks, FastAPI
app = FastAPI()
def write_notification(email: str, message=""):
with open("log.txt", mode="w") as email_file:
content = f"notification for {email}: {message}"
email_file.write(content)
@app.post("/send-notification/{email}")
async def send_notification(email: str, background_tasks: BackgroundTasks):
background_tasks.add_task(write_notification, email, message="some notification")
return {"message": "Notification sent in the background"}

View File

@@ -0,0 +1,24 @@
from fastapi import BackgroundTasks, Depends, FastAPI
app = FastAPI()
def write_log(message: str):
with open("log.txt", mode="a") as log:
log.write(message)
def get_query(background_tasks: BackgroundTasks, q: str = None):
if q:
message = f"found query: {q}\n"
background_tasks.add_task(write_log, message)
return q
@app.post("/send-notification/{email}")
async def send_notification(
email: str, background_tasks: BackgroundTasks, q: str = Depends(get_query)
):
message = f"message to {email}\n"
background_tasks.add_task(write_log, message)
return {"message": "Message sent"}

View File

@@ -1,9 +1,13 @@
from fastapi import FastAPI
from .routers.items import router as items_router
from .routers.users import router as users_router
from .routers import items, users
app = FastAPI()
app.include_router(users_router)
app.include_router(items_router, prefix="/items")
app.include_router(users.router)
app.include_router(
items.router,
prefix="/items",
tags=["items"],
responses={404: {"description": "Not found"}},
)

View File

@@ -1,13 +1,24 @@
from fastapi import APIRouter
from fastapi import APIRouter, HTTPException
router = APIRouter()
@router.get("/", tags=["items"])
@router.get("/")
async def read_items():
return [{"name": "Item Foo"}, {"name": "item Bar"}]
@router.get("/{item_id}", tags=["items"])
@router.get("/{item_id}")
async def read_item(item_id: str):
return {"name": "Fake Specific Item", "item_id": item_id}
@router.put(
"/{item_id}",
tags=["custom"],
responses={403: {"description": "Operation forbidden"}},
)
async def update_item(item_id: str):
if item_id != "foo":
raise HTTPException(status_code=403, detail="You can only update the item: foo")
return {"item_id": item_id, "name": "The Fighters"}

View File

@@ -1,8 +1,7 @@
from typing import Set
from fastapi import FastAPI
from pydantic import BaseModel
from pydantic.types import UrlStr
from pydantic import BaseModel, UrlStr
app = FastAPI()

View File

@@ -1,8 +1,7 @@
from typing import List, Set
from fastapi import FastAPI
from pydantic import BaseModel
from pydantic.types import UrlStr
from pydantic import BaseModel, UrlStr
app = FastAPI()
@@ -18,7 +17,7 @@ class Item(BaseModel):
price: float
tax: float = None
tags: Set[str] = []
image: List[Image] = None
images: List[Image] = None
@app.put("/items/{item_id}")

View File

@@ -1,8 +1,7 @@
from typing import List, Set
from fastapi import FastAPI
from pydantic import BaseModel
from pydantic.types import UrlStr
from pydantic import BaseModel, UrlStr
app = FastAPI()
@@ -18,7 +17,7 @@ class Item(BaseModel):
price: float
tax: float = None
tags: Set[str] = []
image: List[Image] = None
images: List[Image] = None
class Offer(BaseModel):

View File

@@ -1,8 +1,7 @@
from typing import List
from fastapi import FastAPI
from pydantic import BaseModel
from pydantic.types import UrlStr
from pydantic import BaseModel, UrlStr
app = FastAPI()

View File

@@ -0,0 +1,15 @@
import uvicorn
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def root():
a = "a"
b = "b" + a
return {"hello world": b}
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)

View File

@@ -0,0 +1,16 @@
from fastapi import FastAPI
app = FastAPI()
items = {}
@app.on_event("startup")
async def startup_event():
items["foo"] = {"name": "Fighters"}
items["bar"] = {"name": "Tenders"}
@app.get("/items/{item_id}")
async def read_item(item_id: str):
return items[item_id]

View File

@@ -0,0 +1,14 @@
from fastapi import FastAPI
app = FastAPI()
@app.on_event("shutdown")
def shutdown_event():
with open("log.txt", mode="a") as log:
log.write("Application shutdown")
@app.get("/items/")
async def read_items():
return [{"name": "Foo"}]

View File

@@ -0,0 +1,28 @@
from fastapi import FastAPI
from fastapi.openapi.utils import get_openapi
app = FastAPI()
@app.get("/items/")
async def read_items():
return [{"name": "Foo"}]
def custom_openapi():
if app.openapi_schema:
return app.openapi_schema
openapi_schema = get_openapi(
title="Custom title",
version="2.5.0",
description="This is a very custom OpenAPI schema",
routes=app.routes,
)
openapi_schema["info"]["x-logo"] = {
"url": "https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png"
}
app.openapi_schema = openapi_schema
return app.openapi_schema
app.openapi = custom_openapi

View File

@@ -0,0 +1,35 @@
from typing import Union
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class BaseItem(BaseModel):
description: str
type: str
class CarItem(BaseItem):
type = "car"
class PlaneItem(BaseItem):
type = "plane"
size: int
items = {
"item1": {"description": "All my friends drive a low rider", "type": "car"},
"item2": {
"description": "Music is my aeroplane, it's my aeroplane",
"type": "plane",
"size": 5,
},
}
@app.get("/items/{item_id}", response_model=Union[PlaneItem, CarItem])
async def read_item(item_id: str):
return items[item_id]

View File

@@ -0,0 +1,22 @@
from typing import List
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str
items = [
{"name": "Foo", "description": "There comes my hero"},
{"name": "Red", "description": "It's my aeroplane"},
]
@app.get("/items/", response_model=List[Item])
async def read_items():
return items

View File

@@ -0,0 +1,10 @@
from typing import List
from fastapi import FastAPI, Header
app = FastAPI()
@app.get("/items/")
async def read_items(x_token: List[str] = Header(None)):
return {"X-Token values": x_token}

View 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(None)):
query_items = {"q": q}
return query_items

View File

@@ -1,8 +1,13 @@
from fastapi import FastAPI, File
from fastapi import FastAPI, File, UploadFile
app = FastAPI()
@app.post("/files/")
async def create_file(*, file: bytes = File(...)):
async def create_file(file: bytes = File(...)):
return {"file_size": len(file)}
@app.post("/uploadfile/")
async def create_upload_file(file: UploadFile = File(...)):
return {"filename": file.filename}

View File

@@ -1,8 +1,14 @@
from fastapi import FastAPI, File, Form
from fastapi import FastAPI, File, Form, UploadFile
app = FastAPI()
@app.post("/files/")
async def create_file(*, file: bytes = File(...), token: str = Form(...)):
return {"file_size": len(file), "token": token}
async def create_file(
file: bytes = File(...), fileb: UploadFile = File(...), token: str = Form(...)
):
return {
"file_size": len(file),
"token": token,
"fileb_content_type": fileb.content_type,
}

View File

@@ -3,6 +3,7 @@ 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"
@@ -66,7 +67,10 @@ def read_user(user_id: int, db: Session = Depends(get_db)):
@app.middleware("http")
async def db_session_middleware(request: Request, call_next):
request.state.db = SessionLocal()
response = await call_next(request)
request.state.db.close()
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

View File

@@ -0,0 +1,53 @@
from fastapi import FastAPI
from starlette.responses import HTMLResponse
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)">
<input type="text" id="messageText" autocomplete="off"/>
<button>Send</button>
</form>
<ul id='messages'>
</ul>
<script>
var ws = new WebSocket("ws://localhost:8000/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)
@app.websocket_route("/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()

View File

@@ -0,0 +1,235 @@
!!! warning
This is a rather advanced topic.
If you are starting with **FastAPI**, you might not need this.
You can declare additional responses, with additional status codes, media types, descriptions, etc.
Those additional responses will be included in the OpenAPI schema, so they will also appear in the API docs.
But for those additional responses you have to make sure you return a `Response` like `JSONResponse` directly, with your status code and content.
## Additional Response with `model`
You can pass to your *path operation decorators* a parameter `responses`.
It receives a `dict`, the keys are status codes for each response, like `200`, and the values are other `dict`s with the information for each of them.
Each of those response `dict`s can have a key `model`, containing a Pydantic model, just like `response_model`.
**FastAPI** will take that model, generate its JSON Schema and include it in the correct place in OpenAPI.
For example, to declare another response with a status code `404` and a Pydantic model `Message`, you can write:
```Python hl_lines="18 23"
{!./src/additional_responses/tutorial001.py!}
```
!!! note
Have in mind that you have to return the `JSONResponse` directly.
!!! info
The `model` key is not part of OpenAPI.
**FastAPI** will take the Pydantic model from there, generate the `JSON Schema`, and put it in the correct place.
The correct place is:
* In the key `content`, that has as value another JSON object (`dict`) that contains:
* A key with the media type, e.g. `application/json`, that contains as value another JSON object, that contains:
* A key `schema`, that has as the value the JSON Schema from the model, here's the correct place.
* **FastAPI** adds a reference here to the global JSON Schemas in another place in your OpenAPI instead of including it directly. This way, other applications and clients can use those JSON Schemas directly, provide better code generation tools, etc.
The generated responses in the OpenAPI for this *path operation* will be:
```JSON hl_lines="3 4 5 6 7 8 9 10 11 12"
{
"responses": {
"404": {
"description": "Additional Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Message"
}
}
}
},
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Item"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
```
The schemas are referenced to another place inside the OpenAPI schema:
```JSON hl_lines="4 5 6 7 8 9 10 11 12 13 14 15 16"
{
"components": {
"schemas": {
"Message": {
"title": "Message",
"required": [
"message"
],
"type": "object",
"properties": {
"message": {
"title": "Message",
"type": "string"
}
}
},
"Item": {
"title": "Item",
"required": [
"id",
"value"
],
"type": "object",
"properties": {
"id": {
"title": "Id",
"type": "string"
},
"value": {
"title": "Value",
"type": "string"
}
}
},
"ValidationError": {
"title": "ValidationError",
"required": [
"loc",
"msg",
"type"
],
"type": "object",
"properties": {
"loc": {
"title": "Location",
"type": "array",
"items": {
"type": "string"
}
},
"msg": {
"title": "Message",
"type": "string"
},
"type": {
"title": "Error Type",
"type": "string"
}
}
},
"HTTPValidationError": {
"title": "HTTPValidationError",
"type": "object",
"properties": {
"detail": {
"title": "Detail",
"type": "array",
"items": {
"$ref": "#/components/schemas/ValidationError"
}
}
}
}
}
}
}
```
## Additional media types for the main response
You can use this same `responses` parameter to add different media types for the same main response.
For example, you can add an additional media type of `image/png`, declaring that your *path operation* can return a JSON object (with media type `application/json`) or a PNG image:
```Python hl_lines="17 18 19 20 21 22 23 24 28"
{!./src/additional_responses/tutorial002.py!}
```
!!! note
Notice that you have to return the image using a `FileResponse` directly.
## Combining information
You can also combine response information from multiple places, including the `response_model`, `status_code`, and `responses` parameters.
You can declare a `response_model`, using the default status code `200` (or a custom one if you need), and then declare additional information for that same response in `responses`, directly in the OpenAPI schema.
**FastAPI** will keep the additional information from `responses`, and combine it with the JSON Schema from your model.
For example, you can declare a response with a status code `404` that uses a Pydantic model and has a custom `description`.
And a response with a status code `200` that uses your `response_model`, but includes a custom `example`:
```Python hl_lines="20 21 22 23 24 25 26 27 28 29 30 31"
{!./src/additional_responses/tutorial003.py!}
```
It will all be combined and included in your OpenAPI, and shown in the API docs:
<img src="/img/tutorial/additional-responses/image01.png">
## Combine predefined responses and custom ones
You might want to have some predefined responses that apply to many *path operations*, but you want to combine them with custom responses needed by each *path operation*.
For those cases, you can use the Python technique of "unpacking" a `dict` with `**dict_to_unpack`:
```Python
old_dict = {
"old key": "old value",
"second old key": "second old value",
}
new_dict = {**old_dict, "new key": "new value"}
```
Here, `new_dict` will contain all the key-value pairs from `old_dict` plus the new key-value pair:
```Python
{
"old key": "old value",
"second old key": "second old value",
"new key": "new value",
}
```
You can use that technique to re-use some predefined responses in your *path operations* and combine them with additional custom ones.
For example:
```Python hl_lines="11 12 13 14 15 24"
{!./src/additional_responses/tutorial004.py!}
```
## More information about OpenAPI responses
To see what exactly you can include in the responses, you can check these sections in the OpenAPI specification:
* <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#responsesObject" target="_blank">OpenAPI Responses Object</a>, it includes the `Response Object`.
* <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#responseObject" target="_blank">OpenAPI Response Object</a>, you can include anything from this directly in each response inside your `responses` parameter. Including `description`, `headers`, `content` (inside of this is that you declare different media types and JSON Schemas), and `links`.

View File

@@ -1,13 +1,51 @@
Coming soon...
There are several things that you can configure in your FastAPI application.
```Python
## Title, description, and version
You can set the:
* Title: used as your API's title/name, in OpenAPI and the automatic API docs UIs.
* Description: the description of your API, in OpenAPI and the automatic API docs UIs.
* Version: the version of your API, e.g. `v2` or `2.5.0`.
* Useful for example if you had a previous version of the application, also using OpenAPI.
To set them, use the parameters `title`, `description`, and `version`:
```Python hl_lines="4 5 6"
{!./src/application_configuration/tutorial001.py!}
```
```Python
With this configuration, the automatic API docs would look like:
<img src="/img/tutorial/application-configuration/image01.png">
## OpenAPI URL
By default, the OpenAPI schema is served at `/openapi.json`.
But you can configure it with the parameter `openapi_url`.
For example, to set it to be served at `/api/v1/openapi.json`:
```Python hl_lines="3"
{!./src/application_configuration/tutorial002.py!}
```
```Python
If you want to disable the OpenAPI schema completely you can set `openapi_url=None`.
## Docs URLs
You can configure the two documentation user interfaces included:
* **Swagger UI**: served at `/docs`.
* You can set its URL with the parameter `docs_url`.
* You can disable it by setting `docs_url=None`.
* ReDoc: served at `/redoc`.
* You can set its URL with the parameter `redoc_url`.
* You can disable it by setting `redoc_url=None`.
For example, to set Swagger UI to be served at `/documentation` and disable ReDoc:
```Python hl_lines="3"
{!./src/application_configuration/tutorial003.py!}
```

View File

@@ -0,0 +1,160 @@
You can also use <a href="https://github.com/encode/databases" target="_blank">`encode/databases`</a> with **FastAPI** to connect to databases using `async` and `await`.
It is compatible with:
* PostgreSQL
* MySQL
* SQLite
In this example, we'll use **SQLite**, because it uses a single file and Python has integrated support. So, you can copy this example and run it as is.
Later, for your production application, you might want to use a database server like **PostgreSQL**.
!!! tip
You could adopt ideas from the previous section about <a href="/tutorial/sql-databases/" target="_blank">SQLAlchemy ORM</a>, like using utility functions to perform operations in the database, independent of your **FastAPI** code.
This section doesn't apply those ideas, to be equivalent to the counterpart in <a href="https://www.starlette.io/database/" target="_blank">Starlette</a>.
## Import and set up `SQLAlchemy`
* Import `SQLAlchemy`.
* Create a `metadata` object.
* Create a table `notes` using the `metadata` object.
```Python hl_lines="4 14 16 17 18 19 20 21 22"
{!./src/async_sql_databases/tutorial001.py!}
```
!!! tip
Notice that all this code is pure SQLAlchemy Core.
`databases` is not doing anything here yet.
## Import and set up `databases`
* Import `databases`.
* Create a `DATABASE_URL`.
* Create a `database` object.
```Python hl_lines="3 9 12"
{!./src/async_sql_databases/tutorial001.py!}
```
!!! tip
If you where connecting to a different database (e.g. PostgreSQL), you would need to change the `DATABASE_URL`.
## Create the tables
In this case, we are creating the tables in the same Python file, but in production, you would probably want to create them with Alembic, integrated with migrations, etc.
Here, this section would run directly, right before starting your **FastAPI** application.
* Create an `engine`.
* Create all the tables from the `metadata` object.
```Python hl_lines="25 26 27 28"
{!./src/async_sql_databases/tutorial001.py!}
```
## Create models
Create Pydantic models for:
* Notes to be created (`NoteIn`).
* Notes to be returned (`Note`).
```Python hl_lines="31 32 33 36 37 38 39"
{!./src/async_sql_databases/tutorial001.py!}
```
By creating these Pydantic models, the input data will be validated, serialized (converted), and annotated (documented).
So, you will be able to see it all in the interactive API docs.
## Connect and disconnect
* Create your `FastAPI` application.
* Create event handlers to connect and disconnect from the database.
```Python hl_lines="42 45 46 47 50 51 52"
{!./src/async_sql_databases/tutorial001.py!}
```
## Read notes
Create the *path operation function* to read notes:
```Python hl_lines="55 56 57 58"
{!./src/async_sql_databases/tutorial001.py!}
```
!!! Note
Notice that as we communicate with the database using `await`, the *path operation function* is declared with `async`.
### Notice the `response_model=List[Note]`
It uses `typing.List`.
That documents (and validates, serializes, filters) the output data, as a `list` of `Note`s.
## Create notes
Create the *path operation function* to create notes:
```Python hl_lines="61 62 63 64 65"
{!./src/async_sql_databases/tutorial001.py!}
```
!!! Note
Notice that as we communicate with the database using `await`, the *path operation function* is declared with `async`.
### About `{**note.dict(), "id": last_record_id}`
`note` is a Pydantic `Note` object.
`note.dict()` returns a `dict` with its data, something like:
```Python
{
"text": "Some note",
"completed": False,
}
```
but it doesn't have the `id` field.
So we create a new `dict`, that contains the key-value pairs from `note.dict()` with:
```Python
{**note.dict()}
```
`**note.dict()` "unpacks" the key value pairs directly, so, `{**note.dict()}` would be, more or less, a copy of `note.dict()`.
And then, we extend that copy `dict`, adding another key-value pair: `"id": last_record_id`:
```Python
{**note.dict(), "id": last_record_id}
```
So, the final result returned would be something like:
```Python
{
"id": 1,
"text": "Some note",
"completed": False,
}
```
## Check it
You can copy this code as is, and see the docs at <a href="http://127.0.0.1:8000/docs" target="_blank">http://127.0.0.1:8000/docs</a>.
There you can see all your API documented and interact with it:
<img src="/img/tutorial/async-sql-databases/image01.png">
## More info
You can read more about <a href="https://github.com/encode/databases" target="_blank">`encode/databases` at its GitHub page</a>.

View File

@@ -0,0 +1,96 @@
You can define background tasks to be run *after* returning a response.
This is useful for operations that need to happen after a request, but that the client doesn't really have to be waiting for the operation to complete before receiving his response.
This includes, for example:
* Email notifications sent after performing an action:
* As connecting to an email server and sending an email tends to be "slow" (several seconds), you can return the response right away and send the email notification in the background.
* Processing data:
* For example, let's say you receive a file that must go through a slow process, you can return a response of "Accepted" (HTTP 202) and process it in the background.
## Using `BackgroundTasks`
First, import `BackgroundTasks` and define a parameter in your *path operation function* with a type declaration of `BackgroundTasks`:
```Python hl_lines="1 13"
{!./src/background_tasks/tutorial001.py!}
```
**FastAPI** will create the object of type `BackgroundTasks` for you and pass it as that parameter.
!!! tip
You declare a parameter of `BackgroundTasks` and use it in a very similar way as to when <a href="/tutorial/using-request-directly/" target="_blank">using the `Request` directly</a>.
## Create a task function
Create a function to be run as the background task.
It is just a standard function that can receive parameters.
It can be an `async def` or normal `def` function, **FastAPI** will know how to handle it correctly.
In this case, the task function will write to a file (simulating sending an email).
And as the write operation doesn't use `async` and `await`, we define the function with normal `def`:
```Python hl_lines="6 7 8 9"
{!./src/background_tasks/tutorial001.py!}
```
## Add the background task
Inside of your *path operation function*, pass your task function to the *background tasks* object with the method `.add_task()`:
```Python hl_lines="14"
{!./src/background_tasks/tutorial001.py!}
```
`.add_task()` receives as arguments:
* A task function to be run in the background (`write_notification`).
* Any sequence of arguments that should be passed to the task function in order (`email`).
* Any keyword arguments that should be passed to the task function (`message="some notification"`).
## Dependency Injection
Using `BackgroundTasks` also works with the dependency injection system, you can declare a parameter of type `BackgroundTasks` at multiple levels: in a *path operation function*, in a dependency (dependable), in a sub-dependency, etc.
**FastAPI** knows what to do in each case and how to re-use the same object, so that all the background tasks are merged together and are run in the background afterwards:
```Python hl_lines="11 14 20 23"
{!./src/background_tasks/tutorial002.py!}
```
In this example, the messages will be written to the `log.txt` file *after* the response is sent.
If there was a query in the request, it will be written to the log in a background task.
And then another background task generated at the *path operation function* will write a message using the `email` path parameter.
## Technical Details
The class `BackgroundTasks` comes directly from <a href="https://www.starlette.io/background/" target="_blank">`starlette.background`</a>.
It is imported/included directly into FastAPI so that you can import it from `fastapi` and avoid accidentally importing the alternative `BackgroundTask` (without the `s` at the end) from `starlette.background`.
By only using `BackgroundTasks` (and not `BackgroundTask`), it's then possible to use it as a *path operation function* parameter and have **FastAPI** handle the rest for you, just like when using the `Request` object directly.
It's still possible to use `BackgroundTask` alone in FastAPI, but you have to create the object in your code and return a Starlette `Response` including it.
You can see more details in <a href="https://www.starlette.io/background/" target="_blank">Starlette's official docs for Background Tasks</a>.
## Caveat
If you need to perform heavy background computation and you don't necessarily need it to be run by the same process (for example, you don't need to share memory, variables, etc), you might benefit from using other bigger tools like <a href="http://www.celeryproject.org/" target="_blank">Celery</a>.
They tend to require more complex configurations, a message/job queue manager, like RabbitMQ or Redis, but they allow you to run background tasks in multiple processes, and especially, in multiple servers.
To see an example, check the <a href="https://fastapi.tiangolo.com/project-generation/" target="_blank">Project Generators</a>, they all include Celery already configured.
But if you need to access variables and objects from the same **FastAPI** app, or you need to perform small background tasks (like sending an email notification), you can simply just use `BackgroundTasks`.
## Recap
Import and use `BackgroundTasks` with parameters in *path operation functions* and dependencies to add background tasks.

View File

@@ -2,6 +2,8 @@ If you are building an application or a web API, it's rarely the case that you c
**FastAPI** provides a convenience tool to structure your application while keeping all the flexibility.
!!! info
If you come from Flask, this would be the equivalent of Flask's Blueprints.
## An example file structure
@@ -99,12 +101,21 @@ It's all the same structure as with `app/routers/users.py`.
But let's say that this time we are more lazy.
And we don't want to have to explicitly type `/items/` in every path operation, we can do it later:
And we don't want to have to explicitly type `/items/` and `tags=["items"]` in every *path operation* (we will be able to do it later):
```Python hl_lines="6 11 16"
```Python hl_lines="6 11"
{!./src/bigger_applications/app/routers/items.py!}
```
### Add some custom `tags` and `responses`
We are not adding the prefix `/items/` nor the `tags=["items"]` to add them later.
But we can add custom `tags` and `responses` that will be applied to a specific *path operation*:
```Python hl_lines="18 19"
{!./src/bigger_applications/app/routers/items.py!}
```
## The main `FastAPI`
@@ -118,17 +129,17 @@ This will be the main file in your application that ties everything together.
You import and create a `FastAPI` class as normally:
```Python hl_lines="1 6"
```Python hl_lines="1 5"
{!./src/bigger_applications/app/main.py!}
```
### Import the `APIRouter`
But this time we are not adding path operations directly with the `FastAPI` `app`.
But this time we are not adding *path operations* directly with the `FastAPI` `app`.
We import the `APIRouter`s from the other files:
We import the other submodules that have `APIRouter`s:
```Python hl_lines="3 4"
```Python hl_lines="3"
{!./src/bigger_applications/app/main.py!}
```
@@ -140,22 +151,21 @@ As the file `app/routers/items.py` is part of the same Python package, we can im
The section:
```Python
from .routers.items import router
from .routers import items, users
```
Means:
* Starting in the same package that this module (the file `app/main.py`) lives in (the directory `app/`)...
* look for the subpackage `routers` (the directory at `app/routers/`)...
* and from it, the submodule `items` (the file at `app/routers/items.py`)...
* and from that submodule, import the variable `router`.
* and from it, import the submodule `items` (the file at `app/routers/items.py`) and `users` (the file at `app/routers/users.py`)...
The variable `router` is the same one we created in the file `app/routers/items.py`. It's an `APIRouter`.
The module `items` will have a variable `router` (`items.router`). This is the same one we created in the file `app/routers/items.py`. It's an `APIRouter`. The same for the module `users`.
We could also import it like:
We could also import them like:
```Python
from app.routers.items import router
from app.routers import items, users
```
!!! info
@@ -168,40 +178,43 @@ from app.routers.items import router
### Avoid name collisions
We are importing a variable named `router` from the submodule `items`.
We are importing the submodule `items` directly, instead of importing just its variable `router`.
But we also have another variable named `router` in the submodule `users`.
This is because we also have another variable named `router` in the submodule `users`.
If we import one after the other, like:
If we had imported one after the other, like:
```Python
from .routers.items import router
from .routers.users import router
```
The `router` from `users` will overwrite the one form `items` and we won't be able to use them at the same time.
The `router` from `users` would overwrite the one from `items` and we wouldn't be able to use them at the same time.
So, to be able to use both of them in the same file, we rename them while importing them using `as`:
So, to be able to use both of them in the same file, we import the submodules directly:
```Python hl_lines="3 4"
```Python hl_lines="3"
{!./src/bigger_applications/app/main.py!}
```
### Include an `APIRouter`
Now, let's include the router from the submodule `users`, now in the variable `users_router`:
Now, let's include the `router` from the submodule `users`:
```Python hl_lines="8"
```Python hl_lines="7"
{!./src/bigger_applications/app/main.py!}
```
!!! info
`users.router` contains the `APIRouter` inside of the file `app/routers/users.py`.
With `app.include_router()` we can add an `APIRouter` to the main `FastAPI` application.
It will include all the routes from that router as part of it.
!!! note "Technical Details"
It will actually internally create a path operation for each path operation that was declared in the `APIRouter`.
It will actually internally create a *path operation* for each *path operation* that was declared in the `APIRouter`.
So, behind the scenes, it will actually work as if everything was the same single app.
@@ -214,27 +227,31 @@ It will include all the routes from that router as part of it.
So it won't affect performance.
### Include an `APIRouter` with a prefix
### Include an `APIRouter` with a `prefix`, `tags`, and `responses`
Now, let's include the router form the `items` submodule, now in the variable `items_router`.
Now, let's include the router form the `items` submodule.
But, remember that we were lazy and didn't add `/items/` to all the path operations?
But, remember that we were lazy and didn't add `/items/` nor `tags` to all the *path operations*?
We can add a prefix to all the path operations using the parameter `prefix` of `app.include_router()`.
As the path of each path operation has to start with `/`, like in:
```Python hl_lines="1"
@router.get("/{item_id}", tags=["items"])
@router.get("/{item_id}")
async def read_item(item_id: str):
...
```
...the prefix must not include a final `/`.
So, the prefix in this case would be `/items`:
So, the prefix in this case would be `/items`.
```Python hl_lines="9"
We can also add a list of `tags` that will be applied to all the *path operations* included in this router.
And we can add predefined `responses` that will be included in all the *path operations* too.
```Python hl_lines="8 9 10 11 12 13"
{!./src/bigger_applications/app/main.py!}
```
@@ -245,8 +262,18 @@ The end result is that the item paths are now:
...as we intended.
They will be marked with a list of tags that contain a single string `"items"`.
The *path operation* that declared a `"custom"` tag will have both tags, `items` and `custom`.
These "tags" are especially useful for the automatic interactive documentation systems (using OpenAPI).
And all of them will include the the predefined `responses`.
The *path operation* that declared a custom `403` response will have both the predefined responses (`404`) and the `403` declared in it directly.
!!! check
The `prefix` parameter is (as in many other cases) just a feature from **FastAPI** to help you avoid code duplication.
The `prefix`, `tags`, and `responses` parameters are (as in many other cases) just a feature from **FastAPI** to help you avoid code duplication.
!!! tip
@@ -274,11 +301,11 @@ The end result is that the item paths are now:
Now, run `uvicorn`, using the module `app.main` and the variable `app`:
```bash
uvicorn app.main:app --debug
uvicorn app.main:app --reload
```
And open the docs at <a href="http://127.0.0.1:8000/docs" target="_blank">http://127.0.0.1:8000/docs</a>.
You will see the automatic API docs, including the paths from all the submodules:
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">

View File

@@ -120,7 +120,7 @@ To see all the options you have, checkout the docs for <a href="https://pydantic
For example, as in the `Image` model we have a `url` field, we can declare it to be instead of a `str`, a Pydantic's `UrlStr`:
```Python hl_lines="5 11"
```Python hl_lines="4 10"
{!./src/body_nested_models/tutorial005.py!}
```
@@ -130,7 +130,7 @@ The string will be checked to be a valid URL, and documented in JSON Schema / Op
You can also use Pydantic models as subtypes of `list`, `set`, etc:
```Python hl_lines="21"
```Python hl_lines="20"
{!./src/body_nested_models/tutorial006.py!}
```
@@ -167,7 +167,7 @@ This will expect (convert, validate, document, etc) a JSON body like:
You can define arbitrarily deeply nested models:
```Python hl_lines="10 15 21 24 28"
```Python hl_lines="9 14 20 23 27"
{!./src/body_nested_models/tutorial007.py!}
```
@@ -184,7 +184,7 @@ images: List[Image]
as in:
```Python hl_lines="16"
```Python hl_lines="15"
{!./src/body_nested_models/tutorial008.py!}
```

View File

@@ -0,0 +1,87 @@
You can connect the debugger in your editor, for example with Visual Studio Code or PyCharm.
## Call `uvicorn`
In your FastAPI application, import and run `uvicorn` directly:
```Python hl_lines="1 15"
{!./src/debugging/tutorial001.py!}
```
### About `__name__ == "__main__"`
The main purpose of the `__name__ == "__main__"` is to have some code that is executed when your file is called with:
```bash
python myapp.py
```
but is not called when another file imports it, like in:
```Python
from myapp import app
```
#### More details
Let's say your file is named `myapp.py`.
If you run it with:
```bash
python myapp.py
```
then the internal variable `__name__` in your file, created automatically by Python, will have as value the string `"__main__"`.
So, the section:
```Python
uvicorn.run(app, host="0.0.0.0", port=8000)
```
will run.
---
This won't happen if you import that module (file).
So, if you have another file `importer.py` with:
```Python
from myapp import app
# Some more code
```
in that case, the automatic variable inside of `myapp.py` will not have the variable `__name__` with a value of `"__main__"`.
So, the line:
```Python
uvicorn.run(app, host="0.0.0.0", port=8000)
```
will not be executed.
!!! info
For more information, check <a href="https://docs.python.org/3/library/__main__.html" target="_blank">the official Python docs</a>.
## Run your code with your debugger
Because you are running the Uvicorn server directly from your code, you can call your Python program (your FastAPI application) directly form the debugger.
---
For example, in Visual Studio Code, you can:
* Go to the "Debug" panel.
* "Add configuration...".
* Select "Python"
* Run the debugger with the option "`Python: Current File (Integrated Terminal)`".
It will then start the server with your **FastAPI** code, stop at your breakpoints, etc.
Here's how it might look:
<img src="/img/tutorial/debugging/image01.png">

43
docs/tutorial/events.md Normal file
View File

@@ -0,0 +1,43 @@
You can define event handlers (functions) that need to be executed before the application starts up, or when the application is shutting down.
These functions can be declared with `async def` or normal `def`.
## `startup` event
To add a function that should be run before the application starts, declare it with the event `"startup"`:
```Python hl_lines="8"
{!./src/events/tutorial001.py!}
```
In this case, the `startup` event handler function will initialize the items "database" (just a `dict`) with some values.
You can add more than one event handler function.
And your application won't start receiving requests until all the `startup` event handlers have completed.
## `shutdown` event
To add a function that should be run when the application is shutting down, declare it with the event `"shutdown"`:
```Python hl_lines="6"
{!./src/events/tutorial002.py!}
```
Here, the `shutdown` event handler function will write a text line `"Application shutdown"` to a file `log.txt`.
!!! info
In the `open()` function, the `mode="a"` means "append", so, the line will be added after whatever is on that file, without overwriting the previous contents.
!!! tip
Notice that in this case we are using a standard Python `open()` function that interacts with a file.
So, it involves I/O (input/output), that requires "waiting" for things to be written to disk.
But `open()` doesn't use `async` and `await`.
So, we declare the event handler function with standard `def` instead of `async def`.
!!! info
You can read more about these event handlers in <a href="https://www.starlette.io/events/" target="_blank">Starlette's Events' docs</a>.

View File

@@ -0,0 +1,90 @@
!!! warning
This is a rather advanced feature. You probably can skip it.
If you are just following the tutorial - user guide, you can probably skip this section.
If you already know that you need to modify the generated OpenAPI schema, continue reading.
There are some cases where you might need to modify the generated OpenAPI schema.
In this section you will see how.
## The normal process
The normal (default) process, is as follows.
A `FastAPI` application (instance) has an `.openapi()` method that is expected to return the OpenAPI schema.
As part of the application object creation, a *path operation* for `/openapi.json` (or for whatever you set your `openapi_url`) is registered.
It just returns a JSON response with the result of the application's `.openapi()` method.
By default, what the method `.openapi()` does is check the property `.openapi_schema` to see if it has contents and return them.
If it doesn't, it generates them using the utility function at `fastapi.openapi.utils.get_openapi`.
And that function `get_openapi()` receives as parameters:
* `title`: The OpenAPI title, shown in the docs.
* `version`: The version of your API, e.g. `2.5.0`.
* `openapi_version`: The version of the OpenAPI specification used. By default, the latest: `3.0.2`.
* `description`: The description of your API.
* `routes`: A list of routes, these are each of the registered *path operations*. They are taken from `app.routes`.
* `openapi_prefix`: The URL prefix to be used in your OpenAPI.
## Overriding the defaults
Using the information above, you can use the same utility function to generate the OpenAPI schema and override each part that you need.
For example, let's add <a href="https://github.com/Rebilly/ReDoc/blob/master/docs/redoc-vendor-extensions.md#x-logo" target="_blank">ReDoc's OpenAPI extension to include a custom logo</a>.
### Normal **FastAPI**
First, write all your **FastAPI** application as normally:
```Python hl_lines="1 4 7 8 9"
{!./src/extending_openapi/tutorial001.py!}
```
### Generate the OpenAPI schema
Then, use the same utility function to generate the OpenAPI schema, inside a `custom_openapi()` function:
```Python hl_lines="2 15 16 17 18 19 20"
{!./src/extending_openapi/tutorial001.py!}
```
### Modify the OpenAPI schema
Now you can add the ReDoc extension, adding a custom `x-logo` to the `info` "object" in the OpenAPI schema:
```Python hl_lines="21 22 23"
{!./src/extending_openapi/tutorial001.py!}
```
### Cache the OpenAPI schema
You can use the property `.openapi_schema` as a "cache", to store your generated schema.
That way, your application won't have to generate the schema every time a user opens your API docs.
It will be generated only once, and then the same cached schema will be used for the next requests.
```Python hl_lines="13 14 24 25"
{!./src/extending_openapi/tutorial001.py!}
```
### Override the method
Now you can replace the `.openapi()` method with your new function.
```Python hl_lines="28"
{!./src/extending_openapi/tutorial001.py!}
```
### Check it
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">

View File

@@ -109,7 +109,7 @@ UserInDB(**user_in.dict())
So, we get a Pydantic model from the data in another Pydantic model.
#### Unrapping a `dict` and extra keywords
#### Unwrapping a `dict` and extra keywords
And then adding the extra keyword argument `hashed_password=hashed_password`, like in:
@@ -152,6 +152,28 @@ That way, we can declare just the differences between the models (with plaintext
{!./src/extra_models/tutorial002.py!}
```
## `Union` or `anyOf`
You can declare a response to be the `Union` of two types, that means, that the response would be any of the two.
It will be defined in OpenAPI with `anyOf`.
To do that, use the standard Python type hint <a href="https://docs.python.org/3/library/typing.html#typing.Union" target="_blank">`typing.Union`</a>:
```Python hl_lines="1 14 15 18 19 20 33"
{!./src/extra_models/tutorial003.py!}
```
## List of models
The same way, you can declare responses of lists of objects.
For that, use the standard Python `typing.List`:
```Python hl_lines="1 20"
{!./src/extra_models/tutorial004.py!}
```
## Recap
Use multiple Pydantic models and inherit freely for each case.

View File

@@ -9,7 +9,7 @@ Copy that to a file `main.py`.
Run the live server:
```bash
uvicorn main:app --debug
uvicorn main:app --reload
```
!!! note
@@ -17,7 +17,7 @@ uvicorn main:app --debug
* `main`: the file `main.py` (the Python "module").
* `app`: the object created inside of `main.py` with the line `app = FastAPI()`.
* `--debug`: make the server restart after code changes. Only use for development.
* `--reload`: make the server restart after code changes. Only use for development.
You will see an output like:
@@ -69,7 +69,7 @@ A "schema" is a definition or description of something. Not the code that implem
In this case, OpenAPI is a specification that dictates how to define a schema of your API.
This OpenAPI schema would include your API paths, the posible parameters they take, etc.
This OpenAPI schema would include your API paths, the possible parameters they take, etc.
#### Data "schema"
@@ -146,7 +146,7 @@ This will be the main point of interaction to create all your API.
This `app` is the same one referred by `uvicorn` in the command:
```bash
uvicorn main:app --debug
uvicorn main:app --reload
```
If you create your app like:
@@ -158,7 +158,7 @@ If you create your app like:
And put it in a file `main.py`, then you would call `uvicorn` like:
```bash
uvicorn main:my_awesome_api --debug
uvicorn main:my_awesome_api --reload
```
### Step 3: create a path operation
@@ -311,4 +311,4 @@ There are many other objects and models that will be automatically converted to
* Create an `app` instance.
* Write a **path operation decorator** (like `@app.get("/")`).
* Write a **path operation function** (like `def root(): ...` above).
* Run the debugging server (like `uvicorn main:app --debug`).
* Run the development server (like `uvicorn main:app --reload`).

View File

@@ -96,4 +96,4 @@ For example, you could override the default exception handler with:
!!! info
Note that in this example we set the exception handler with Starlette's `HTTPException` instead of FastAPI's `HTTPException`.
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 catched by your 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.

View File

@@ -47,6 +47,39 @@ If for some reason you need to disable automatic conversion of underscores to hy
!!! warning
Before setting `convert_underscores` to `False`, bear in mind that some HTTP proxies and servers disallow the usage of headers with underscores.
## Duplicate headers
It is possible to receive duplicate headers. That means, the same header with multiple values.
You can define those cases using a list in the type declaration.
You will receive all the values from the duplicate header as a Python `list`.
For example, to declare a header of `X-Token` that can appear more than once, you can write:
```Python hl_lines="9"
{!./src/header_params/tutorial003.py!}
```
If you communicate with that *path operation* sending two HTTP headers like:
```
X-Token: foo
X-Token: bar
```
The response would be like:
```JSON
{
"X-Token values": [
"bar",
"foo"
]
}
```
## Recap
Declare headers with `Header`, using the same common pattern as `Query`, `Path` and `Cookie`.

View File

@@ -1,6 +1,6 @@
This tutorial shows you how to use **FastAPI** with all its features, step by step.
Eeach section gradually builds on the previous ones, but it's structured to separate topics, so that you can go directly to any specific one to solve your specific API needs.
Each section gradually builds on the previous ones, but it's structured to separate topics, so that you can go directly to any specific one to solve your specific API needs.
It is also built to work as a future reference.
@@ -13,7 +13,7 @@ All the code blocks can be copied and used directly (they are actually tested Py
To run any of the examples, copy the code to a file `main.py`, and start `uvicorn` with:
```bash
uvicorn main:app --debug
uvicorn main:app --reload
```
It is **HIGHLY encouraged** that you write or copy the code, edit it and run it locally.

View File

@@ -1,4 +1,4 @@
**FastAPI** allows you to declare additonal information and validation for your parameters.
**FastAPI** allows you to declare additional information and validation for your parameters.
Let's take this application as example:
@@ -124,6 +124,43 @@ So, when you need to declare a value as required while using `Query`, you can us
This will let **FastAPI** know that this parameter is required.
## Query parameter list / multiple values
When you define a query parameter explicitly with `Query` you can also declare it to receive a list of values, or said in other way, to receive multiple values.
For example, to declare a query parameter `q` that can appear multiple times in the URL, you can write:
```Python hl_lines="9"
{!./src/query_params_str_validations/tutorial011.py!}
```
Then, with a URL like:
```
http://localhost:8000/items/?q=foo&q=bar
```
you would receive the multiple `q` *query parameters'* values (`foo` and `bar`) in a Python `list` inside your *path operation function*, in the *function parameter* `q`.
So, the response to that URL would be:
```JSON
{
"q": [
"foo",
"bar"
]
}
```
!!! 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">
## Declare more metadata
You can add more information about the parameter.

View File

@@ -2,7 +2,7 @@ You can define files to be uploaded by the client using `File`.
## Import `File`
Import `File` from `fastapi`:
Import `File` and `UploadFile` from `fastapi`:
```Python hl_lines="1"
{!./src/request_files/tutorial001.py!}
@@ -16,14 +16,78 @@ Create file parameters the same way you would for `Body` or `Form`:
{!./src/request_files/tutorial001.py!}
```
The files will be uploaded as form data and you will receive the contents as `bytes`.
!!! info
`File` is a class that inherits directly from `Form`.
!!! info
To declare File bodies, you need to use `File`, because otherwise the parameters would be interpreted as query parameters or body (JSON) parameters.
The files will be uploaded as "form data".
If you declare the type of your *path operation function* parameter as `bytes`, **FastAPI** will read the file for you and you will receive the contents as `bytes`.
Have in mind that this means that the whole contents will be stored in memory. This will work well for small files.
But there are several cases in where you might benefit from using `UploadFile`.
## `File` parameters with `UploadFile`
Define a `File` parameter with a type of `UploadFile`:
```Python hl_lines="12"
{!./src/request_files/tutorial001.py!}
```
Using `UploadFile` has several advantages over `bytes`:
* It uses a "spooled" file:
* A file stored in memory up to a maximum size limit, and after passing this limit it will be stored in disk.
* This means that it will work well for large files like images, videos, large binaries, etc. All without consuming all the memory.
* You can get metadata from the uploaded file.
* It has a <a href="https://docs.python.org/3/glossary.html#term-file-like-object" target="_blank">file-like</a> `async` interface.
* It exposes an actual Python <a href="https://docs.python.org/3/library/tempfile.html#tempfile.SpooledTemporaryFile" target="_blank">`SpooledTemporaryFile`</a> object that you can pass directly to other libraries that expect a file-like object.
### `UploadFile`
`UploadFile` has the following attributes:
* `filename`: A `str` with the original file name that was uploaded (e.g. `myimage.jpg`).
* `content_type`: A `str` with the content type (MIME type / media type) (e.g. `image/jpeg`).
* `file`: A <a href="https://docs.python.org/3/library/tempfile.html#tempfile.SpooledTemporaryFile" target="_blank">`SpooledTemporaryFile`</a> (a <a href="https://docs.python.org/3/glossary.html#term-file-like-object" target="_blank">file-like</a> object). This is the actual Python file that you can pass directly to other functions or libraries that expect a "file-like" object.
`UploadFile` has the following `async` methods. They all call the corresponding file methods underneath (using the internal `SpooledTemporaryFile`).
* `write(data)`: Writes `data` (`str` or `bytes`) to the file.
* `read(size)`: Reads `size` (`int`) bytes/characters of the file.
* `seek(offset)`: Goes to the byte position `offset` (`int`) in the file.
* E.g., `await myfile.seek(0)` would go to the start of the file.
* This is especially useful if you run `await myfile.read()` once and then need to read the contents again.
* `close()`: Closes the file.
As all these methods are `async` methods, you need to "await" them.
For example, inside of an `async` *path operation function* you can get the contents with:
```Python
contents = await myfile.read()
```
If you are inside of a normal `def` *path operation function*, you can access the `UploadFile.file` directly, for example:
```Python
contents = myfile.file.read()
```
!!! note "`async` Technical Details"
When you use the `async` methods, **FastAPI** runs the file methods in a threadpool and awaits for them.
!!! note "Starlette Technical Details"
**FastAPI**'s `UploadFile` inherits directly from **Starlette**'s `UploadFile`, but adds some necessary parts to make it compatible with **Pydantic** and the other parts of FastAPI.
## "Form Data"?
The way HTML forms (`<form></form>`) sends the data to the server normally uses a "special" encoding for that data, it's different from JSON.
@@ -35,7 +99,7 @@ The way HTML forms (`<form></form>`) sends the data to the server normally uses
But when the form includes files, it is encoded as `multipart/form-data`. If you use `File`, **FastAPI** will know it has to get the files from the correct part of the body.
If you want to read more about these encondings and form fields, head to the <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST" target="_blank"><abbr title="Mozilla Developer Network">MDN</abbr> web docs for <code>POST</code></a>.
If you want to read more about these encodings and form fields, head to the <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST" target="_blank"><abbr title="Mozilla Developer Network">MDN</abbr> web docs for <code>POST</code></a>.
!!! warning

View File

@@ -10,12 +10,14 @@ You can define files and form fields at the same time using `File` and `Form`.
Create file and form parameters the same way you would for `Body` or `Query`:
```Python hl_lines="7"
```Python hl_lines="8"
{!./src/request_forms_and_files/tutorial001.py!}
```
The files and form fields will be uploaded as form data and you will receive the files and form fields.
And you can declare some of the files as `bytes` and some as `UploadFile`.
!!! warning
You can declare multiple `File` and `Form` parameters in a path operation, but you can't also declare `Body` fields that you expect to receive as JSON, as the request will have the body encoded using `multipart/form-data` instead of `application/json`.

View File

@@ -39,7 +39,7 @@ The way HTML forms (`<form></form>`) sends the data to the server normally uses
But when the form includes files, it is encoded as `multipart/form-data`. You'll read about handling files in the next chapter.
If you want to read more about these encondings and form fields, head to the <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST" target="_blank"><abbr title="Mozilla Developer Network">MDN</abbr> web docs for <code>POST</code></a>.
If you want to read more about these encodings and form fields, head to the <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST" target="_blank"><abbr title="Mozilla Developer Network">MDN</abbr> web docs for <code>POST</code></a>.
!!! warning

View File

@@ -24,6 +24,9 @@ But most importantly:
* Will limit the output data to that of the model. We'll see how that's important below.
!!! note "Technical Details"
The response model is declared in this parameter instead of as a function return type annotation, because the path function may not actually return that response model but rather return a `dict`, database object or some other model, and then use the `response_model` to perform the field limiting and serialization.
## Return the same input data
Here we are declaring a `UserIn` model, it will contain a plaintext password:
@@ -40,7 +43,7 @@ And we are using this model to declare our input and the same model to declare o
Now, whenever a browser is creating a user with a password, the API will return the same password in the response.
In this case, it might not be a problem, becase the user himself is sending the password.
In this case, it might not be a problem, because the user himself is sending the password.
But if we use the same model for another path operation, we could be sending the passwords of our users to every client.

View File

@@ -27,7 +27,7 @@ Copy the example in a file `main.py`:
Run the example with:
```bash
uvicorn main:app --debug
uvicorn main:app --reload
```
## Check it

View File

@@ -30,7 +30,7 @@ So, we can have sub-dependencies using `Security` too.
`get_current_user` will have a `Security` dependency with the same `oauth2_scheme` we created before.
The same as we were doing before in the path operation direclty, our new dependency will receive a `token` as a `str` from the `Security` dependency:
The same as we were doing before in the path operation directly, our new dependency will receive a `token` as a `str` from the `Security` dependency:
```Python hl_lines="25"
{!./src/security/tutorial002.py!}

View File

@@ -22,7 +22,7 @@ If you want to play with JWT tokens and see how they work, check <a href="https:
## Install `PyJWT`
We need to install `PyJWT` to generate and verity the JWT tokens in Python:
We need to install `PyJWT` to generate and verify the JWT tokens in Python:
```bash
pip install pyjwt
@@ -198,7 +198,7 @@ Many packages that simplify it a lot have to make many compromises with the data
**FastAPI** doesn't make any compromise with any database, data model or tool.
It gives you all the flexibility to chose the ones that fit your project the best.
It gives you all the flexibility to choose the ones that fit your project the best.
And you can use directly many well maintained and widely used packages like `passlib` and `pyjwt`, because **FastAPI** doesn't require any complex mechanisms to integrate external packages.

View File

@@ -24,14 +24,14 @@ The form field name is `scope` (in singular), but it is actually a long string w
Each "scope" is just a string (without spaces).
They are normally used to declare specific security permissions, for exampe:
They are normally used to declare specific security permissions, for example:
* `"users:read"` or `"users:write"` are common examples.
* `instagram_basic` is used by Facebook / Instagram.
* `https://www.googleapis.com/auth/drive` is used by Google.
!!! info
In OAuth2 a "scope" is just a string that declares a specific permision required.
In OAuth2 a "scope" is just a string that declares a specific permission required.
It doesn't matter if it has other characters like `:`, or if it is a URL.

View File

@@ -37,7 +37,7 @@ For now, don't pay attention to the rest, only the imports:
Define the database that SQLAlchemy should "connect" to:
```Python hl_lines="8"
```Python hl_lines="9"
{!./src/sql_databases/tutorial001.py!}
```
@@ -59,7 +59,7 @@ SQLALCHEMY_DATABASE_URI = "postgresql://user:password@postgresserver/db"
## Create the SQLAlchemy `engine`
```Python hl_lines="11 12 13"
```Python hl_lines="12 13 14"
{!./src/sql_databases/tutorial001.py!}
```
@@ -86,11 +86,11 @@ This object (class) is not a connection to the database yet, but once we create
We name it `SessionLocal` to distinguish it form the `Session` we are importing from SQLAlchemy.
We will use `Session` to declare types later and getter better editor support and completion.
We will use `Session` to declare types later and to get better editor support and completion.
For now, create the `SessionLocal`:
```Python hl_lines="14"
```Python hl_lines="15"
{!./src/sql_databases/tutorial001.py!}
```
@@ -108,10 +108,17 @@ A "middleware" is a function that is always executed for each request, and have
This middleware (just a function) will create a new SQLAlchemy `SessionLocal` for each request, add it to the request and then close it once the request is finished.
```Python hl_lines="67 68 69 70 71 72"
```Python hl_lines="68 69 70 71 72 73 74 75 76"
{!./src/sql_databases/tutorial001.py!}
```
!!! info
We put the creation of the `SessionLocal()` and handling of the requests in a `try` block.
And then we close it in the `finally` block.
This way we make sure the database session is always closed after the request. Even if there was an exception in the middle.
### About `request.state`
<a href="https://www.starlette.io/requests/#other-state" target="_blank">`request.state` is a property of each Starlette `Request` object</a>, it is there to store arbitrary objects attached to the request itself, like the database session in this case.
@@ -126,11 +133,11 @@ And when using the dependency in a path operation function, we declare it with t
This will then give us better editor support inside the path operation function, because the editor will know that the `db` parameter is of type `Session`.
```Python hl_lines="53 54 68"
```Python hl_lines="54 55 69"
{!./src/sql_databases/tutorial001.py!}
```
!!! info "Technical Detail"
!!! info "Technical Details"
The parameter `db` is actually of type `SessionLocal`, but this class (created with `sessionmaker()`) is a "proxy" of a SQLAlchemy `Session`, so, the editor doesn't really know what methods are provided.
But by declaring the type as `Session`, the editor now can know the available methods (`.add()`, `.query()`, `.commit()`, etc) and can provide better support (like completion). The type declaration doesn't affect the actual object.
@@ -145,13 +152,13 @@ That way you don't have to declare them explicitly in every model.
So, your models will behave very similarly to, for example, Flask-SQLAlchemy.
```Python hl_lines="17 18 19 20 21"
```Python hl_lines="18 19 20 21 22"
{!./src/sql_databases/tutorial001.py!}
```
## Create the SQLAlchemy `Base` model
```Python hl_lines="24"
```Python hl_lines="25"
{!./src/sql_databases/tutorial001.py!}
```
@@ -161,7 +168,7 @@ Now this is finally code specific to your app.
Here's a user model that will be a table in the database:
```Python hl_lines="27 28 29 30 31"
```Python hl_lines="28 29 30 31 32"
{!./src/sql_databases/tutorial001.py!}
```
@@ -169,7 +176,7 @@ Here's a user model that will be a table in the database:
In a very simplistic way, initialize your database (create the tables, etc) and make sure you have a first user:
```Python hl_lines="34 36 38 39 40 41 42 44"
```Python hl_lines="35 37 39 40 41 42 43 45"
{!./src/sql_databases/tutorial001.py!}
```
@@ -197,7 +204,7 @@ Also, as all the functionality is self-contained in the same code, you can copy
By creating a function that is only dedicated to getting your user from a `user_id` (or any other parameter) independent of your path operation function, you can more easily re-use it in multiple parts and also add <abbr title="Automated tests, written in code, that check if another piece of code is working correctly.">unit tests</abbr> for it:
```Python hl_lines="48 49"
```Python hl_lines="49 50"
{!./src/sql_databases/tutorial001.py!}
```
@@ -207,7 +214,7 @@ Now, finally, here's the standard **FastAPI** code.
Create your app and path operation function:
```Python hl_lines="58 61 62 63 64"
```Python hl_lines="59 62 63 64 65"
{!./src/sql_databases/tutorial001.py!}
```
@@ -243,10 +250,13 @@ user = get_user(db_session, user_id=user_id)
Then we should declare the path operation without `async def`, just with a normal `def`:
```Python hl_lines="62"
```Python hl_lines="63"
{!./src/sql_databases/tutorial001.py!}
```
!!! note "Very Technical Details"
If you are curious and have a deep technical knowledge, you can check <a href="https://fastapi.tiangolo.com/async/#very-technical-details" target="_blank">the very technical details of how this `async def` vs `def` is handled</a>.
## Migrations
Because we are using SQLAlchemy directly and we don't require any kind of plug-in for it to work with **FastAPI**, we could integrate database <abbr title="Automatically updating the database to have any new column we define in our models.">migrations</abbr> with <a href="https://alembic.sqlalchemy.org" target="_blank">Alembic</a> directly.
@@ -267,7 +277,7 @@ You can copy it, let's say, to a file `main.py`.
Then you can run it with Uvicorn:
```bash
uvicorn main:app --debug
uvicorn main:app --reload
```
And then, you can open your browser at <a href="http://127.0.0.1:8000/docs" target="_blank">http://127.0.0.1:8000/docs</a>.
@@ -297,7 +307,7 @@ That's something that you can improve in this example application, here's the cu
}
```
## Interact with the database direclty
## Interact with the database directly
If you want to explore the SQLite database (file) directly, independently of FastAPI, to debug its contents, add tables, columns, records, modify data, etc. you can use <a href="https://sqlitebrowser.org/" target="_blank">DB Browser for SQLite</a>.

View File

@@ -75,7 +75,7 @@ Here you need to make sure you use the same path that you used for the `openapi_
Now, run `uvicorn`, if your file is at `main.py`, it would be:
```bash
uvicorn main:app --debug
uvicorn main:app --reload
```
And open the docs at <a href="http://127.0.0.1:8000/docs" target="_blank">http://127.0.0.1:8000/docs</a>.

View File

@@ -21,7 +21,7 @@ Although any other parameter declared normally (for example, the body with a Pyd
But there are specific cases where it's useful to get the `Request` object.
## Use the `Request` object direclty
## Use the `Request` object directly
Let's imagine you want to get the client's IP address/host inside of your *path operation function*.

View File

@@ -0,0 +1,93 @@
You can use <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API" target="_blank">WebSockets</a> with **FastAPI**.
## WebSockets client
### In production
In your production system, you probably have a frontend created with a modern framework like React, Vue.js or Angular.
And to communicate using WebSockets with your backend you would probably use your frontend's utilities.
Or you might have a native mobile application that communicates with your WebSocket backend directly, in native code.
Or you might have any other way to communicate with the WebSocket endpoint.
---
But for this example, we'll use a very simple HTML document with some JavaScript, all inside a long string.
This, of course, is not optimal and you wouldn't use it for production.
In production you would have one of the options above.
But it's the simplest way to focus on the server-side of WebSockets and have a working example:
```Python hl_lines="2 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 42 43 44"
{!./src/websockets/tutorial001.py!}
```
## Create a `websocket_route`
In your **FastAPI** application, create a `websocket_route`:
```Python hl_lines="3 47 48"
{!./src/websockets/tutorial001.py!}
```
!!! tip
In this example we are importing `WebSocket` from `starlette.websockets` to use it in the type declaration in the WebSocket route function.
That is not required, but it's recommended as it will provide you completion and checks inside the function.
!!! info
This `websocket_route` we are using comes directly from <a href="https://www.starlette.io/applications/" target="_blank">Starlette</a>.
That's why the naming convention is not the same as with other API path operations (`get`, `post`, etc).
## Await for messages and send messages
In your WebSocket route you can `await` for messages and send messages.
```Python hl_lines="49 50 51 52 53"
{!./src/websockets/tutorial001.py!}
```
You can receive and send binary, text, and JSON data.
To learn more about the options, check Starlette's documentation for:
* <a href="https://www.starlette.io/applications/" target="_blank">Applications (`websocket_route`)</a>.
* <a href="https://www.starlette.io/websockets/" target="_blank">The `WebSocket` class</a>.
* <a href="https://www.starlette.io/endpoints/#websocketendpoint" target="_blank">Class-based WebSocket handling</a>.
## Test it
If your file is named `main.py`, run your application with:
```bash
uvicorn main:app --reload
```
Open your browser at <a href="http://127.0.0.1:8000" target="_blank">http://127.0.0.1:8000</a>.
You will see a simple page like:
<img src="/img/tutorial/websockets/image01.png">
You can type messages in the input box, and send them:
<img src="/img/tutorial/websockets/image02.png">
And your **FastAPI** application with WebSockets will respond back:
<img src="/img/tutorial/websockets/image03.png">
You can send (and receive) many messages:
<img src="/img/tutorial/websockets/image04.png">
And all of them will use the same WebSocket connection.

View File

@@ -1,8 +1,11 @@
"""FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
__version__ = "0.6.2"
__version__ = "0.12.1"
from starlette.background import BackgroundTasks
from .applications import FastAPI
from .routing import APIRouter
from .params import Body, Path, Query, Header, Cookie, Form, File, Security, Depends
from .datastructures import UploadFile
from .exceptions import HTTPException
from .params import Body, Cookie, Depends, File, Form, Header, Path, Query, Security
from .routing import APIRouter

View File

@@ -1,4 +1,4 @@
from typing import Any, Callable, Dict, List, Optional, Type
from typing import Any, Callable, Dict, List, Optional, Type, Union
from fastapi import routing
from fastapi.openapi.docs import get_redoc_html, get_swagger_ui_html
@@ -114,6 +114,7 @@ class FastAPI(Starlette):
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
methods: List[str] = None,
operation_id: str = None,
@@ -130,6 +131,7 @@ class FastAPI(Starlette):
summary=summary,
description=description,
response_description=response_description,
responses=responses or {},
deprecated=deprecated,
methods=methods,
operation_id=operation_id,
@@ -148,6 +150,7 @@ class FastAPI(Starlette):
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
methods: List[str] = None,
operation_id: str = None,
@@ -165,6 +168,7 @@ class FastAPI(Starlette):
summary=summary,
description=description,
response_description=response_description,
responses=responses or {},
deprecated=deprecated,
methods=methods,
operation_id=operation_id,
@@ -176,8 +180,17 @@ class FastAPI(Starlette):
return decorator
def include_router(self, router: routing.APIRouter, *, prefix: str = "") -> None:
self.router.include_router(router, prefix=prefix)
def include_router(
self,
router: routing.APIRouter,
*,
prefix: str = "",
tags: List[str] = None,
responses: Dict[Union[int, str], Dict[str, Any]] = None,
) -> None:
self.router.include_router(
router, prefix=prefix, tags=tags, responses=responses or {}
)
def get(
self,
@@ -189,6 +202,7 @@ class FastAPI(Starlette):
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
include_in_schema: bool = True,
@@ -203,6 +217,7 @@ class FastAPI(Starlette):
summary=summary,
description=description,
response_description=response_description,
responses=responses or {},
deprecated=deprecated,
operation_id=operation_id,
include_in_schema=include_in_schema,
@@ -220,6 +235,7 @@ class FastAPI(Starlette):
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
include_in_schema: bool = True,
@@ -234,6 +250,7 @@ class FastAPI(Starlette):
summary=summary,
description=description,
response_description=response_description,
responses=responses or {},
deprecated=deprecated,
operation_id=operation_id,
include_in_schema=include_in_schema,
@@ -251,6 +268,7 @@ class FastAPI(Starlette):
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
include_in_schema: bool = True,
@@ -265,6 +283,7 @@ class FastAPI(Starlette):
summary=summary,
description=description,
response_description=response_description,
responses=responses or {},
deprecated=deprecated,
operation_id=operation_id,
include_in_schema=include_in_schema,
@@ -282,6 +301,7 @@ class FastAPI(Starlette):
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
include_in_schema: bool = True,
@@ -296,6 +316,7 @@ class FastAPI(Starlette):
summary=summary,
description=description,
response_description=response_description,
responses=responses or {},
deprecated=deprecated,
operation_id=operation_id,
include_in_schema=include_in_schema,
@@ -313,6 +334,7 @@ class FastAPI(Starlette):
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
include_in_schema: bool = True,
@@ -327,6 +349,7 @@ class FastAPI(Starlette):
summary=summary,
description=description,
response_description=response_description,
responses=responses or {},
deprecated=deprecated,
operation_id=operation_id,
include_in_schema=include_in_schema,
@@ -344,6 +367,7 @@ class FastAPI(Starlette):
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
include_in_schema: bool = True,
@@ -358,6 +382,7 @@ class FastAPI(Starlette):
summary=summary,
description=description,
response_description=response_description,
responses=responses or {},
deprecated=deprecated,
operation_id=operation_id,
include_in_schema=include_in_schema,
@@ -375,6 +400,7 @@ class FastAPI(Starlette):
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
include_in_schema: bool = True,
@@ -389,6 +415,7 @@ class FastAPI(Starlette):
summary=summary,
description=description,
response_description=response_description,
responses=responses or {},
deprecated=deprecated,
operation_id=operation_id,
include_in_schema=include_in_schema,
@@ -406,6 +433,7 @@ class FastAPI(Starlette):
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
include_in_schema: bool = True,
@@ -420,6 +448,7 @@ class FastAPI(Starlette):
summary=summary,
description=description,
response_description=response_description,
responses=responses or {},
deprecated=deprecated,
operation_id=operation_id,
include_in_schema=include_in_schema,

15
fastapi/datastructures.py Normal file
View File

@@ -0,0 +1,15 @@
from typing import Any, Callable, Iterable, Type
from starlette.datastructures import UploadFile as StarletteUploadFile
class UploadFile(StarletteUploadFile):
@classmethod
def __get_validators__(cls: Type["UploadFile"]) -> Iterable[Callable]:
yield cls.validate
@classmethod
def validate(cls: Type["UploadFile"], v: Any) -> Any:
if not isinstance(v, StarletteUploadFile):
raise ValueError(f"Expected UploadFile, received: {type(v)}")
return v

View File

@@ -26,6 +26,7 @@ class Dependant:
name: str = None,
call: Callable = None,
request_param_name: str = None,
background_tasks_param_name: str = None,
) -> None:
self.path_params = path_params or []
self.query_params = query_params or []
@@ -35,5 +36,6 @@ class Dependant:
self.dependencies = dependencies or []
self.security_requirements = security_schemes or []
self.request_param_name = request_param_name
self.background_tasks_param_name = background_tasks_param_name
self.name = name
self.call = call

View File

@@ -3,7 +3,18 @@ import inspect
from copy import deepcopy
from datetime import date, datetime, time, timedelta
from decimal import Decimal
from typing import Any, Callable, Dict, List, Mapping, Sequence, Tuple, Type, Union
from typing import (
Any,
Callable,
Dict,
List,
Mapping,
Optional,
Sequence,
Tuple,
Type,
Union,
)
from uuid import UUID
from fastapi import params
@@ -16,7 +27,9 @@ from pydantic.errors import MissingError
from pydantic.fields import Field, Required, Shape
from pydantic.schema import get_annotation_from_schema
from pydantic.utils import lenient_issubclass
from starlette.background import BackgroundTasks
from starlette.concurrency import run_in_threadpool
from starlette.datastructures import UploadFile
from starlette.requests import Headers, QueryParams, Request
param_supported_types = (
@@ -87,7 +100,6 @@ def get_dependant(*, path: str, call: Callable, name: str = None) -> Dependant:
lenient_issubclass(param.annotation, param_supported_types)
or param.annotation == param.empty
), f"Path params must be of one of the supported types"
param = signature_params[param_name]
add_param_to_fields(
param=param,
dependant=dependant,
@@ -124,6 +136,8 @@ def get_dependant(*, path: str, call: Callable, name: str = None) -> Dependant:
)
elif lenient_issubclass(param.annotation, Request):
dependant.request_param_name = param_name
elif lenient_issubclass(param.annotation, BackgroundTasks):
dependant.background_tasks_param_name = param_name
elif not isinstance(param.default, params.Depends):
add_param_to_body_fields(param=param, dependant=dependant)
return dependant
@@ -214,13 +228,20 @@ def is_coroutine_callable(call: Callable) -> bool:
async def solve_dependencies(
*, request: Request, dependant: Dependant, body: Dict[str, Any] = None
) -> Tuple[Dict[str, Any], List[ErrorWrapper]]:
*,
request: Request,
dependant: Dependant,
body: Dict[str, Any] = None,
background_tasks: BackgroundTasks = None,
) -> Tuple[Dict[str, Any], List[ErrorWrapper], Optional[BackgroundTasks]]:
values: Dict[str, Any] = {}
errors: List[ErrorWrapper] = []
for sub_dependant in dependant.dependencies:
sub_values, sub_errors = await solve_dependencies(
request=request, dependant=sub_dependant, body=body
sub_values, sub_errors, background_tasks = await solve_dependencies(
request=request,
dependant=sub_dependant,
body=body,
background_tasks=background_tasks,
)
if sub_errors:
errors.extend(sub_errors)
@@ -257,7 +278,11 @@ async def solve_dependencies(
errors.extend(body_errors)
if dependant.request_param_name:
values[dependant.request_param_name] = request
return values, errors
if dependant.background_tasks_param_name:
if background_tasks is None:
background_tasks = BackgroundTasks()
values[dependant.background_tasks_param_name] = background_tasks
return values, errors, background_tasks
def request_params_to_args(
@@ -323,6 +348,12 @@ async def request_body_to_args(
else:
values[field.name] = deepcopy(field.default)
continue
if (
isinstance(field.schema, params.File)
and lenient_issubclass(field.type_, bytes)
and isinstance(value, UploadFile)
):
value = await value.read()
v_, errors_ = field.validate(value, values, loc=("body", field.alias))
if isinstance(errors_, ErrorWrapper):
errors.append(errors_)
@@ -333,6 +364,21 @@ async def request_body_to_args(
return values, errors
def get_schema_compatible_field(*, field: Field) -> Field:
if lenient_issubclass(field.type_, UploadFile):
return Field(
name=field.name,
type_=bytes,
class_validators=field.class_validators,
model_config=field.model_config,
default=field.default,
required=field.required,
alias=field.alias,
schema=field.schema,
)
return field
def get_body_field(*, dependant: Dependant, name: str) -> Field:
flat_dependant = get_flat_dependant(dependant)
if not flat_dependant.body_params:
@@ -340,11 +386,11 @@ def get_body_field(*, dependant: Dependant, name: str) -> Field:
first_param = flat_dependant.body_params[0]
embed = getattr(first_param.schema, "embed", None)
if len(flat_dependant.body_params) == 1 and not embed:
return first_param
return get_schema_compatible_field(field=first_param)
model_name = "Body_" + name
BodyModel = create_model(model_name)
for f in flat_dependant.body_params:
BodyModel.__fields__[f.name] = f
BodyModel.__fields__[f.name] = get_schema_compatible_field(field=f)
required = any(True for f in flat_dependant.body_params if f.required)
if any(isinstance(f.schema, params.File) for f in flat_dependant.body_params):
BodySchema: Type[params.Body] = params.File

View File

@@ -1,6 +1,6 @@
from enum import Enum
from types import GeneratorType
from typing import Any, Set
from typing import Any, List, Set
from pydantic import BaseModel
from pydantic.json import ENCODERS_BY_TYPE
@@ -70,7 +70,7 @@ def jsonable_encoder(
)
)
return encoded_list
errors = []
errors: List[Exception] = []
try:
if custom_encoder and type(obj) in custom_encoder:
encoder = custom_encoder[type(obj)]

View File

@@ -8,6 +8,7 @@ def get_swagger_ui_html(*, openapi_url: str, title: str) -> HTMLResponse:
<html>
<head>
<link type="text/css" rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swagger-ui-dist@3/swagger-ui.css">
<link rel="shortcut icon" href="https://fastapi.tiangolo.com/img/favicon.png">
<title>
"""
+ title
@@ -55,6 +56,7 @@ def get_redoc_html(*, openapi_url: str, title: str) -> HTMLResponse:
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
<link rel="shortcut icon" href="https://fastapi.tiangolo.com/img/favicon.png">
<!--
ReDoc doesn't change outer page styles

View File

@@ -99,7 +99,7 @@ class SchemaBase(BaseModel):
not_: Optional[List[Any]] = PSchema(None, alias="not") # type: ignore
items: Optional[Any] = None
properties: Optional[Dict[str, Any]] = None
additionalProperties: Optional[Union[bool, Any]] = None
additionalProperties: Optional[Union[Dict[str, Any], bool]] = None
description: Optional[str] = None
format: Optional[str] = None
default: Optional[Any] = None
@@ -120,7 +120,7 @@ class Schema(SchemaBase):
not_: Optional[List[SchemaBase]] = PSchema(None, alias="not") # type: ignore
items: Optional[SchemaBase] = None
properties: Optional[Dict[str, SchemaBase]] = None
additionalProperties: Optional[Union[bool, SchemaBase]] = None
additionalProperties: Optional[Union[SchemaBase, bool]] = None
class Example(BaseModel):
@@ -220,7 +220,7 @@ class Operation(BaseModel):
operationId: Optional[str] = None
parameters: Optional[List[Union[Parameter, Reference]]] = None
requestBody: Optional[Union[RequestBody, Reference]] = None
responses: Union[Responses, Dict[Union[str], Response]]
responses: Union[Responses, Dict[str, Response]]
# Workaround OpenAPI recursive reference
callbacks: Optional[Dict[str, Union[Dict[str, Any], Reference]]] = None
deprecated: Optional[bool] = None

View File

@@ -178,6 +178,23 @@ def get_openapi_path(
definitions[
"HTTPValidationError"
] = validation_error_response_definition
if route.responses:
for (additional_status_code, response) in route.responses.items():
assert isinstance(
response, dict
), "An additional response must be a dict"
field = route.response_fields.get(additional_status_code)
if field:
response_schema, _ = field_schema(
field, model_name_map=model_name_map, ref_prefix=REF_PREFIX
)
response.setdefault("content", {}).setdefault(
"application/json", {}
)["schema"] = response_schema
response.setdefault("description", "Additional Response")
operation.setdefault("responses", {})[
str(additional_status_code)
] = response
status_code = str(route.status_code)
response_schema = {"type": "string"}
if lenient_issubclass(route.content_type, JSONResponse):
@@ -189,13 +206,14 @@ def get_openapi_path(
)
else:
response_schema = {}
content = {route.content_type.media_type: {"schema": response_schema}}
operation["responses"] = {
status_code: {
"description": route.response_description,
"content": content,
}
}
operation.setdefault("responses", {}).setdefault(status_code, {})[
"description"
] = route.response_description
operation.setdefault("responses", {}).setdefault(
status_code, {}
).setdefault("content", {}).setdefault(route.content_type.media_type, {})[
"schema"
] = response_schema
if all_route_params or route.body_field:
operation["responses"][str(HTTP_422_UNPROCESSABLE_ENTITY)] = {
"description": "Validation Error",

View File

@@ -1,7 +1,7 @@
import asyncio
import inspect
import logging
from typing import Any, Callable, List, Optional, Type
from typing import Any, Callable, Dict, List, Optional, Type, Union
from fastapi import params
from fastapi.dependencies.models import Dependant
@@ -15,7 +15,6 @@ from pydantic.utils import lenient_issubclass
from starlette import routing
from starlette.concurrency import run_in_threadpool
from starlette.exceptions import HTTPException
from starlette.formparsers import UploadFile
from starlette.requests import Request
from starlette.responses import JSONResponse, Response
from starlette.routing import compile_path, get_name, request_response
@@ -57,10 +56,7 @@ def get_app(
raw_body = await request.form()
form_fields = {}
for field, value in raw_body.items():
if isinstance(value, UploadFile):
form_fields[field] = await value.read()
else:
form_fields[field] = value
form_fields[field] = value
if form_fields:
body = form_fields
else:
@@ -72,7 +68,7 @@ def get_app(
raise HTTPException(
status_code=400, detail="There was an error parsing the body"
)
values, errors = await solve_dependencies(
values, errors, background_tasks = await solve_dependencies(
request=request, dependant=dependant, body=body
)
if errors:
@@ -87,11 +83,17 @@ def get_app(
else:
raw_response = await run_in_threadpool(dependant.call, **values)
if isinstance(raw_response, Response):
if raw_response.background is None:
raw_response.background = background_tasks
return raw_response
response_data = serialize_response(
field=response_field, response=raw_response
)
return content_type(content=response_data, status_code=status_code)
return content_type(
content=response_data,
status_code=status_code,
background=background_tasks,
)
return app
@@ -108,6 +110,7 @@ class APIRoute(routing.Route):
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
name: str = None,
methods: List[str] = None,
@@ -141,6 +144,30 @@ class APIRoute(routing.Route):
self.summary = summary
self.description = description or self.endpoint.__doc__
self.response_description = response_description
self.responses = responses or {}
response_fields = {}
for additional_status_code, response in self.responses.items():
assert isinstance(response, dict), "An additional response must be a dict"
model = response.get("model")
if model:
assert lenient_issubclass(
model, BaseModel
), "A response model must be a Pydantic model"
response_name = f"Response_{additional_status_code}_{self.name}"
response_field = Field(
name=response_name,
type_=model,
class_validators=None,
default=None,
required=False,
model_config=UnconstrainedConfig,
schema=Schema(None),
)
response_fields[additional_status_code] = response_field
if response_fields:
self.response_fields: Dict[Union[int, str], Field] = response_fields
else:
self.response_fields = {}
self.deprecated = deprecated
if methods is None:
methods = ["GET"]
@@ -178,6 +205,7 @@ class APIRouter(routing.Router):
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
methods: List[str] = None,
operation_id: str = None,
@@ -194,6 +222,7 @@ class APIRouter(routing.Router):
summary=summary,
description=description,
response_description=response_description,
responses=responses or {},
deprecated=deprecated,
methods=methods,
operation_id=operation_id,
@@ -213,6 +242,7 @@ class APIRouter(routing.Router):
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
methods: List[str] = None,
operation_id: str = None,
@@ -230,6 +260,7 @@ class APIRouter(routing.Router):
summary=summary,
description=description,
response_description=response_description,
responses=responses or {},
deprecated=deprecated,
methods=methods,
operation_id=operation_id,
@@ -241,23 +272,34 @@ class APIRouter(routing.Router):
return decorator
def include_router(self, router: "APIRouter", *, prefix: str = "") -> None:
def include_router(
self,
router: "APIRouter",
*,
prefix: str = "",
tags: List[str] = None,
responses: Dict[Union[int, str], Dict[str, Any]] = None,
) -> None:
if prefix:
assert prefix.startswith("/"), "A path prefix must start with '/'"
assert not prefix.endswith(
"/"
), "A path prefix must not end with '/', as the routes will start with '/'"
if responses is None:
responses = {}
for route in router.routes:
if isinstance(route, APIRoute):
combined_responses = {**responses, **route.responses}
self.add_api_route(
prefix + route.path,
route.endpoint,
response_model=route.response_model,
status_code=route.status_code,
tags=route.tags or [],
tags=(route.tags or []) + (tags or []),
summary=route.summary,
description=route.description,
response_description=route.response_description,
responses=combined_responses,
deprecated=route.deprecated,
methods=route.methods,
operation_id=route.operation_id,
@@ -273,6 +315,10 @@ class APIRouter(routing.Router):
include_in_schema=route.include_in_schema,
name=route.name,
)
elif isinstance(route, routing.WebSocketRoute):
self.add_websocket_route(
prefix + route.path, route.endpoint, name=route.name
)
def get(
self,
@@ -284,6 +330,7 @@ class APIRouter(routing.Router):
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
include_in_schema: bool = True,
@@ -298,6 +345,7 @@ class APIRouter(routing.Router):
summary=summary,
description=description,
response_description=response_description,
responses=responses or {},
deprecated=deprecated,
methods=["GET"],
operation_id=operation_id,
@@ -316,6 +364,7 @@ class APIRouter(routing.Router):
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
include_in_schema: bool = True,
@@ -330,6 +379,7 @@ class APIRouter(routing.Router):
summary=summary,
description=description,
response_description=response_description,
responses=responses or {},
deprecated=deprecated,
methods=["PUT"],
operation_id=operation_id,
@@ -348,6 +398,7 @@ class APIRouter(routing.Router):
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
include_in_schema: bool = True,
@@ -362,6 +413,7 @@ class APIRouter(routing.Router):
summary=summary,
description=description,
response_description=response_description,
responses=responses or {},
deprecated=deprecated,
methods=["POST"],
operation_id=operation_id,
@@ -380,6 +432,7 @@ class APIRouter(routing.Router):
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
include_in_schema: bool = True,
@@ -394,6 +447,7 @@ class APIRouter(routing.Router):
summary=summary,
description=description,
response_description=response_description,
responses=responses or {},
deprecated=deprecated,
methods=["DELETE"],
operation_id=operation_id,
@@ -412,6 +466,7 @@ class APIRouter(routing.Router):
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
include_in_schema: bool = True,
@@ -426,6 +481,7 @@ class APIRouter(routing.Router):
summary=summary,
description=description,
response_description=response_description,
responses=responses or {},
deprecated=deprecated,
methods=["OPTIONS"],
operation_id=operation_id,
@@ -444,6 +500,7 @@ class APIRouter(routing.Router):
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
include_in_schema: bool = True,
@@ -458,6 +515,7 @@ class APIRouter(routing.Router):
summary=summary,
description=description,
response_description=response_description,
responses=responses or {},
deprecated=deprecated,
methods=["HEAD"],
operation_id=operation_id,
@@ -476,6 +534,7 @@ class APIRouter(routing.Router):
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
include_in_schema: bool = True,
@@ -490,6 +549,7 @@ class APIRouter(routing.Router):
summary=summary,
description=description,
response_description=response_description,
responses=responses or {},
deprecated=deprecated,
methods=["PATCH"],
operation_id=operation_id,
@@ -508,6 +568,7 @@ class APIRouter(routing.Router):
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
include_in_schema: bool = True,
@@ -522,6 +583,7 @@ class APIRouter(routing.Router):
summary=summary,
description=description,
response_description=response_description,
responses=responses or {},
deprecated=deprecated,
methods=["TRACE"],
operation_id=operation_id,

View File

@@ -1,10 +1,10 @@
from .api_key import APIKeyQuery, APIKeyHeader, APIKeyCookie
from .api_key import APIKeyCookie, APIKeyHeader, APIKeyQuery
from .http import (
HTTPAuthorizationCredentials,
HTTPBasic,
HTTPBasicCredentials,
HTTPBearer,
HTTPDigest,
HTTPBasicCredentials,
HTTPAuthorizationCredentials,
)
from .oauth2 import OAuth2PasswordRequestForm, OAuth2, OAuth2PasswordBearer
from .oauth2 import OAuth2, OAuth2PasswordBearer, OAuth2PasswordRequestForm
from .open_id_connect_url import OpenIdConnect

View File

@@ -1,3 +1,5 @@
from typing import Optional
from fastapi.openapi.models import APIKey, APIKeyIn
from fastapi.security.base import SecurityBase
from starlette.exceptions import HTTPException
@@ -10,42 +12,54 @@ class APIKeyBase(SecurityBase):
class APIKeyQuery(APIKeyBase):
def __init__(self, *, name: str, scheme_name: str = None):
self.model = APIKey(**{"in": APIKeyIn.query}, name=name)
def __init__(self, *, name: str, scheme_name: str = None, auto_error: bool = True):
self.model: APIKey = APIKey(**{"in": APIKeyIn.query}, name=name)
self.scheme_name = scheme_name or self.__class__.__name__
self.auto_error = auto_error
async def __call__(self, request: Request) -> str:
async def __call__(self, request: Request) -> Optional[str]:
api_key: str = request.query_params.get(self.model.name)
if not api_key:
raise HTTPException(
status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
)
if self.auto_error:
raise HTTPException(
status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
)
else:
return None
return api_key
class APIKeyHeader(APIKeyBase):
def __init__(self, *, name: str, scheme_name: str = None):
self.model = APIKey(**{"in": APIKeyIn.header}, name=name)
def __init__(self, *, name: str, scheme_name: str = None, auto_error: bool = True):
self.model: APIKey = APIKey(**{"in": APIKeyIn.header}, name=name)
self.scheme_name = scheme_name or self.__class__.__name__
self.auto_error = auto_error
async def __call__(self, request: Request) -> str:
async def __call__(self, request: Request) -> Optional[str]:
api_key: str = request.headers.get(self.model.name)
if not api_key:
raise HTTPException(
status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
)
if self.auto_error:
raise HTTPException(
status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
)
else:
return None
return api_key
class APIKeyCookie(APIKeyBase):
def __init__(self, *, name: str, scheme_name: str = None):
self.model = APIKey(**{"in": APIKeyIn.cookie}, name=name)
def __init__(self, *, name: str, scheme_name: str = None, auto_error: bool = True):
self.model: APIKey = APIKey(**{"in": APIKeyIn.cookie}, name=name)
self.scheme_name = scheme_name or self.__class__.__name__
self.auto_error = auto_error
async def __call__(self, request: Request) -> str:
async def __call__(self, request: Request) -> Optional[str]:
api_key: str = request.cookies.get(self.model.name)
if not api_key:
raise HTTPException(
status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
)
if self.auto_error:
raise HTTPException(
status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
)
else:
return None
return api_key

View File

@@ -1,5 +1,6 @@
import binascii
from base64 import b64decode
from typing import Optional
from fastapi.openapi.models import (
HTTPBase as HTTPBaseModel,
@@ -24,27 +25,38 @@ class HTTPAuthorizationCredentials(BaseModel):
class HTTPBase(SecurityBase):
def __init__(self, *, scheme: str, scheme_name: str = None):
def __init__(
self, *, scheme: str, scheme_name: str = None, auto_error: bool = True
):
self.model = HTTPBaseModel(scheme=scheme)
self.scheme_name = scheme_name or self.__class__.__name__
self.auto_error = auto_error
async def __call__(self, request: Request) -> str:
async def __call__(
self, request: Request
) -> Optional[HTTPAuthorizationCredentials]:
authorization: str = request.headers.get("Authorization")
scheme, credentials = get_authorization_scheme_param(authorization)
if not (authorization and scheme and credentials):
raise HTTPException(
status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
)
if self.auto_error:
raise HTTPException(
status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
)
else:
return None
return HTTPAuthorizationCredentials(scheme=scheme, credentials=credentials)
class HTTPBasic(HTTPBase):
def __init__(self, *, scheme_name: str = None, realm: str = None):
def __init__(
self, *, scheme_name: str = None, realm: str = None, auto_error: bool = True
):
self.model = HTTPBaseModel(scheme="basic")
self.scheme_name = scheme_name or self.__class__.__name__
self.realm = realm
self.auto_error = auto_error
async def __call__(self, request: Request) -> str:
async def __call__(self, request: Request) -> Optional[HTTPBasicCredentials]:
authorization: str = request.headers.get("Authorization")
scheme, param = get_authorization_scheme_param(authorization)
# before implementing headers with 401 errors, wait for: https://github.com/encode/starlette/issues/295
@@ -53,9 +65,12 @@ class HTTPBasic(HTTPBase):
status_code=HTTP_403_FORBIDDEN, detail="Invalid authentication credentials"
)
if not authorization or scheme.lower() != "basic":
raise HTTPException(
status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
)
if self.auto_error:
raise HTTPException(
status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
)
else:
return None
try:
data = b64decode(param).decode("ascii")
except (ValueError, UnicodeDecodeError, binascii.Error):
@@ -67,17 +82,29 @@ class HTTPBasic(HTTPBase):
class HTTPBearer(HTTPBase):
def __init__(self, *, bearerFormat: str = None, scheme_name: str = None):
def __init__(
self,
*,
bearerFormat: str = None,
scheme_name: str = None,
auto_error: bool = True
):
self.model = HTTPBearerModel(bearerFormat=bearerFormat)
self.scheme_name = scheme_name or self.__class__.__name__
self.auto_error = auto_error
async def __call__(self, request: Request) -> str:
async def __call__(
self, request: Request
) -> Optional[HTTPAuthorizationCredentials]:
authorization: str = request.headers.get("Authorization")
scheme, credentials = get_authorization_scheme_param(authorization)
if not (authorization and scheme and credentials):
raise HTTPException(
status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
)
if self.auto_error:
raise HTTPException(
status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
)
else:
return None
if scheme.lower() != "bearer":
raise HTTPException(
status_code=HTTP_403_FORBIDDEN,
@@ -87,17 +114,23 @@ class HTTPBearer(HTTPBase):
class HTTPDigest(HTTPBase):
def __init__(self, *, scheme_name: str = None):
def __init__(self, *, scheme_name: str = None, auto_error: bool = True):
self.model = HTTPBaseModel(scheme="digest")
self.scheme_name = scheme_name or self.__class__.__name__
self.auto_error = auto_error
async def __call__(self, request: Request) -> str:
async def __call__(
self, request: Request
) -> Optional[HTTPAuthorizationCredentials]:
authorization: str = request.headers.get("Authorization")
scheme, credentials = get_authorization_scheme_param(authorization)
if not (authorization and scheme and credentials):
raise HTTPException(
status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
)
if self.auto_error:
raise HTTPException(
status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
)
else:
return None
if scheme.lower() != "digest":
raise HTTPException(
status_code=HTTP_403_FORBIDDEN,

View File

@@ -113,32 +113,49 @@ class OAuth2PasswordRequestFormStrict(OAuth2PasswordRequestForm):
class OAuth2(SecurityBase):
def __init__(
self, *, flows: OAuthFlowsModel = OAuthFlowsModel(), scheme_name: str = None
self,
*,
flows: OAuthFlowsModel = OAuthFlowsModel(),
scheme_name: str = None,
auto_error: bool = True
):
self.model = OAuth2Model(flows=flows)
self.scheme_name = scheme_name or self.__class__.__name__
self.auto_error = auto_error
async def __call__(self, request: Request) -> str:
async def __call__(self, request: Request) -> Optional[str]:
authorization: str = request.headers.get("Authorization")
if not authorization:
raise HTTPException(
status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
)
if self.auto_error:
raise HTTPException(
status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
)
else:
return None
return authorization
class OAuth2PasswordBearer(OAuth2):
def __init__(self, tokenUrl: str, scheme_name: str = None, scopes: dict = None):
def __init__(
self,
tokenUrl: str,
scheme_name: str = None,
scopes: dict = None,
auto_error: bool = True,
):
if not scopes:
scopes = {}
flows = OAuthFlowsModel(password={"tokenUrl": tokenUrl, "scopes": scopes})
super().__init__(flows=flows, scheme_name=scheme_name)
super().__init__(flows=flows, scheme_name=scheme_name, auto_error=auto_error)
async def __call__(self, request: Request) -> str:
async def __call__(self, request: Request) -> Optional[str]:
authorization: str = request.headers.get("Authorization")
scheme, param = get_authorization_scheme_param(authorization)
if not authorization or scheme.lower() != "bearer":
raise HTTPException(
status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
)
if self.auto_error:
raise HTTPException(
status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
)
else:
return None
return param

View File

@@ -1,3 +1,5 @@
from typing import Optional
from fastapi.openapi.models import OpenIdConnect as OpenIdConnectModel
from fastapi.security.base import SecurityBase
from starlette.exceptions import HTTPException
@@ -6,14 +8,20 @@ from starlette.status import HTTP_403_FORBIDDEN
class OpenIdConnect(SecurityBase):
def __init__(self, *, openIdConnectUrl: str, scheme_name: str = None):
def __init__(
self, *, openIdConnectUrl: str, scheme_name: str = None, auto_error: bool = True
):
self.model = OpenIdConnectModel(openIdConnectUrl=openIdConnectUrl)
self.scheme_name = scheme_name or self.__class__.__name__
self.auto_error = auto_error
async def __call__(self, request: Request) -> str:
async def __call__(self, request: Request) -> Optional[str]:
authorization: str = request.headers.get("Authorization")
if not authorization:
raise HTTPException(
status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
)
if self.auto_error:
raise HTTPException(
status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
)
else:
return None
return authorization

View File

@@ -30,6 +30,8 @@ def get_flat_models_from_routes(
body_fields_from_routes.append(route.body_field)
if route.response_field:
responses_from_routes.append(route.response_field)
if route.response_fields:
responses_from_routes.extend(route.response_fields.values())
flat_models = get_flat_models_from_fields(
body_fields_from_routes + responses_from_routes
)

View File

@@ -44,6 +44,7 @@ nav:
- Path Operation Configuration: 'tutorial/path-operation-configuration.md'
- Path Operation Advanced Configuration: 'tutorial/path-operation-advanced-configuration.md'
- Custom Response: 'tutorial/custom-response.md'
- Additional Responses: 'tutorial/additional-responses.md'
- Dependencies:
- First Steps: 'tutorial/dependencies/first-steps.md'
- Classes as Dependencies: 'tutorial/dependencies/classes-as-dependencies.md'
@@ -57,15 +58,22 @@ nav:
- OAuth2 with Password (and hashing), Bearer with JWT tokens: 'tutorial/security/oauth2-jwt.md'
- Using the Request Directly: 'tutorial/using-request-directly.md'
- SQL (Relational) Databases: 'tutorial/sql-databases.md'
- Async SQL (Relational) Databases: 'tutorial/async-sql-databases.md'
- NoSQL (Distributed / Big Data) Databases: 'tutorial/nosql-databases.md'
- Bigger Applications - Multiple Files: 'tutorial/bigger-applications.md'
- Background Tasks: 'tutorial/background-tasks.md'
- Sub Applications - Behind a Proxy: 'tutorial/sub-applications-proxy.md'
- Application Configuration: 'tutorial/application-configuration.md'
- GraphQL: 'tutorial/graphql.md'
- WebSockets: 'tutorial/websockets.md'
- 'Events: startup - shutdown': 'tutorial/events.md'
- Debugging: 'tutorial/debugging.md'
- Extending OpenAPI: 'tutorial/extending-openapi.md'
- Concurrency and async / await: 'async.md'
- Deployment: 'deployment.md'
- Project Generation - Template: 'project-generation.md'
- Alternatives, Inspiration and Comparisons: 'alternatives.md'
- History, Design and Future: 'history-design-future.md'
- Benchmarks: 'benchmarks.md'
- Help FastAPI - Get Help: 'help-fastapi.md'
- Development - Contributing: 'contributing.md'

View File

@@ -62,7 +62,7 @@ class FakeDB:
"johndoe": {
"username": "johndoe",
"password": "shouldbehashed",
"fist_name": "John",
"first_name": "John",
"last_name": "Doe",
}
}
@@ -87,7 +87,7 @@ class TokenUserData(BaseModel):
class UserInDB(BaseModel):
username: str
password: str
fist_name: str
first_name: str
last_name: str
@@ -109,7 +109,7 @@ def require_user(
class UserOut(BaseModel):
username: str
fist_name: str
first_name: str
last_name: str

View File

@@ -20,7 +20,7 @@ classifiers = [
]
requires = [
"starlette ==0.11.1",
"pydantic >=0.17,<=0.18.2"
"pydantic >=0.17,<=0.21.0"
]
description-file = "README.md"
requires-python = ">=3.6"
@@ -37,7 +37,8 @@ test = [
"isort",
"requests",
"email_validator",
"sqlalchemy"
"sqlalchemy",
"databases[sqlite]",
]
doc = [
"mkdocs",
@@ -57,7 +58,6 @@ all = [
"pyyaml",
"graphene",
"ujson",
"ujson",
"email_validator",
"uvicorn",
]

0
scripts/build-docs.sh Normal file → Executable file
View File

Some files were not shown because too many files have changed in this diff Show More