Compare commits

...

28 Commits

Author SHA1 Message Date
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
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
54 changed files with 1594 additions and 115 deletions

1
.gitignore vendored
View File

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

View File

@@ -26,7 +26,8 @@ uvicorn = "*"
[packages]
starlette = "==0.11.1"
pydantic = "==0.18.2"
pydantic = "==0.21.0"
databases = {extras = ["sqlite"],version = "*"}
[requires]
python_version = "3.6"

182
Pipfile.lock generated
View File

@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "f1f1b7fb88822fcf89e5073263c5a42234fa68d18ce0b1b82e4dd926bcdf12e9"
"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": [
@@ -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": [
@@ -224,7 +277,6 @@
"hashes": [
"sha256:e00cbd7ba01ff748e494248183abc6e153f49181169d8a3d41bb49132ca01dfc"
],
"markers": "sys_platform != 'win32' and sys_platform != 'cygwin' and platform_python_implementation != 'pypy'",
"version": "==0.0.13"
},
"idna": {
@@ -243,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": [
@@ -265,11 +317,11 @@
},
"isort": {
"hashes": [
"sha256:38a74a5ccf3a15a7a99f975071164f48d4d10eed4154879009c18e6e8933e5aa",
"sha256:abbb2684aa234d5eb8a67ef36d4aa62ea080d46c2eba36ad09e2990ae52e4305"
"sha256:08f8e3f0f0b7249e9fad7e5c41e2113aba44969798a26452ee790c06f155d4ec",
"sha256:4e9e9c4bd1acd66cf6c36973f29b031ec752cbfd991c69695e4e259f9a756927"
],
"index": "pypi",
"version": "==4.3.13"
"version": "==4.3.16"
},
"jedi": {
"hashes": [
@@ -400,11 +452,11 @@
},
"mkdocs-material": {
"hashes": [
"sha256:762a71f82c1e291c3ff067cecd9d581557da777332fd98bc0af20fd5ab4a2dd0",
"sha256:b2c7174ecaa81fb1d62a5f4906f99fa0e7062ced8f9a14ec4f60b1bef9feebbf"
"sha256:0b394aa034b25a09a5874ae2a6ccc426fd81f5764e0991217b169e31cb0c1c0e",
"sha256:f5bb80a2c16d045d380edb2c5b05636af1bb709cb859bfaa9d01063a11df803f"
],
"index": "pypi",
"version": "==4.0.2"
"version": "==4.1.0"
},
"more-itertools": {
"hashes": [
@@ -445,10 +497,10 @@
},
"notebook": {
"hashes": [
"sha256:9ca7f597ce4f5a24611c589fa320a7af2861f0ca1dc20839129c91ae354453fe",
"sha256:c5011449a1a6d9f96bf65c4c2d6713802a21125476312b39c99010ccd7a2e2ed"
"sha256:18a98858c0331fb65a60f2ebb6439f8c0c4defd14ca363731b6cabc7f61624b4",
"sha256:cc027a62be0f7756e0ef3d2d98458c4d7f4b3566449fb1a05891207f5bd9a1bf"
],
"version": "==5.7.5"
"version": "==5.7.6"
},
"pandocfilters": {
"hashes": [
@@ -550,11 +602,11 @@
},
"pytest": {
"hashes": [
"sha256:067a1d4bf827ffdd56ad21bd46674703fce77c5957f6c1eef731f6146bfcef1c",
"sha256:9687049d53695ad45cf5fdc7bbd51f0c49f1ea3ecfc4b7f3fde7501b541f17f4"
"sha256:592eaa2c33fae68c7d75aacf042efc9f77b27c08a6224a4f59beab8d9a420523",
"sha256:ad3ad5c450284819ecde191a654c09b0ec72257a2c711b9633d677c71c9850c4"
],
"index": "pypi",
"version": "==4.3.0"
"version": "==4.3.1"
},
"pytest-cov": {
"hashes": [
@@ -586,19 +638,19 @@
},
"pyyaml": {
"hashes": [
"sha256:544a0050e76e9b60751c58617fa28c253ad5d23af2e5f0b1c250390bf90bb0df",
"sha256:594bf80477a58b6fd53e8b3f24ccf965c25eeeb6e05e4b1fb18c82c2d2090603",
"sha256:75e20ca689d0a2bf0c84f0e2028cc68ebef34b213fa66d73c410c53f870c49f4",
"sha256:994da68a1dc1050f290f8017f044172360b608c0f2562b47645ecc69d7a61c0a",
"sha256:ad902e00088c50bdced94a57b819c24fdadaeaed5494df7a9a67d63774f210fd",
"sha256:b11aff75875ffc73541c4e4b1ac2f5e21717c1fc4396238943b9a44d962e74e1",
"sha256:bc733b5a9047c3e4848c0e80eeacfa6a799139242606410260c5450d665ea58c",
"sha256:d960c68931b96bb215f385baa8ef867b8ebac66af60fa06cc1008f963848c7ad",
"sha256:dd461c04e6a91e4eef7d5b75c1fc1c7013d3f8d354033b16526baadddd524079",
"sha256:e4d6b5d6218a06f3141189d75c93876dd525a6d15f1b00ef4f274726c93719f1",
"sha256:f3c386fa12415bde8a0162745c4badf98fe171c6dfd67e54831f05ec88feeebb"
"sha256:1adecc22f88d38052fb787d959f003811ca858b799590a5eaa70e63dca50308c",
"sha256:436bc774ecf7c103814098159fbb84c2715d25980175292c648f2da143909f95",
"sha256:460a5a4248763f6f37ea225d19d5c205677d8d525f6a83357ca622ed541830c2",
"sha256:5a22a9c84653debfbf198d02fe592c176ea548cccce47553f35f466e15cf2fd4",
"sha256:7a5d3f26b89d688db27822343dfa25c599627bc92093e788956372285c6298ad",
"sha256:9372b04a02080752d9e6f990179a4ab840227c6e2ce15b95e1278456664cf2ba",
"sha256:a5dcbebee834eaddf3fa7366316b880ff4062e4bcc9787b78c7fbb4a26ff2dd1",
"sha256:aee5bab92a176e7cd034e57f46e9df9a9862a71f8f37cad167c6fc74c65f5b4e",
"sha256:c51f642898c0bacd335fc119da60baae0824f2cde95b0330b56c0553439f0673",
"sha256:c68ea4d3ba1705da1e0d85da6684ac657912679a649e8868bd850d2c299cce13",
"sha256:e23d0cc5299223dcc37885dae624f382297717e459ea24053709675a976a3e19"
],
"version": "==5.1b5"
"version": "==5.1"
},
"pyzmq": {
"hashes": [
@@ -661,10 +713,9 @@
},
"sqlalchemy": {
"hashes": [
"sha256:11ead7047ff3f394ed0d4b62aded6c5d970a9b718e1dc6add9f5e79442cc5b14"
"sha256:781fb7b9d194ed3fc596b8f0dd4623ff160e3e825dd8c15472376a438c19598b"
],
"index": "pypi",
"version": "==1.3.0"
"version": "==1.3.1"
},
"terminado": {
"hashes": [
@@ -689,15 +740,15 @@
},
"tornado": {
"hashes": [
"sha256:1a58f2d603476d5e462f7c28ca1dbb5ac7e51348b27a9cac849cdec3471101f8",
"sha256:33f93243cd46dd398e5d2bbdd75539564d1f13f25d704cfc7541db74066d6695",
"sha256:34e59401afcecf0381a28228daad8ed3275bcb726810654612d5e9c001f421b7",
"sha256:35817031611d2c296c69e5023ea1f9b5720be803e3bb119464bb2a0405d5cd70",
"sha256:666b335cef5cc2759c21b7394cff881f71559aaf7cb8c4458af5bb6cb7275b47",
"sha256:81203efb26debaaef7158187af45bc440796de9fb1df12a75b65fae11600a255",
"sha256:de274c65f45f6656c375cdf1759dbf0bc52902a1e999d12a35eb13020a641a53"
"sha256:1174dcb84d08887b55defb2cda1986faeeea715fff189ef3dc44cce99f5fca6b",
"sha256:2613fab506bd2aedb3722c8c64c17f8f74f4070afed6eea17f20b2115e445aec",
"sha256:44b82bc1146a24e5b9853d04c142576b4e8fa7a92f2e30bc364a85d1f75c4de2",
"sha256:457fcbee4df737d2defc181b9073758d73f54a6cfc1f280533ff48831b39f4a8",
"sha256:49603e1a6e24104961497ad0c07c799aec1caac7400a6762b687e74c8206677d",
"sha256:8c2f40b99a8153893793559919a355d7b74649a11e59f411b0b0a1793e160bc0",
"sha256:e1d897889c3b5a829426b7d52828fb37b28bc181cd598624e65c8be40ee3f7fa"
],
"version": "==6.0.1"
"version": "==6.0.2"
},
"traitlets": {
"hashes": [
@@ -746,26 +797,25 @@
},
"uvicorn": {
"hashes": [
"sha256:8d523d0a003a874245025295b0c1233a762402c0d4c3988017401c6b461c83e9"
"sha256:d700b65169820fc260f39402b7f966c178691daaa40cb376cad99d7cd737f772"
],
"index": "pypi",
"version": "==0.5.1"
"version": "==0.7.0b1"
},
"uvloop": {
"hashes": [
"sha256:198fe0c196056930ec6c4a0a878e531a66d15467ca7c74a875aa90271f0c6e3f",
"sha256:1c175f47d34b84e33c0e312f4987c927ea004afc3a5f05d2f0f610d71d0e4c89",
"sha256:1c47f197be8f0a3c651dd20be1e1bd43268186246f246d4e86c91e95a89e4865",
"sha256:3fd4943570d20e8cd4d9f0a3190ebd5cf040e5610b685e05c878128a11f7ad14",
"sha256:435e232869923fd2248e4ca0ad73e24a5b4debf40bed9dcde133cfe1bef98a7a",
"sha256:9cfdb966ae804c46b96c92207dfd2174935ffc70e706e42e1c94c60d16dbe860",
"sha256:a585781443eeb2edb858f8c08c503aac237a5f1bebf0c84ea8340cc337afa408",
"sha256:b296493e033846e46488a6aa227a75c790091f5ee5456ec637bb0badad1e8851",
"sha256:c684047c6cf6d697ba37872fb1b4489012ea91f3f802c8fbb9c367c4902e88dc",
"sha256:da5a59d8812188b57b5783c7fb78891d14dd1050b6259680e0dbd4253d7d0f64"
"sha256:0fcd894f6fc3226a962ee7ad895c4f52e3f5c3c55098e21efb17c071849a0573",
"sha256:2f31de1742c059c96cb76b91c5275b22b22b965c886ee1fced093fa27dde9e64",
"sha256:459e4649fcd5ff719523de33964aa284898e55df62761e7773d088823ccbd3e0",
"sha256:67867aafd6e0bc2c30a079603a85d83b94f23c5593b3cc08ec7e58ac18bf48e5",
"sha256:8c200457e6847f28d8bb91c5e5039d301716f5f2fce25646f5fb3fd65eda4a26",
"sha256:958906b9ca39eb158414fbb7d6b8ef1b7aee4db5c8e8e5d00fcbb69a1ce9dca7",
"sha256:ac1dca3d8f3ef52806059e81042ee397ac939e5a86c8a3cea55d6b087db66115",
"sha256:b284c22d8938866318e3b9d178142b8be316c52d16fcfe1560685a686718a021",
"sha256:c48692bf4587ce281d641087658eca275a5ad3b63c78297bbded96570ae9ce8f",
"sha256:fefc3b2b947c99737c348887db2c32e539160dcbeb7af9aa6b53db7a283538fe"
],
"markers": "sys_platform != 'win32' and sys_platform != 'cygwin' and platform_python_implementation != 'pypy'",
"version": "==0.12.1"
"version": "==0.12.2"
},
"wcwidth": {
"hashes": [

View File

@@ -39,7 +39,7 @@ COPY ./app /app
#### Bigger Applications
If you followed the section about creating <a href="" target="_blank">Bigger Applications with Multiple Files
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

View File

@@ -27,7 +27,7 @@ Doing it, you will receive notifications (in your email) whenever there's a new
## 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

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.

After

Width:  |  Height:  |  Size: 71 KiB

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.

View File

@@ -1,8 +1,42 @@
## Next release
## 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="htthttps://github.com/tiangolo/fastapi/pull/76" target="_blank">#76</a> by <a href="https://github.com/euri10" target="_blank">@euri10</a>.
* 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>.

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,7 +1,6 @@
from fastapi import FastAPI
from .routers import items
from .routers import users
from .routers import items, users
app = FastAPI()

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 startup_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,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,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

@@ -119,7 +119,7 @@ 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!}
```
@@ -129,7 +129,7 @@ But this time we are not adding *path operations* directly with the `FastAPI` `a
We import the other submodules that have `APIRouter`s:
```Python hl_lines="3 4"
```Python hl_lines="3"
{!./src/bigger_applications/app/main.py!}
```
@@ -141,21 +141,21 @@ As the file `app/routers/items.py` is part of the same Python package, we can im
The section:
```Python
from .routers import items
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, import the submodule `items` (the file at `app/routers/items.py`)...
* and from it, import the submodule `items` (the file at `app/routers/items.py`) and `users` (the file at `app/routers/users.py`)...
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 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 import items
from app.routers import items, users
```
!!! info
@@ -183,7 +183,7 @@ The `router` from `users` would overwrite the one from `items` and we wouldn't b
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!}
```

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

@@ -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

@@ -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

@@ -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

@@ -1,6 +1,8 @@
"""FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
__version__ = "0.8.0"
__version__ = "0.10.2"
from starlette.background import BackgroundTasks
from .applications import FastAPI
from .datastructures import UploadFile

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,6 +27,7 @@ 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
@@ -125,6 +137,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
@@ -215,13 +229,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)
@@ -258,7 +279,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(

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

@@ -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

@@ -68,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:
@@ -83,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
@@ -271,6 +277,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,

View File

@@ -57,12 +57,15 @@ 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'
- Concurrency and async / await: 'async.md'
- Deployment: 'deployment.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",

View File

@@ -13,7 +13,7 @@ fi
export PYTHONPATH=./docs/src
pytest --cov=fastapi --cov=tests --cov=docs/src --cov-report=term-missing ${@}
mypy fastapi --disallow-untyped-defs
mypy fastapi --disallow-untyped-defs --follow-imports=skip
if [ "${PYTHON_VERSION}" = '3.7' ]; then
echo "Skipping 'black' on 3.7. See issue https://github.com/ambv/black/issues/494"
else

View File

@@ -0,0 +1,110 @@
from typing import Dict
from fastapi import FastAPI
from pydantic import BaseModel
from starlette.testclient import TestClient
app = FastAPI()
class Items(BaseModel):
items: Dict[str, int]
@app.post("/foo")
def foo(items: Items):
return items.items
client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"paths": {
"/foo": {
"post": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Foo Post",
"operationId": "foo_foo_post",
"requestBody": {
"content": {
"application/json": {
"schema": {"$ref": "#/components/schemas/Items"}
}
},
"required": True,
},
}
}
},
"components": {
"schemas": {
"Items": {
"title": "Items",
"required": ["items"],
"type": "object",
"properties": {
"items": {
"title": "Items",
"type": "object",
"additionalProperties": {"type": "integer"},
}
},
},
"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"},
}
},
},
}
},
}
def test_additional_properties_schema():
response = client.get("/openapi.json")
assert response.status_code == 200
assert response.json() == openapi_schema
def test_additional_properties_post():
response = client.post("/foo", json={"items": {"foo": 1, "bar": 2}})
assert response.status_code == 200
assert response.json() == {"foo": 1, "bar": 2}

View File

View File

@@ -0,0 +1,131 @@
from starlette.testclient import TestClient
from async_sql_databases.tutorial001 import app
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"paths": {
"/notes/": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"title": "Response_Read_Notes",
"type": "array",
"items": {"$ref": "#/components/schemas/Note"},
}
}
},
}
},
"summary": "Read Notes Get",
"operationId": "read_notes_notes__get",
},
"post": {
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {"$ref": "#/components/schemas/Note"}
}
},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Create Note Post",
"operationId": "create_note_notes__post",
"requestBody": {
"content": {
"application/json": {
"schema": {"$ref": "#/components/schemas/NoteIn"}
}
},
"required": True,
},
},
}
},
"components": {
"schemas": {
"NoteIn": {
"title": "NoteIn",
"required": ["text", "completed"],
"type": "object",
"properties": {
"text": {"title": "Text", "type": "string"},
"completed": {"title": "Completed", "type": "boolean"},
},
},
"Note": {
"title": "Note",
"required": ["id", "text", "completed"],
"type": "object",
"properties": {
"id": {"title": "Id", "type": "integer"},
"text": {"title": "Text", "type": "string"},
"completed": {"title": "Completed", "type": "boolean"},
},
},
"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"},
}
},
},
}
},
}
def test_openapi_schema():
with TestClient(app) as client:
response = client.get("/openapi.json")
assert response.status_code == 200
assert response.json() == openapi_schema
def test_create_read():
with TestClient(app) as client:
note = {"text": "Foo bar", "completed": False}
response = client.post("/notes/", json=note)
assert response.status_code == 200
data = response.json()
assert data["text"] == note["text"]
assert data["completed"] == note["completed"]
assert "id" in data
response = client.get(f"/notes/")
assert response.status_code == 200
assert data in response.json()

View File

View File

@@ -0,0 +1,19 @@
import os
from pathlib import Path
from starlette.testclient import TestClient
from background_tasks.tutorial001 import app
client = TestClient(app)
def test():
log = Path("log.txt")
if log.is_file():
os.remove(log) # pragma: no cover
response = client.post("/send-notification/foo@example.com")
assert response.status_code == 200
assert response.json() == {"message": "Notification sent in the background"}
with open("./log.txt") as f:
assert "notification for foo@example.com: some notification" in f.read()

View File

@@ -0,0 +1,19 @@
import os
from pathlib import Path
from starlette.testclient import TestClient
from background_tasks.tutorial002 import app
client = TestClient(app)
def test():
log = Path("log.txt")
if log.is_file():
os.remove(log) # pragma: no cover
response = client.post("/send-notification/foo@example.com?q=some-query")
assert response.status_code == 200
assert response.json() == {"message": "Message sent"}
with open("./log.txt") as f:
assert "found query: some-query\nmessage to foo@example.com" in f.read()

View File

View File

@@ -0,0 +1,79 @@
from starlette.testclient import TestClient
from events.tutorial001 import app
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"paths": {
"/items/{item_id}": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Read Item Get",
"operationId": "read_item_items__item_id__get",
"parameters": [
{
"required": True,
"schema": {"title": "Item_Id", "type": "string"},
"name": "item_id",
"in": "path",
}
],
}
}
},
"components": {
"schemas": {
"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"},
}
},
},
}
},
}
def test_events():
with TestClient(app) as client:
response = client.get("/openapi.json")
assert response.status_code == 200
assert response.json() == openapi_schema
response = client.get("/items/foo")
assert response.status_code == 200
assert response.json() == {"name": "Fighters"}

View File

@@ -0,0 +1,34 @@
from starlette.testclient import TestClient
from events.tutorial002 import app
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"paths": {
"/items/": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
}
},
"summary": "Read Items Get",
"operationId": "read_items_items__get",
}
}
},
}
def test_events():
with TestClient(app) as client:
response = client.get("/openapi.json")
assert response.status_code == 200
assert response.json() == openapi_schema
response = client.get("/items/")
assert response.status_code == 200
assert response.json() == [{"name": "Foo"}]
with open("log.txt") as log:
assert "Application shutdown" in log.read()

View File

View File

@@ -0,0 +1,125 @@
from starlette.testclient import TestClient
from extra_models.tutorial003 import app
client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"paths": {
"/items/{item_id}": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"title": "Response_Read_Item",
"anyOf": [
{"$ref": "#/components/schemas/PlaneItem"},
{"$ref": "#/components/schemas/CarItem"},
],
}
}
},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Read Item Get",
"operationId": "read_item_items__item_id__get",
"parameters": [
{
"required": True,
"schema": {"title": "Item_Id", "type": "string"},
"name": "item_id",
"in": "path",
}
],
}
}
},
"components": {
"schemas": {
"PlaneItem": {
"title": "PlaneItem",
"required": ["description", "size"],
"type": "object",
"properties": {
"description": {"title": "Description", "type": "string"},
"type": {"title": "Type", "type": "string", "default": "plane"},
"size": {"title": "Size", "type": "integer"},
},
},
"CarItem": {
"title": "CarItem",
"required": ["description"],
"type": "object",
"properties": {
"description": {"title": "Description", "type": "string"},
"type": {"title": "Type", "type": "string", "default": "car"},
},
},
"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"},
}
},
},
}
},
}
def test_openapi_schema():
response = client.get("/openapi.json")
assert response.status_code == 200
assert response.json() == openapi_schema
def test_get_car():
response = client.get("/items/item1")
assert response.status_code == 200
assert response.json() == {
"description": "All my friends drive a low rider",
"type": "car",
}
def test_get_plane():
response = client.get("/items/item2")
assert response.status_code == 200
assert response.json() == {
"description": "Music is my aeroplane, it's my aeroplane",
"type": "plane",
"size": 5,
}

View File

@@ -0,0 +1,60 @@
from starlette.testclient import TestClient
from extra_models.tutorial004 import app
client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"paths": {
"/items/": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"title": "Response_Read_Items",
"type": "array",
"items": {"$ref": "#/components/schemas/Item"},
}
}
},
}
},
"summary": "Read Items Get",
"operationId": "read_items_items__get",
}
}
},
"components": {
"schemas": {
"Item": {
"title": "Item",
"required": ["name", "description"],
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},
"description": {"title": "Description", "type": "string"},
},
}
}
},
}
def test_openapi_schema():
response = client.get("/openapi.json")
assert response.status_code == 200
assert response.json() == openapi_schema
def test_get_items():
response = client.get("/items/")
assert response.status_code == 200
assert response.json() == [
{"name": "Foo", "description": "There comes my hero"},
{"name": "Red", "description": "It's my aeroplane"},
]

View File

@@ -0,0 +1,88 @@
from starlette.testclient import TestClient
from query_params_str_validations.tutorial011 import app
client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"paths": {
"/items/": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Read Items Get",
"operationId": "read_items_items__get",
"parameters": [
{
"required": False,
"schema": {
"title": "Q",
"type": "array",
"items": {"type": "string"},
},
"name": "q",
"in": "query",
}
],
}
}
},
"components": {
"schemas": {
"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"},
}
},
},
}
},
}
def test_openapi_schema():
response = client.get("/openapi.json")
assert response.status_code == 200
assert response.json() == openapi_schema
def test_multi_query_values():
url = "/items/?q=foo&q=bar"
response = client.get(url)
assert response.status_code == 200
assert response.json() == {"q": ["foo", "bar"]}

53
tests/test_ws_router.py Normal file
View File

@@ -0,0 +1,53 @@
from fastapi import APIRouter, FastAPI
from starlette.testclient import TestClient
from starlette.websockets import WebSocket
router = APIRouter()
prefix_router = APIRouter()
app = FastAPI()
@app.websocket_route("/")
async def index(websocket: WebSocket):
await websocket.accept()
await websocket.send_text("Hello, world!")
await websocket.close()
@router.websocket_route("/router")
async def routerindex(websocket: WebSocket):
await websocket.accept()
await websocket.send_text("Hello, router!")
await websocket.close()
@prefix_router.websocket_route("/")
async def routerprefixindex(websocket: WebSocket):
await websocket.accept()
await websocket.send_text("Hello, router with prefix!")
await websocket.close()
app.include_router(router)
app.include_router(prefix_router, prefix="/prefix")
def test_app():
client = TestClient(app)
with client.websocket_connect("/") as websocket:
data = websocket.receive_text()
assert data == "Hello, world!"
def test_router():
client = TestClient(app)
with client.websocket_connect("/router") as websocket:
data = websocket.receive_text()
assert data == "Hello, router!"
def test_prefix_router():
client = TestClient(app)
with client.websocket_connect("/prefix/") as websocket:
data = websocket.receive_text()
assert data == "Hello, router with prefix!"