Compare commits

..

51 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
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
82 changed files with 2113 additions and 262 deletions

1
.gitignore vendored
View File

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

View File

@@ -15,3 +15,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

@@ -31,10 +31,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 +116,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 +199,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 +344,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

@@ -377,6 +377,10 @@ All that is what powers FastAPI (through Starlette) and what makes it have such
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.

View File

@@ -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
@@ -81,18 +116,6 @@ docker run -d --name mycontainer -p 80:80 myimage
Now you have an optimized FastAPI server in a Docker container. Auto-tuned for your current server (and number of CPU cores).
#### Bigger Applications
If you followed the section about creating <a href="" 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
```
### Check it
You should be able to check it in your Docker container's URL, for example: <a href="http://192.168.99.100/items/5?q=somequery" target="_blank">http://192.168.99.100/items/5?q=somequery</a> or <a href="http://127.0.0.1/items/5?q=somequery" target="_blank">http://127.0.0.1/items/5?q=somequery</a> (or equivalent, using your Docker host).
@@ -216,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

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

@@ -31,10 +31,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 +116,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 +199,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 +344,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.

View File

@@ -1,4 +1,64 @@
## Next
## 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="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

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,8 @@
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"])

View File

@@ -3,11 +3,11 @@ from fastapi import APIRouter
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}

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

@@ -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,13 +101,12 @@ 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"
{!./src/bigger_applications/app/routers/items.py!}
```
## The main `FastAPI`
Now, let's see the module at `app/main.py`.
@@ -118,17 +119,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 +141,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 +168,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"
{!./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.
@@ -216,23 +219,25 @@ It will include all the routes from that router as part of it.
### Include an `APIRouter` with a prefix
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`.
And we can also add a list of `tags` that will be applied to all the *path operations* included in this router:
```Python hl_lines="9"
{!./src/bigger_applications/app/main.py!}
@@ -245,8 +250,12 @@ The end result is that the item paths are now:
...as we intended.
And they are marked with a list of tags that contain a single string `"items"`.
These "tags" are especially useful for the automatic interactive documentation systems (using OpenAPI).
!!! check
The `prefix` parameter is (as in many other cases) just a feature from **FastAPI** to help you avoid code duplication.
The `prefix` and `tags` parameters are (as in many other cases) just a feature from **FastAPI** to help you avoid code duplication.
!!! tip
@@ -274,11 +283,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">

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

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

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

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

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

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

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

@@ -270,7 +270,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>.

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

@@ -69,7 +69,7 @@ To learn more about the options, check Starlette's documentation for:
If your file is named `main.py`, run your application with:
```bash
uvicorn main:app --debug
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>.

View File

@@ -1,8 +1,11 @@
"""FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
__version__ = "0.6.4"
__version__ = "0.10.2"
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

@@ -176,8 +176,10 @@ 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
) -> None:
self.router.include_router(router, prefix=prefix, tags=tags)
def get(
self,

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 = (
@@ -124,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
@@ -214,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)
@@ -257,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(
@@ -323,6 +349,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 +365,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 +387,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

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

@@ -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
@@ -241,7 +243,9 @@ 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
) -> None:
if prefix:
assert prefix.startswith("/"), "A path prefix must start with '/'"
assert not prefix.endswith(
@@ -254,7 +258,7 @@ class APIRouter(routing.Router):
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,
@@ -273,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

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

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

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

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

2
scripts/lint.sh Normal file → Executable file
View File

@@ -3,4 +3,4 @@ set -x
autoflake --remove-all-unused-imports --recursive --remove-unused-variables --in-place docs/src/ fastapi tests --exclude=__init__.py
black fastapi tests docs/src
isort --multi-line=3 --trailing-comma --force-grid-wrap=0 --combine-as --line-width 88 --recursive --apply fastapi tests docs/src
isort --multi-line=3 --trailing-comma --force-grid-wrap=0 --combine-as --line-width 88 --recursive --thirdparty fastapi --apply fastapi tests docs/src

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

0
scripts/test-cov-html.sh Normal file → Executable file
View File

View File

@@ -13,10 +13,10 @@ 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
black fastapi tests --check
fi
isort --multi-line=3 --trailing-comma --force-grid-wrap=0 --combine-as --line-width 88 --recursive --check-only fastapi tests
isort --multi-line=3 --trailing-comma --force-grid-wrap=0 --combine-as --line-width 88 --recursive --check-only --thirdparty fastapi fastapi tests

17
scripts/trigger-docker.sh Executable file
View File

@@ -0,0 +1,17 @@
#!/usr/bin/env bash
set -e
set -x
body='{
"request": {
"branch":"master"
}}'
curl -s -X POST \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-H "Travis-API-Version: 3" \
-H "Authorization: token $TRAVIS_TOKEN" \
-d "$body" \
https://api.travis-ci.org/repo/tiangolo%2Fuvicorn-gunicorn-fastapi-docker/requests

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

@@ -0,0 +1,7 @@
import pytest
from fastapi import UploadFile
def test_upload_file_invalid():
with pytest.raises(ValueError):
UploadFile.validate("not a Starlette UploadFile")

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"]}

View File

@@ -39,7 +39,39 @@ openapi_schema = {
"required": True,
},
}
}
},
"/uploadfile/": {
"post": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Create Upload File Post",
"operationId": "create_upload_file_uploadfile__post",
"requestBody": {
"content": {
"multipart/form-data": {
"schema": {
"$ref": "#/components/schemas/Body_create_upload_file"
}
}
},
"required": True,
},
}
},
},
"components": {
"schemas": {
@@ -51,6 +83,14 @@ openapi_schema = {
"file": {"title": "File", "type": "string", "format": "binary"}
},
},
"Body_create_upload_file": {
"title": "Body_create_upload_file",
"required": ["file"],
"type": "object",
"properties": {
"file": {"title": "File", "type": "string", "format": "binary"}
},
},
"ValidationError": {
"title": "ValidationError",
"required": ["loc", "msg", "type"],
@@ -131,3 +171,14 @@ def test_post_large_file(tmpdir):
response = client.post("/files/", files={"file": open(path, "rb")})
assert response.status_code == 200
assert response.json() == {"file_size": default_pydantic_max_size + 1}
def test_post_upload_file(tmpdir):
path = os.path.join(tmpdir, "test.txt")
with open(path, "wb") as file:
file.write(b"<file content>")
client = TestClient(app)
response = client.post("/uploadfile/", files={"file": open(path, "rb")})
assert response.status_code == 200
assert response.json() == {"filename": "test.txt"}

View File

@@ -1,4 +1,5 @@
import os
from pathlib import Path
from starlette.testclient import TestClient
@@ -45,10 +46,11 @@ openapi_schema = {
"schemas": {
"Body_create_file": {
"title": "Body_create_file",
"required": ["file", "token"],
"required": ["file", "fileb", "token"],
"type": "object",
"properties": {
"file": {"title": "File", "type": "string", "format": "binary"},
"fileb": {"title": "Fileb", "type": "string", "format": "binary"},
"token": {"title": "Token", "type": "string"},
},
},
@@ -94,20 +96,32 @@ file_required = {
"loc": ["body", "file"],
"msg": "field required",
"type": "value_error.missing",
}
},
{
"loc": ["body", "fileb"],
"msg": "field required",
"type": "value_error.missing",
},
]
}
token_required = {
"detail": [
{
"loc": ["body", "fileb"],
"msg": "field required",
"type": "value_error.missing",
},
{
"loc": ["body", "token"],
"msg": "field required",
"type": "value_error.missing",
}
},
]
}
# {'detail': [, {'loc': ['body', 'token'], 'msg': 'field required', 'type': 'value_error.missing'}]}
file_and_token_required = {
"detail": [
{
@@ -115,6 +129,11 @@ file_and_token_required = {
"msg": "field required",
"type": "value_error.missing",
},
{
"loc": ["body", "fileb"],
"msg": "field required",
"type": "value_error.missing",
},
{
"loc": ["body", "token"],
"msg": "field required",
@@ -153,14 +172,24 @@ def test_post_file_no_token(tmpdir):
assert response.json() == token_required
def test_post_file_and_token(tmpdir):
path = os.path.join(tmpdir, "test.txt")
with open(path, "wb") as file:
file.write(b"<file content>")
def test_post_files_and_token(tmpdir):
patha = Path(tmpdir) / "test.txt"
pathb = Path(tmpdir) / "testb.txt"
patha.write_text("<file content>")
pathb.write_text("<file b content>")
client = TestClient(app)
response = client.post(
"/files/", data={"token": "foo"}, files={"file": open(path, "rb")}
"/files/",
data={"token": "foo"},
files={
"file": patha.open("rb"),
"fileb": ("testb.txt", pathb.open("rb"), "text/plain"),
},
)
assert response.status_code == 200
assert response.json() == {"file_size": 14, "token": "foo"}
assert response.json() == {
"file_size": 14,
"token": "foo",
"fileb_content_type": "text/plain",
}

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!"