Compare commits
110 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a274d18b4 | ||
|
|
62626b0175 | ||
|
|
c8df3ae57c | ||
|
|
6f7f9268f6 | ||
|
|
50653e205f | ||
|
|
50a280b17b | ||
|
|
c1da3b38a3 | ||
|
|
e68a68c97c | ||
|
|
907e613ff2 | ||
|
|
f0fc2fad2c | ||
|
|
ad471307e2 | ||
|
|
2bd775988f | ||
|
|
c59ddc8a24 | ||
|
|
378b39bbbc | ||
|
|
37e0306517 | ||
|
|
fad3a9e1dc | ||
|
|
b35b0a9a90 | ||
|
|
1426b6200a | ||
|
|
40e5f3764e | ||
|
|
e5c75807ce | ||
|
|
deff2b6678 | ||
|
|
7c572fdb3a | ||
|
|
ae970638cf | ||
|
|
deae92bba1 | ||
|
|
f806ba642a | ||
|
|
5a3cf863da | ||
|
|
dd6ab23b62 | ||
|
|
0449499188 | ||
|
|
4dc7b32861 | ||
|
|
08d849d5c5 | ||
|
|
714e68b5f0 | ||
|
|
3d4f59f35a | ||
|
|
3ce2920fef | ||
|
|
825f397918 | ||
|
|
b390e32372 | ||
|
|
b7d184363f | ||
|
|
2ddb804940 | ||
|
|
a2c9f666b5 | ||
|
|
1594222e39 | ||
|
|
dc1e94d05f | ||
|
|
b0f7961b65 | ||
|
|
1c2ecbb89a | ||
|
|
5a6e47bd49 | ||
|
|
58872dca74 | ||
|
|
9b04593260 | ||
|
|
6d77e2ac5f | ||
|
|
b16ca54c30 | ||
|
|
834723cf2c | ||
|
|
eda9b28338 | ||
|
|
7514ac6fb0 | ||
|
|
25fb4239cc | ||
|
|
65568065e0 | ||
|
|
95679ca5e6 | ||
|
|
84a300ef84 | ||
|
|
c6d28c8209 | ||
|
|
3984e9b8ac | ||
|
|
aa0bca7bb2 | ||
|
|
9778542ba6 | ||
|
|
34c34c68d2 | ||
|
|
c64f8346ae | ||
|
|
4f852878d6 | ||
|
|
59bc4b7d69 | ||
|
|
3cae2ccbae | ||
|
|
e21ba7646a | ||
|
|
10498fcfbd | ||
|
|
3f7b7837fb | ||
|
|
1c26e77a66 | ||
|
|
108c2f3c0e | ||
|
|
f2fd948ce3 | ||
|
|
b269655b7f | ||
|
|
a174f01901 | ||
|
|
9b76ad1870 | ||
|
|
f1c367aead | ||
|
|
8291c664b9 | ||
|
|
e8472ebbd1 | ||
|
|
f4391e2a87 | ||
|
|
11c755bee3 | ||
|
|
35054a450c | ||
|
|
da60de33c1 | ||
|
|
c0758dfe71 | ||
|
|
1112ac7538 | ||
|
|
ac2b18bf40 | ||
|
|
b89a24448b | ||
|
|
e76216dd26 | ||
|
|
123d778a0c | ||
|
|
829ad209a6 | ||
|
|
b15a65c37e | ||
|
|
0eed798aac | ||
|
|
2caca42b9e | ||
|
|
7658d0af16 | ||
|
|
c14ec50f73 | ||
|
|
6b6ea0da2e | ||
|
|
0b9fe62a10 | ||
|
|
1f03e85f06 | ||
|
|
b98bf178a6 | ||
|
|
bbd2198fa2 | ||
|
|
e2723e8480 | ||
|
|
1896153d58 | ||
|
|
770b4421f9 | ||
|
|
e89aacbdf7 | ||
|
|
cf25291650 | ||
|
|
13772fbd11 | ||
|
|
1d69b6f480 | ||
|
|
01d6aa3dd1 | ||
|
|
74db8ddf9b | ||
|
|
819b3b2516 | ||
|
|
76fb2879ed | ||
|
|
daaf654868 | ||
|
|
6e0553b4cf | ||
|
|
8e1ecaf221 |
1
.gitignore
vendored
@@ -11,3 +11,4 @@ site
|
||||
coverage.xml
|
||||
.netlify
|
||||
test.db
|
||||
log.txt
|
||||
|
||||
10
.travis.yml
@@ -1,10 +1,12 @@
|
||||
dist: xenial
|
||||
|
||||
language: python
|
||||
|
||||
cache: pip
|
||||
|
||||
python:
|
||||
- "3.6"
|
||||
- "3.7-dev"
|
||||
- "3.7"
|
||||
|
||||
install:
|
||||
- pip install flit
|
||||
@@ -15,3 +17,9 @@ script:
|
||||
|
||||
after_script:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
|
||||
deploy:
|
||||
provider: script
|
||||
script: bash scripts/trigger-docker.sh
|
||||
on:
|
||||
branch: master
|
||||
|
||||
4
Pipfile
@@ -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
@@ -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",
|
||||
|
||||
17
README.md
@@ -14,6 +14,9 @@
|
||||
<a href="https://pypi.org/project/fastapi" target="_blank">
|
||||
<img src="https://badge.fury.io/py/fastapi.svg" alt="Package version">
|
||||
</a>
|
||||
<a href="https://gitter.im/tiangolo/fastapi?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge" target="_blank">
|
||||
<img src="https://badges.gitter.im/tiangolo/fastapi.svg" alt="Join the chat at https://gitter.im/tiangolo/fastapi">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
---
|
||||
@@ -31,10 +34,10 @@ The key features are:
|
||||
* **Fast**: Very high performance, on par with **NodeJS** and **Go** (thanks to Starlette and Pydantic). [One of the fastest Python frameworks available](#performance).
|
||||
|
||||
* **Fast to code**: Increase the speed to develop features by about 200% to 300% *.
|
||||
* **Less bugs**: Reduce about 40% of human (developer) induced errors. *
|
||||
* **Fewer bugs**: Reduce about 40% of human (developer) induced errors. *
|
||||
* **Intuitive**: Great editor support. <abbr title="also known as auto-complete, autocompletion, IntelliSense">Completion</abbr> everywhere. Less time debugging.
|
||||
* **Easy**: Designed to be easy to use and learn. Less time reading docs.
|
||||
* **Short**: Minimize code duplication. Multiple features from each parameter declaration. Less bugs.
|
||||
* **Short**: Minimize code duplication. Multiple features from each parameter declaration. Fewer bugs.
|
||||
* **Robust**: Get production-ready code. With automatic interactive documentation.
|
||||
* **Standards-based**: Based on (and fully compatible with) the open standards for APIs: <a href="https://github.com/OAI/OpenAPI-Specification" target="_blank">OpenAPI</a> (previously known as Swagger) and <a href="http://json-schema.org/" target="_blank">JSON Schema</a>.
|
||||
|
||||
@@ -116,17 +119,17 @@ If you don't know, check the _"In a hurry?"_ section about <a href="https://fast
|
||||
Run the server with:
|
||||
|
||||
```bash
|
||||
uvicorn main:app --debug
|
||||
uvicorn main:app --reload
|
||||
```
|
||||
|
||||
<details markdown="1">
|
||||
<summary>About the command <code>uvicorn main:app --debug</code>...</summary>
|
||||
<summary>About the command <code>uvicorn main:app --reload</code>...</summary>
|
||||
|
||||
The command `uvicorn main:app` refers to:
|
||||
|
||||
* `main`: the file `main.py` (the Python "module").
|
||||
* `app`: the object created inside of `main.py` with the line `app = FastAPI()`.
|
||||
* `--debug`: make the server restart after code changes. Only do this for development.
|
||||
* `--reload`: make the server restart after code changes. Only do this for development.
|
||||
|
||||
</details>
|
||||
|
||||
@@ -199,7 +202,7 @@ def create_item(item_id: int, item: Item):
|
||||
return {"item_name": item.name, "item_id": item_id}
|
||||
```
|
||||
|
||||
The server should reload automatically (because you added `--debug` to the `uvicorn` command above).
|
||||
The server should reload automatically (because you added `--reload` to the `uvicorn` command above).
|
||||
|
||||
### Interactive API docs upgrade
|
||||
|
||||
@@ -344,7 +347,7 @@ For a more complete example including more features, see the <a href="https://fa
|
||||
|
||||
## Performance
|
||||
|
||||
Independent TechEmpower benchmarks show **FastAPI** applications running under Uvicorn as <a href="https://www.techempower.com/benchmarks/#section=test&runid=a979de55-980d-4721-a46f-77298b3f3923&hw=ph&test=fortune&l=zijzen-7" target="_blank">one of the fastest Python frameworks available</a>, only below Starlette and Uvicorn themselves (used internally by FastAPI). (*)
|
||||
Independent TechEmpower benchmarks show **FastAPI** applications running under Uvicorn as <a href="https://www.techempower.com/benchmarks/#section=test&runid=7464e520-0dc2-473d-bd34-dbdfd7e85911&hw=ph&test=query&l=zijzen-7" target="_blank">one of the fastest Python frameworks available</a>, only below Starlette and Uvicorn themselves (used internally by FastAPI). (*)
|
||||
|
||||
To understand more about it, see the section <a href="https://fastapi.tiangolo.com/benchmarks/" target="_blank">Benchmarks</a>.
|
||||
|
||||
|
||||
@@ -123,7 +123,7 @@ There are several Flask REST frameworks, but after investing the time and work i
|
||||
|
||||
### <a href="https://marshmallow.readthedocs.io/en/3.0/" target="_blank">Marshmallow</a>
|
||||
|
||||
One of the main features needed by API systems is data "<abbr title="also called marshalling, convertion">serialization</abbr>" which is taking data from the code (Python) and converting it into something that can be sent through the network. For example, converting an object containing data from a database into a JSON object. Converting `datetime` objects into strings, etc.
|
||||
One of the main features needed by API systems is data "<abbr title="also called marshalling, conversion">serialization</abbr>" which is taking data from the code (Python) and converting it into something that can be sent through the network. For example, converting an object containing data from a database into a JSON object. Converting `datetime` objects into strings, etc.
|
||||
|
||||
Another big feature needed by APIs is data validation, making sure that the data is valid, given certain parameters. For example, that some field is an `int`, and not some random string. This is especially useful for incoming data.
|
||||
|
||||
@@ -365,7 +365,7 @@ It is the recommended server for Starlette and **FastAPI**.
|
||||
!!! check "**FastAPI** recommends it as"
|
||||
The main web server to run **FastAPI** applications.
|
||||
|
||||
You can combine it with Gunicorn, to have an asynchronous multiprocess server.
|
||||
You can combine it with Gunicorn, to have an asynchronous multi-process server.
|
||||
|
||||
Check more details in the <a href="/deployment/" target="_blank">Deployment</a> section.
|
||||
|
||||
|
||||
@@ -329,7 +329,7 @@ So, about the egg and the chicken, how do you call the first `async` function?
|
||||
|
||||
If you are working with **FastAPI** you don't have to worry about that, because that "first" function will be your path operation function, and FastAPI will know how to do the right thing.
|
||||
|
||||
But if you want to use `async` / `await` without FastAPI, <a href="https://docs.python.org/3/library/asyncio-task.html#coroutine" target="_blank">check the official Python docs</a>
|
||||
But if you want to use `async` / `await` without FastAPI, <a href="https://docs.python.org/3/library/asyncio-task.html#coroutine" target="_blank">check the official Python docs</a>.
|
||||
|
||||
|
||||
### Other forms of asynchronous code
|
||||
@@ -362,3 +362,43 @@ Let's see the same phrase from above:
|
||||
That should make more sense now.
|
||||
|
||||
All that is what powers FastAPI (through Starlette) and what makes it have such an impressive performance.
|
||||
|
||||
|
||||
## Very Technical Details
|
||||
|
||||
!!! warning
|
||||
You can probably skip this.
|
||||
|
||||
These are very technical details of how **FastAPI** works underneath.
|
||||
|
||||
If you have quite some technical knowledge (co-routines, threads, blocking, etc) and are curious about how FastAPI handles `async def` vs normal `def`, go ahead.
|
||||
|
||||
### Path operation functions
|
||||
|
||||
When you declare a *path operation function* with normal `def` instead of `async def`, it is run in an external threadpool that is then awaited, instead of being called directly (as it would block the server).
|
||||
|
||||
If you are coming from another async framework that does not work in the way described above and you are used to define trivial compute-only *path operation functions* with plain `def` for a tiny performance gain (about 100 nanoseconds), please note that in **FastAPI** the effect would be quite opposite. In these cases, it's better to use `async def` unless your *path operation functions* use code that performs blocking <abbr title="Input/Output: disk reading or writing, network communications.">IO</abbr>.
|
||||
|
||||
Still, in both situations, chances are that **FastAPI** will <a href="https://fastapi.tiangolo.com/#performance" target="_blank">still be faster</a> than (or at least comparable to) your previous framework.
|
||||
|
||||
### Dependencies
|
||||
|
||||
The same applies for dependencies. If a dependency is a standard `def` function instead of `async def`, it is run in the external threadpool.
|
||||
|
||||
### Sub-dependencies
|
||||
|
||||
You can have multiple dependencies and sub-dependencies requiring each other (as parameters of the function definitions), some of them might be created with `async def` and some with normal `def`. It would still work, and the ones created with normal `def` would be called on an external thread instead of being "awaited".
|
||||
|
||||
### Other utility functions
|
||||
|
||||
Any other utility function that you call directly can be created with normal `def` or `async def` and FastAPI won't affect the way you call it.
|
||||
|
||||
This is in contrast to the functions that FastAPI calls for you: *path operation functions* and dependencies.
|
||||
|
||||
If your utility function is a normal function with `def`, it will be called directly (as you write it in your code), not in a threadpool, if the function is created with `async def` then you should await for that function when you call it in your code.
|
||||
|
||||
---
|
||||
|
||||
Again, these are very technical details that would probably be useful if you came searching for them.
|
||||
|
||||
Otherwise, you should be good with the guidelines from the section above: <a href="#in-a-hurry">In a hurry?</a>.
|
||||
|
||||
@@ -74,7 +74,7 @@ All the documentation is in Markdown format in the directory `./docs`.
|
||||
|
||||
Many of the tutorials have blocks of code.
|
||||
|
||||
In most of the cases, these blocks of code are actual complete applicactions that can be run as is.
|
||||
In most of the cases, these blocks of code are actual complete applications that can be run as is.
|
||||
|
||||
In fact, those blocks of code are not written inside the Markdown, they are Python files in the `./docs/src/` directory.
|
||||
|
||||
@@ -106,7 +106,7 @@ That way, you can edit the documentation/source files and see the changes live.
|
||||
And if you run the examples with, e.g.:
|
||||
|
||||
```bash
|
||||
uvicorn tutorial001:app --debug
|
||||
uvicorn tutorial001:app --reload
|
||||
```
|
||||
|
||||
as Uvicorn by default will use the port `8000`, the documentation on port `8008` won't clash.
|
||||
|
||||
@@ -26,7 +26,7 @@ But you can still change and update all the configurations with environment vari
|
||||
To see all the configurations and options, go to the Docker image page: <a href="https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker" target="_blank">tiangolo/uvicorn-gunicorn-fastapi</a>.
|
||||
|
||||
|
||||
### Build your Image
|
||||
### Create a `Dockerfile`
|
||||
|
||||
* Go to your project directory.
|
||||
* Create a `Dockerfile` with:
|
||||
@@ -37,6 +37,37 @@ FROM tiangolo/uvicorn-gunicorn-fastapi:python3.7
|
||||
COPY ./app /app
|
||||
```
|
||||
|
||||
#### Bigger Applications
|
||||
|
||||
If you followed the section about creating <a href="https://fastapi.tiangolo.com/tutorial/bigger-applications/" target="_blank">Bigger Applications with Multiple Files
|
||||
</a>, your `Dockerfile` might instead look like:
|
||||
|
||||
```Dockerfile
|
||||
FROM tiangolo/uvicorn-gunicorn-fastapi:python3.7
|
||||
|
||||
COPY ./app /app/app
|
||||
```
|
||||
|
||||
#### Raspberry Pi and other architectures
|
||||
|
||||
If you are running Docker in a Raspberry Pi (that has an ARM processor) or any other architecture, you can create a `Dockerfile` from scratch, based on a Python base image (that is multi-architecture) and use Uvicorn alone.
|
||||
|
||||
In this case, your `Dockerfile` could look like:
|
||||
|
||||
```Dockerfile
|
||||
FROM python:3.7
|
||||
|
||||
RUN pip install fastapi uvicorn
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
COPY ./app /app
|
||||
|
||||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]
|
||||
```
|
||||
|
||||
### Create the **FastAPI** Code
|
||||
|
||||
* Create an `app` directory and enter in it.
|
||||
* Create a `main.py` file with:
|
||||
|
||||
@@ -65,6 +96,8 @@ def read_item(item_id: int, q: str = None):
|
||||
└── Dockerfile
|
||||
```
|
||||
|
||||
### Build the Docker image
|
||||
|
||||
* Go to the project directory (in where your `Dockerfile` is, containing your `app` directory).
|
||||
* Build your FastAPI image:
|
||||
|
||||
@@ -72,6 +105,8 @@ def read_item(item_id: int, q: str = None):
|
||||
docker build -t myimage .
|
||||
```
|
||||
|
||||
### Start the Docker container
|
||||
|
||||
* Run a container based on your image:
|
||||
|
||||
```bash
|
||||
@@ -145,7 +180,7 @@ Now, from a developer's perspective, here are several things to have in mind whi
|
||||
* It goes encrypted, but the encrypted contents are the same HTTP protocol.
|
||||
|
||||
|
||||
It is a common practice to have one program/HTTP server runing in the server (the machine, host, etc) and managing all the HTTPS parts, sending the decrypted HTTP requests to the actual HTTP application running in the same server (the **FastAPI** application, in this case), take the HTTP response from the application, encrypt it using the appropriate certificate and sending it back to the client using HTTPS. This server is ofter called a <a href="https://en.wikipedia.org/wiki/TLS_termination_proxy" target="_blank">TLS Termination Proxy</a>.
|
||||
It is a common practice to have one program/HTTP server running in the server (the machine, host, etc) and managing all the HTTPS parts, sending the decrypted HTTP requests to the actual HTTP application running in the same server (the **FastAPI** application, in this case), take the HTTP response from the application, encrypt it using the appropriate certificate and sending it back to the client using HTTPS. This server is ofter called a <a href="https://en.wikipedia.org/wiki/TLS_termination_proxy" target="_blank">TLS Termination Proxy</a>.
|
||||
|
||||
|
||||
### Let's Encrypt
|
||||
@@ -204,7 +239,7 @@ You can deploy **FastAPI** directly without Docker too.
|
||||
|
||||
You just need to install <a href="https://www.uvicorn.org/" target="_blank">Uvicorn</a> (or any other ASGI server).
|
||||
|
||||
And run your application the same way you have done in the tutorials, but without the `--debug` option, e.g.:
|
||||
And run your application the same way you have done in the tutorials, but without the `--reload` option, e.g.:
|
||||
|
||||
```bash
|
||||
uvicorn main:app --host 0.0.0.0 --port 80
|
||||
|
||||
@@ -71,7 +71,7 @@ my_second_user: User = User(**second_user_data)
|
||||
|
||||
### Editor support
|
||||
|
||||
All the framework was designed to be easy and intuitive to use, all the decisons where tested on multiple editors even before starting development, to ensure the best development experience.
|
||||
All the framework was designed to be easy and intuitive to use, all the decisions where tested on multiple editors even before starting development, to ensure the best development experience.
|
||||
|
||||
In the last Python developer survey it was clear <a href="https://www.jetbrains.com/research/python-developers-survey-2017/#tools-and-features" target="_blank">that the most used feature is "autocompletion"</a>.
|
||||
|
||||
@@ -89,7 +89,7 @@ Here's how your editor might help you:
|
||||
|
||||

|
||||
|
||||
You will get completion in code you might even consider imposible before. As for example, the `price` key inside a JSON body (that could have been nested) that comes from a request.
|
||||
You will get completion in code you might even consider impossible before. As for example, the `price` key inside a JSON body (that could have been nested) that comes from a request.
|
||||
|
||||
No more typing the wrong key names, coming back and forth between docs, or scrolling up and down to find if you finally used `username` or `user_name`.
|
||||
|
||||
@@ -201,4 +201,4 @@ With **FastAPI** you get all of **Pydantic**'s features (as FastAPI is based on
|
||||
* You can have deeply **nested JSON** objects and have them all validated and annotated.
|
||||
* **Extendible**:
|
||||
* Pydantic allows custom data types to be defined or you can extend validation with methods on a model decorated with the validator decorator.
|
||||
* 100% test coverage.
|
||||
* 100% test coverage.
|
||||
|
||||
@@ -24,10 +24,19 @@ There you can select "Releases only".
|
||||
|
||||
Doing it, you will receive notifications (in your email) whenever there's a new release (a new version) of **FastAPI** with bug fixes and new features.
|
||||
|
||||
## Join the chat
|
||||
|
||||
<a href="https://gitter.im/tiangolo/fastapi?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge" target="_blank">
|
||||
<img src="https://badges.gitter.im/tiangolo/fastapi.svg" alt="Join the chat at https://gitter.im/tiangolo/fastapi">
|
||||
</a>
|
||||
|
||||
Join the chat on Gitter: <a href="https://gitter.im/tiangolo/fastapi" target="_blank">https://gitter.im/tiangolo/fastapi</a>.
|
||||
|
||||
There you can ask quick questions, help others, share ideas, etc.
|
||||
|
||||
## Connect with the author
|
||||
|
||||
You can connect with me (Sebastián Ramírez / `tiangolo`), the author.
|
||||
You can connect with <a href="https://tiangolo.com" target="_blank">me (Sebastián Ramírez / `tiangolo`)</a>, the author.
|
||||
|
||||
You can:
|
||||
|
||||
|
||||
83
docs/history-design-future.md
Normal file
@@ -0,0 +1,83 @@
|
||||
Some time ago, <a href="https://github.com/tiangolo/fastapi/issues/3#issuecomment-454956920" target="_blank">a **FastAPI** user asked</a>:
|
||||
|
||||
> What’s the history of this project? It seems to have come from nowhere to awesome in a few weeks [...]
|
||||
|
||||
Here's a little bit of that history.
|
||||
|
||||
|
||||
## Alternatives
|
||||
|
||||
I have been creating APIs with complex requirements for several years (Machine Learning, distributed systems, asynchronous jobs, NoSQL databases, etc), leading several teams of developers.
|
||||
|
||||
As part of that, I needed to investigate, test and use many alternatives.
|
||||
|
||||
The history of **FastAPI** is in great part the history of its predecessors.
|
||||
|
||||
As said in the section <a href="https://fastapi.tiangolo.com/alternatives/" target="_blank">Alternatives</a>:
|
||||
|
||||
<blockquote markdown="1">
|
||||
|
||||
**FastAPI** wouldn't exist if not for the previous work of others.
|
||||
|
||||
There have been many tools created before that have helped inspire its creation.
|
||||
|
||||
I have been avoiding the creation of a new framework for several years. First I tried to solve all the features covered by **FastAPI** using many different frameworks, plug-ins, and tools.
|
||||
|
||||
But at some point, there was no other option than creating something that provided all these features, taking the best ideas from previous tools, and combining them in the best way possible, using language features that weren't even available before (Python 3.6+ type hints).
|
||||
|
||||
</blockquote>
|
||||
|
||||
|
||||
## Investigation
|
||||
|
||||
By using all the previous alternatives I had the chance to learn from all of them, take ideas, and combine them in the best way I could find for myself and the teams of developers I have worked with.
|
||||
|
||||
For example, it was clear that ideally it should be based on standard Python type hints.
|
||||
|
||||
Also, the best approach was to use already existing standards.
|
||||
|
||||
So, before even starting to code **FastAPI**, I spent several months studying the specs for OpenAPI, JSON Schema, OAuth2, etc. Understanding their relationship, overlap, and differences.
|
||||
|
||||
|
||||
## Design
|
||||
|
||||
Then I spent some time designing the developer "API" I wanted to have as a user (as a developer using FastAPI).
|
||||
|
||||
I tested several ideas in the most popular Python editors: PyCharm, VS Code, Jedi based editors.
|
||||
|
||||
By the last <a href="https://www.jetbrains.com/research/python-developers-survey-2018/#development-tools" target="_blank">Python Developer Survey</a>, that covers about 80% of the users.
|
||||
|
||||
It means that **FastAPI** was specifically tested with the editors used by 80% of the Python developers. And as most of the other editors tend to work similarly, all its benefits should work for virtually all editors.
|
||||
|
||||
That way I could find the best ways to reduce code duplication as much as possible, to have completion everywhere, type and error checks, etc.
|
||||
|
||||
All in a way that provided the best development experience for all the developers.
|
||||
|
||||
|
||||
## Requirements
|
||||
|
||||
After testing several alternatives, I decided that I was going to use <a href="https://pydantic-docs.helpmanual.io/" target="_blank">**Pydantic**</a> for its advantages.
|
||||
|
||||
Then I contributed to it, to make it fully compliant with JSON Schema, to support different ways to define constraint declarations, and to improve editor support (type checks, autocompletion) based on the tests in several editors.
|
||||
|
||||
During the development, I also contributed to <a href="https://www.starlette.io/" target="_blank">**Starlette**</a>, the other key requirement.
|
||||
|
||||
|
||||
## Development
|
||||
|
||||
By the time I started creating **FastAPI** itself, most of the pieces were already in place, the design was defined, the requirements and tools were ready, and the knowledge about the standards and specifications was clear and fresh.
|
||||
|
||||
|
||||
## Future
|
||||
|
||||
By this point, it's already clear that **FastAPI** with its ideas is being useful for many people.
|
||||
|
||||
It is being chosen over previous alternatives for suiting many use cases better.
|
||||
|
||||
Many developers and teams already depend on **FastAPI** for their projects (including me and my team).
|
||||
|
||||
But still, there are many improvements and features to come.
|
||||
|
||||
**FastAPI** has a great future ahead.
|
||||
|
||||
And <a href="https://fastapi.tiangolo.com/help-fastapi/" target="_blank">your help</a> is greatly appreciated.
|
||||
BIN
docs/img/tutorial/additional-responses/image01.png
Normal file
|
After Width: | Height: | Size: 80 KiB |
BIN
docs/img/tutorial/application-configuration/image01.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
docs/img/tutorial/async-sql-databases/image01.png
Normal file
|
After Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 76 KiB |
BIN
docs/img/tutorial/debugging/image01.png
Normal file
|
After Width: | Height: | Size: 170 KiB |
BIN
docs/img/tutorial/extending-openapi/image01.png
Normal file
|
After Width: | Height: | Size: 63 KiB |
BIN
docs/img/tutorial/query-params-str-validations/image02.png
Normal file
|
After Width: | Height: | Size: 71 KiB |
BIN
docs/img/tutorial/websockets/image01.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
docs/img/tutorial/websockets/image02.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
docs/img/tutorial/websockets/image03.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
docs/img/tutorial/websockets/image04.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
@@ -14,6 +14,9 @@
|
||||
<a href="https://pypi.org/project/fastapi" target="_blank">
|
||||
<img src="https://badge.fury.io/py/fastapi.svg" alt="Package version">
|
||||
</a>
|
||||
<a href="https://gitter.im/tiangolo/fastapi?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge" target="_blank">
|
||||
<img src="https://badges.gitter.im/tiangolo/fastapi.svg" alt="Join the chat at https://gitter.im/tiangolo/fastapi">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
---
|
||||
@@ -31,10 +34,10 @@ The key features are:
|
||||
* **Fast**: Very high performance, on par with **NodeJS** and **Go** (thanks to Starlette and Pydantic). [One of the fastest Python frameworks available](#performance).
|
||||
|
||||
* **Fast to code**: Increase the speed to develop features by about 200% to 300% *.
|
||||
* **Less bugs**: Reduce about 40% of human (developer) induced errors. *
|
||||
* **Fewer bugs**: Reduce about 40% of human (developer) induced errors. *
|
||||
* **Intuitive**: Great editor support. <abbr title="also known as auto-complete, autocompletion, IntelliSense">Completion</abbr> everywhere. Less time debugging.
|
||||
* **Easy**: Designed to be easy to use and learn. Less time reading docs.
|
||||
* **Short**: Minimize code duplication. Multiple features from each parameter declaration. Less bugs.
|
||||
* **Short**: Minimize code duplication. Multiple features from each parameter declaration. Fewer bugs.
|
||||
* **Robust**: Get production-ready code. With automatic interactive documentation.
|
||||
* **Standards-based**: Based on (and fully compatible with) the open standards for APIs: <a href="https://github.com/OAI/OpenAPI-Specification" target="_blank">OpenAPI</a> (previously known as Swagger) and <a href="http://json-schema.org/" target="_blank">JSON Schema</a>.
|
||||
|
||||
@@ -116,17 +119,17 @@ If you don't know, check the _"In a hurry?"_ section about <a href="https://fast
|
||||
Run the server with:
|
||||
|
||||
```bash
|
||||
uvicorn main:app --debug
|
||||
uvicorn main:app --reload
|
||||
```
|
||||
|
||||
<details markdown="1">
|
||||
<summary>About the command <code>uvicorn main:app --debug</code>...</summary>
|
||||
<summary>About the command <code>uvicorn main:app --reload</code>...</summary>
|
||||
|
||||
The command `uvicorn main:app` refers to:
|
||||
|
||||
* `main`: the file `main.py` (the Python "module").
|
||||
* `app`: the object created inside of `main.py` with the line `app = FastAPI()`.
|
||||
* `--debug`: make the server restart after code changes. Only do this for development.
|
||||
* `--reload`: make the server restart after code changes. Only do this for development.
|
||||
|
||||
</details>
|
||||
|
||||
@@ -199,7 +202,7 @@ def create_item(item_id: int, item: Item):
|
||||
return {"item_name": item.name, "item_id": item_id}
|
||||
```
|
||||
|
||||
The server should reload automatically (because you added `--debug` to the `uvicorn` command above).
|
||||
The server should reload automatically (because you added `--reload` to the `uvicorn` command above).
|
||||
|
||||
### Interactive API docs upgrade
|
||||
|
||||
@@ -344,7 +347,7 @@ For a more complete example including more features, see the <a href="https://fa
|
||||
|
||||
## Performance
|
||||
|
||||
Independent TechEmpower benchmarks show **FastAPI** applications running under Uvicorn as <a href="https://www.techempower.com/benchmarks/#section=test&runid=a979de55-980d-4721-a46f-77298b3f3923&hw=ph&test=fortune&l=zijzen-7" target="_blank">one of the fastest Python frameworks available</a>, only below Starlette and Uvicorn themselves (used internally by FastAPI). (*)
|
||||
Independent TechEmpower benchmarks show **FastAPI** applications running under Uvicorn as <a href="https://www.techempower.com/benchmarks/#section=test&runid=7464e520-0dc2-473d-bd34-dbdfd7e85911&hw=ph&test=query&l=zijzen-7" target="_blank">one of the fastest Python frameworks available</a>, only below Starlette and Uvicorn themselves (used internally by FastAPI). (*)
|
||||
|
||||
To understand more about it, see the section <a href="https://fastapi.tiangolo.com/benchmarks/" target="_blank">Benchmarks</a>.
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ John Doe
|
||||
|
||||
The function does the following:
|
||||
|
||||
* Takes a `fist_name` and `last_name`.
|
||||
* Takes a `first_name` and `last_name`.
|
||||
* Converts the first letter of each one to upper case with `title()`.
|
||||
* <abbr title="Puts them together, as one. With the contents of one after the other.">Concatenates</abbr> them with a space in the middle.
|
||||
|
||||
@@ -49,7 +49,7 @@ But then you have to call "that method that converts the first letter to upper c
|
||||
|
||||
Was it `upper`? Was it `uppercase`? `first_uppercase`? `capitalize`?
|
||||
|
||||
Then, you try with the old programer's friend, editor autocompletion.
|
||||
Then, you try with the old programmer's friend, editor autocompletion.
|
||||
|
||||
You type the first parameter of the function, `first_name`, then a dot (`.`) and then hit `Ctrl+Space` to trigger the completion.
|
||||
|
||||
|
||||
@@ -1,4 +1,123 @@
|
||||
## Next
|
||||
## Next release
|
||||
|
||||
## 0.12.1
|
||||
|
||||
* Fix bug: handling additional `responses` in `APIRouter.include_router()`. PR <a href="https://github.com/tiangolo/fastapi/pull/140" target="_blank">#140</a>.
|
||||
|
||||
* Fix typo in SQL tutorial. PR <a href="https://github.com/tiangolo/fastapi/pull/138" target="_blank">#138</a> by <a href="https://github.com/mostaphaRoudsari" target="_blank">@mostaphaRoudsari</a>.
|
||||
|
||||
* Fix typos in section about nested models and OAuth2 with JWT. PR <a href="https://github.com/tiangolo/fastapi/pull/127" target="_blank">#127</a> by <a href="https://github.com/mmcloud" target="_blank">@mmcloud</a>.
|
||||
|
||||
## 0.12.0
|
||||
|
||||
* Add additional `responses` parameter to *path operation decorators* to extend responses in OpenAPI (and API docs).
|
||||
* It also allows extending existing responses generated from `response_model`, declare other media types (like images), etc.
|
||||
* The new documentation is here: <a href="https://fastapi.tiangolo.com/tutorial/additional-responses/" target="_blank">Additional Responses</a>.
|
||||
* `responses` can also be added to `.include_router()`, the updated docs are here: <a href="https://fastapi.tiangolo.com/tutorial/bigger-applications/#add-some-custom-tags-and-responses" target="_blank">Bigger Applications</a>.
|
||||
* PR <a href="https://github.com/tiangolo/fastapi/pull/97" target="_blank">#97</a> originally initiated by <a href="https://github.com/barsi" target="_blank">@barsi</a>.
|
||||
|
||||
* Update `scripts/test-cov-html.sh` to allow passing extra parameters like `-vv`, for development.
|
||||
|
||||
## 0.11.0
|
||||
|
||||
* Add `auto_error` parameter to security utility functions. Allowing them to be optional. Also allowing to have multiple alternative security schemes that are then checked in a single dependency instead of each one verifying and returning the error to the client automatically when not satisfied. PR <a href="https://github.com/tiangolo/fastapi/pull/134" target="_blank">#134</a>.
|
||||
|
||||
* Update <a href="https://fastapi.tiangolo.com/tutorial/sql-databases/#create-a-middleware-to-handle-sessions" target="_blank">SQL Tutorial</a> to close database sessions even when there are exceptions. PR <a href="https://github.com/tiangolo/fastapi/pull/89" target="_blank">#89</a> by <a href="https://github.com/alexiri" target="_blank">@alexiri</a>.
|
||||
|
||||
* Fix duplicate dependency in `pyproject.toml`. PR <a href="https://github.com/tiangolo/fastapi/pull/128" target="_blank">#128</a> by <a href="https://github.com/zxalif" target="_blank">@zxalif</a>.
|
||||
|
||||
## 0.10.3
|
||||
|
||||
* Add Gitter chat, badge, links, etc. <a href="https://gitter.im/tiangolo/fastapi" target="_blank">https://gitter.im/tiangolo/fastapi
|
||||
</a>. PR <a href="https://github.com/tiangolo/fastapi/pull/117" target="_blank">#117</a>.
|
||||
|
||||
* Add docs about <a href="https://fastapi.tiangolo.com/tutorial/extending-openapi/" target="_blank">Extending OpenAPI</a>. PR <a href="https://github.com/tiangolo/fastapi/pull/126" target="_blank">#126</a>.
|
||||
|
||||
* Make Travis run Ubuntu Xenial (newer version) and Python 3.7 instead of Python 3.7-dev. PR <a href="https://github.com/tiangolo/fastapi/pull/92" target="_blank">#92</a> by <a href="https://github.com/blueyed" target="_blank">@blueyed</a>.
|
||||
|
||||
* Fix duplicated param variable creation. PR <a href="https://github.com/tiangolo/fastapi/pull/123" target="_blank">#123</a> by <a href="https://github.com/yihuang" target="_blank">@yihuang</a>.
|
||||
|
||||
* Add note in <a href="https://fastapi.tiangolo.com/tutorial/response-model/" target="_blank">Response Model docs</a> about why using a function parameter instead of a function return type annotation. PR <a href="https://github.com/tiangolo/fastapi/pull/109" target="_blank">#109</a> by <a href="https://github.com/JHSaunders" target="_blank">@JHSaunders</a>.
|
||||
|
||||
* Fix event docs (startup/shutdown) function name. PR <a href="https://github.com/tiangolo/fastapi/pull/105" target="_blank">#105</a> by <a href="https://github.com/stratosgear" target="_blank">@stratosgear</a>.
|
||||
|
||||
## 0.10.2
|
||||
|
||||
* Fix OpenAPI (JSON Schema) for declarations of Python `Union` (JSON Schema `additionalProperties`). PR <a href="https://github.com/tiangolo/fastapi/pull/121" target="_blank">#121</a>.
|
||||
|
||||
* Update <a href="https://fastapi.tiangolo.com/tutorial/background-tasks/" target="_blank">Background Tasks</a> with a note on Celery.
|
||||
|
||||
* Document response models using unions and lists, updated at: <a href="https://fastapi.tiangolo.com/tutorial/extra-models/" target="_blank">Extra Models</a>. PR <a href="https://github.com/tiangolo/fastapi/pull/108" target="_blank">#108</a>.
|
||||
|
||||
## 0.10.1
|
||||
|
||||
* Add docs and tests for <a href="https://github.com/encode/databases" target="_blank">encode/databases</a>. New docs at: <a href="https://fastapi.tiangolo.com/tutorial/async-sql-databases/" target="_blank">Async SQL (Relational) Databases</a>. PR <a href="https://github.com/tiangolo/fastapi/pull/107" target="_blank">#107</a>.
|
||||
|
||||
## 0.10.0
|
||||
|
||||
* Add support for Background Tasks in *path operation functions* and dependencies. New documentation about <a href="https://fastapi.tiangolo.com/tutorial/background-tasks/" target="_blank">Background Tasks is here</a>. PR <a href="https://github.com/tiangolo/fastapi/pull/103" target="_blank">#103</a>.
|
||||
|
||||
* Add support for `.websocket_route()` in `APIRouter`. PR <a href="https://github.com/tiangolo/fastapi/pull/100" target="_blank">#100</a> by <a href="https://github.com/euri10" target="_blank">@euri10</a>.
|
||||
|
||||
* New docs section about <a href="https://fastapi.tiangolo.com/tutorial/events/" target="_blank">Events: startup - shutdown</a>. PR <a href="https://github.com/tiangolo/fastapi/pull/99" target="_blank">#99</a>.
|
||||
|
||||
## 0.9.1
|
||||
|
||||
* Document receiving <a href="https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#query-parameter-list-multiple-values" target="_blank">Multiple values with the same query parameter</a> and <a href="https://fastapi.tiangolo.com/tutorial/header-params/#duplicate-headers" target="_blank">Duplicate headers</a>. PR <a href="https://github.com/tiangolo/fastapi/pull/95" target="_blank">#95</a>.
|
||||
|
||||
## 0.9.0
|
||||
|
||||
* Upgrade compatible Pydantic version to `0.21.0`. PR <a href="https://github.com/tiangolo/fastapi/pull/90" target="_blank">#90</a>.
|
||||
|
||||
* Add documentation for: <a href="https://fastapi.tiangolo.com/tutorial/application-configuration/" target="_blank">Application Configuration</a>.
|
||||
|
||||
* Fix typo in docs. PR <a href="https://github.com/tiangolo/fastapi/pull/76" target="_blank">#76</a> by <a href="https://github.com/matthewhegarty" target="_blank">@matthewhegarty</a>.
|
||||
|
||||
* Fix link in "Deployment" to "Bigger Applications".
|
||||
|
||||
## 0.8.0
|
||||
|
||||
* Make development scripts executable. PR <a href="https://github.com/tiangolo/fastapi/pull/76" target="_blank">#76</a> by <a href="https://github.com/euri10" target="_blank">@euri10</a>.
|
||||
|
||||
* Add support for adding `tags` in `app.include_router()`. PR <a href="https://github.com/tiangolo/fastapi/pull/55" target="_blank">#55</a> by <a href="https://github.com/euri10" target="_blank">@euri10</a>. Documentation updated in the section: <a href="https://fastapi.tiangolo.com/tutorial/bigger-applications/" target="_blank">Bigger Applications</a>.
|
||||
|
||||
* Update docs related to Uvicorn to use new `--reload` option from version `0.5.x`. PR <a href="https://github.com/tiangolo/fastapi/pull/74" target="_blank">#74</a>.
|
||||
|
||||
* Update `isort` imports and scripts to be compatible with newer versions. PR <a href="https://github.com/tiangolo/fastapi/pull/75" target="_blank">#75</a>.
|
||||
|
||||
## 0.7.1
|
||||
|
||||
* Update <a href="https://fastapi.tiangolo.com/async/#path-operation-functions" target="_blank">technical details about `async def` handling</a> with respect to previous frameworks. PR <a href="https://github.com/tiangolo/fastapi/pull/64" target="_blank">#64</a> by <a href="https://github.com/haizaar" target="_blank">@haizaar</a>.
|
||||
|
||||
* Add <a href="https://fastapi.tiangolo.com/deployment/#raspberry-pi-and-other-architectures" target="_blank">deployment documentation for Docker in Raspberry Pi</a> and other architectures.
|
||||
|
||||
* Trigger Docker images build on Travis CI automatically. PR <a href="https://github.com/tiangolo/fastapi/pull/65" target="_blank">#65</a>.
|
||||
|
||||
## 0.7.0
|
||||
|
||||
* Add support for `UploadFile` in `File` parameter annotations.
|
||||
* This includes a file-like interface.
|
||||
* Here's the updated documentation for declaring <a href="https://fastapi.tiangolo.com/tutorial/request-files/#file-parameters-with-uploadfile" target="_blank"> `File` parameters with `UploadFile`</a>.
|
||||
* And here's the updated documentation for using <a href="https://fastapi.tiangolo.com/tutorial/request-forms-and-files/" target="_blank">`Form` parameters mixed with `File` parameters, supporting `bytes` and `UploadFile`</a> at the same time.
|
||||
* PR <a href="https://github.com/tiangolo/fastapi/pull/63" target="_blank">#63</a>.
|
||||
|
||||
## 0.6.4
|
||||
|
||||
* Add <a href="https://fastapi.tiangolo.com/async/#very-technical-details" target="_blank">technical details about `async def` handling to docs</a>. PR <a href="https://github.com/tiangolo/fastapi/pull/61" target="_blank">#61</a>.
|
||||
|
||||
* Add docs for <a href="https://fastapi.tiangolo.com/tutorial/debugging/" target="_blank">Debugging FastAPI applications in editors</a>.
|
||||
|
||||
* Clarify <a href="https://fastapi.tiangolo.com/deployment/#bigger-applications" target="_blank">Bigger Applications deployed with Docker</a>.
|
||||
|
||||
* Fix typos in docs.
|
||||
|
||||
* Add section about <a href="https://fastapi.tiangolo.com/history-design-future/" target="_blank">History, Design and Future</a>.
|
||||
|
||||
* Add docs for using <a href="https://fastapi.tiangolo.com/tutorial/websockets/" target="_blank">WebSockets with **FastAPI**</a>. PR <a href="https://github.com/tiangolo/fastapi/pull/62" target="_blank">#62</a>.
|
||||
|
||||
## 0.6.3
|
||||
|
||||
* Add Favicons to docs. PR <a href="https://github.com/tiangolo/fastapi/pull/53" target="_blank">#53</a>.
|
||||
|
||||
## 0.6.2
|
||||
|
||||
@@ -40,7 +159,7 @@
|
||||
|
||||
## 0.4.0
|
||||
|
||||
* Add `openapi_prefix`, support for reverse proxy and mounting sub-applicaitons. See the docs at <a href="https://fastapi.tiangolo.com/tutorial/sub-applications-proxy/" target="_blank">https://fastapi.tiangolo.com/tutorial/sub-applications-proxy/</a>: <a href="https://github.com/tiangolo/fastapi/pull/26" target="_blank">#26</a> by <a href="https://github.com/kabirkhan" target="_blank">@kabirkhan</a>.
|
||||
* Add `openapi_prefix`, support for reverse proxy and mounting sub-applications. See the docs at <a href="https://fastapi.tiangolo.com/tutorial/sub-applications-proxy/" target="_blank">https://fastapi.tiangolo.com/tutorial/sub-applications-proxy/</a>: <a href="https://github.com/tiangolo/fastapi/pull/26" target="_blank">#26</a> by <a href="https://github.com/kabirkhan" target="_blank">@kabirkhan</a>.
|
||||
|
||||
* Update <a href="https://fastapi.tiangolo.com/tutorial/sql-databases/" target="_blank">docs/tutorial for SQLAlchemy</a> including note about *DB Browser for SQLite*.
|
||||
|
||||
|
||||
23
docs/src/additional_responses/tutorial001.py
Normal file
@@ -0,0 +1,23 @@
|
||||
from fastapi import FastAPI
|
||||
from pydantic import BaseModel
|
||||
from starlette.responses import JSONResponse
|
||||
|
||||
|
||||
class Item(BaseModel):
|
||||
id: str
|
||||
value: str
|
||||
|
||||
|
||||
class Message(BaseModel):
|
||||
message: str
|
||||
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.get("/items/{item_id}", response_model=Item, responses={404: {"model": Message}})
|
||||
async def read_item(item_id: str):
|
||||
if item_id == "foo":
|
||||
return {"id": "foo", "value": "there goes my hero"}
|
||||
else:
|
||||
return JSONResponse(status_code=404, content={"message": "Item not found"})
|
||||
28
docs/src/additional_responses/tutorial002.py
Normal file
@@ -0,0 +1,28 @@
|
||||
from fastapi import FastAPI
|
||||
from pydantic import BaseModel
|
||||
from starlette.responses import FileResponse
|
||||
|
||||
|
||||
class Item(BaseModel):
|
||||
id: str
|
||||
value: str
|
||||
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.get(
|
||||
"/items/{item_id}",
|
||||
response_model=Item,
|
||||
responses={
|
||||
200: {
|
||||
"content": {"image/png": {}},
|
||||
"description": "Return the JSON item or an image.",
|
||||
}
|
||||
},
|
||||
)
|
||||
async def read_item(item_id: str, img: bool = None):
|
||||
if img:
|
||||
return FileResponse("image.png", media_type="image/png")
|
||||
else:
|
||||
return {"id": "foo", "value": "there goes my hero"}
|
||||
37
docs/src/additional_responses/tutorial003.py
Normal file
@@ -0,0 +1,37 @@
|
||||
from fastapi import FastAPI
|
||||
from pydantic import BaseModel
|
||||
from starlette.responses import JSONResponse
|
||||
|
||||
|
||||
class Item(BaseModel):
|
||||
id: str
|
||||
value: str
|
||||
|
||||
|
||||
class Message(BaseModel):
|
||||
message: str
|
||||
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.get(
|
||||
"/items/{item_id}",
|
||||
response_model=Item,
|
||||
responses={
|
||||
404: {"model": Message, "description": "The item was not found"},
|
||||
200: {
|
||||
"description": "Item requested by ID",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"example": {"id": "bar", "value": "The bar tenders"}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
async def read_item(item_id: str):
|
||||
if item_id == "foo":
|
||||
return {"id": "foo", "value": "there goes my hero"}
|
||||
else:
|
||||
return JSONResponse(status_code=404, content={"message": "Item not found"})
|
||||
30
docs/src/additional_responses/tutorial004.py
Normal file
@@ -0,0 +1,30 @@
|
||||
from fastapi import FastAPI
|
||||
from pydantic import BaseModel
|
||||
from starlette.responses import FileResponse
|
||||
|
||||
|
||||
class Item(BaseModel):
|
||||
id: str
|
||||
value: str
|
||||
|
||||
|
||||
responses = {
|
||||
404: {"description": "Item not found"},
|
||||
302: {"description": "The item was moved"},
|
||||
403: {"description": "Not enough privileges"},
|
||||
}
|
||||
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.get(
|
||||
"/items/{item_id}",
|
||||
response_model=Item,
|
||||
responses={**responses, 200: {"content": {"image/png": {}}}},
|
||||
)
|
||||
async def read_item(item_id: str, img: bool = None):
|
||||
if img:
|
||||
return FileResponse("image.png", media_type="image/png")
|
||||
else:
|
||||
return {"id": "foo", "value": "there goes my hero"}
|
||||
@@ -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/")
|
||||
|
||||
@@ -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/")
|
||||
|
||||
65
docs/src/async_sql_databases/tutorial001.py
Normal 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}
|
||||
15
docs/src/background_tasks/tutorial001.py
Normal 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"}
|
||||
24
docs/src/background_tasks/tutorial002.py
Normal 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"}
|
||||
@@ -1,9 +1,13 @@
|
||||
from fastapi import FastAPI
|
||||
|
||||
from .routers.items import router as items_router
|
||||
from .routers.users import router as users_router
|
||||
from .routers import items, users
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
app.include_router(users_router)
|
||||
app.include_router(items_router, prefix="/items")
|
||||
app.include_router(users.router)
|
||||
app.include_router(
|
||||
items.router,
|
||||
prefix="/items",
|
||||
tags=["items"],
|
||||
responses={404: {"description": "Not found"}},
|
||||
)
|
||||
|
||||
@@ -1,13 +1,24 @@
|
||||
from fastapi import APIRouter
|
||||
from fastapi import APIRouter, HTTPException
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/", tags=["items"])
|
||||
@router.get("/")
|
||||
async def read_items():
|
||||
return [{"name": "Item Foo"}, {"name": "item Bar"}]
|
||||
|
||||
|
||||
@router.get("/{item_id}", tags=["items"])
|
||||
@router.get("/{item_id}")
|
||||
async def read_item(item_id: str):
|
||||
return {"name": "Fake Specific Item", "item_id": item_id}
|
||||
|
||||
|
||||
@router.put(
|
||||
"/{item_id}",
|
||||
tags=["custom"],
|
||||
responses={403: {"description": "Operation forbidden"}},
|
||||
)
|
||||
async def update_item(item_id: str):
|
||||
if item_id != "foo":
|
||||
raise HTTPException(status_code=403, detail="You can only update the item: foo")
|
||||
return {"item_id": item_id, "name": "The Fighters"}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
from typing import Set
|
||||
|
||||
from fastapi import FastAPI
|
||||
from pydantic import BaseModel
|
||||
from pydantic.types import UrlStr
|
||||
from pydantic import BaseModel, UrlStr
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
from typing import List, Set
|
||||
|
||||
from fastapi import FastAPI
|
||||
from pydantic import BaseModel
|
||||
from pydantic.types import UrlStr
|
||||
from pydantic import BaseModel, UrlStr
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
@@ -18,7 +17,7 @@ class Item(BaseModel):
|
||||
price: float
|
||||
tax: float = None
|
||||
tags: Set[str] = []
|
||||
image: List[Image] = None
|
||||
images: List[Image] = None
|
||||
|
||||
|
||||
@app.put("/items/{item_id}")
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
from typing import List, Set
|
||||
|
||||
from fastapi import FastAPI
|
||||
from pydantic import BaseModel
|
||||
from pydantic.types import UrlStr
|
||||
from pydantic import BaseModel, UrlStr
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
@@ -18,7 +17,7 @@ class Item(BaseModel):
|
||||
price: float
|
||||
tax: float = None
|
||||
tags: Set[str] = []
|
||||
image: List[Image] = None
|
||||
images: List[Image] = None
|
||||
|
||||
|
||||
class Offer(BaseModel):
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
from typing import List
|
||||
|
||||
from fastapi import FastAPI
|
||||
from pydantic import BaseModel
|
||||
from pydantic.types import UrlStr
|
||||
from pydantic import BaseModel, UrlStr
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
15
docs/src/debugging/tutorial001.py
Normal file
@@ -0,0 +1,15 @@
|
||||
import uvicorn
|
||||
from fastapi import FastAPI
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.get("/")
|
||||
def root():
|
||||
a = "a"
|
||||
b = "b" + a
|
||||
return {"hello world": b}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
uvicorn.run(app, host="0.0.0.0", port=8000)
|
||||
16
docs/src/events/tutorial001.py
Normal 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]
|
||||
14
docs/src/events/tutorial002.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from fastapi import FastAPI
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.on_event("shutdown")
|
||||
def shutdown_event():
|
||||
with open("log.txt", mode="a") as log:
|
||||
log.write("Application shutdown")
|
||||
|
||||
|
||||
@app.get("/items/")
|
||||
async def read_items():
|
||||
return [{"name": "Foo"}]
|
||||
28
docs/src/extending_openapi/tutorial001.py
Normal file
@@ -0,0 +1,28 @@
|
||||
from fastapi import FastAPI
|
||||
from fastapi.openapi.utils import get_openapi
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.get("/items/")
|
||||
async def read_items():
|
||||
return [{"name": "Foo"}]
|
||||
|
||||
|
||||
def custom_openapi():
|
||||
if app.openapi_schema:
|
||||
return app.openapi_schema
|
||||
openapi_schema = get_openapi(
|
||||
title="Custom title",
|
||||
version="2.5.0",
|
||||
description="This is a very custom OpenAPI schema",
|
||||
routes=app.routes,
|
||||
)
|
||||
openapi_schema["info"]["x-logo"] = {
|
||||
"url": "https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png"
|
||||
}
|
||||
app.openapi_schema = openapi_schema
|
||||
return app.openapi_schema
|
||||
|
||||
|
||||
app.openapi = custom_openapi
|
||||
35
docs/src/extra_models/tutorial003.py
Normal 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]
|
||||
22
docs/src/extra_models/tutorial004.py
Normal 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
|
||||
10
docs/src/header_params/tutorial003.py
Normal 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}
|
||||
11
docs/src/query_params_str_validations/tutorial011.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from typing import List
|
||||
|
||||
from fastapi import FastAPI, Query
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.get("/items/")
|
||||
async def read_items(q: List[str] = Query(None)):
|
||||
query_items = {"q": q}
|
||||
return query_items
|
||||
@@ -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}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ from sqlalchemy import Boolean, Column, Integer, String, create_engine
|
||||
from sqlalchemy.ext.declarative import declarative_base, declared_attr
|
||||
from sqlalchemy.orm import Session, sessionmaker
|
||||
from starlette.requests import Request
|
||||
from starlette.responses import Response
|
||||
|
||||
# SQLAlchemy specific code, as with any other app
|
||||
SQLALCHEMY_DATABASE_URI = "sqlite:///./test.db"
|
||||
@@ -66,7 +67,10 @@ def read_user(user_id: int, db: Session = Depends(get_db)):
|
||||
|
||||
@app.middleware("http")
|
||||
async def db_session_middleware(request: Request, call_next):
|
||||
request.state.db = SessionLocal()
|
||||
response = await call_next(request)
|
||||
request.state.db.close()
|
||||
response = Response("Internal server error", status_code=500)
|
||||
try:
|
||||
request.state.db = SessionLocal()
|
||||
response = await call_next(request)
|
||||
finally:
|
||||
request.state.db.close()
|
||||
return response
|
||||
|
||||
53
docs/src/websockets/tutorial001.py
Normal file
@@ -0,0 +1,53 @@
|
||||
from fastapi import FastAPI
|
||||
from starlette.responses import HTMLResponse
|
||||
from starlette.websockets import WebSocket
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
html = """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Chat</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>WebSocket Chat</h1>
|
||||
<form action="" onsubmit="sendMessage(event)">
|
||||
<input type="text" id="messageText" autocomplete="off"/>
|
||||
<button>Send</button>
|
||||
</form>
|
||||
<ul id='messages'>
|
||||
</ul>
|
||||
<script>
|
||||
var ws = new WebSocket("ws://localhost:8000/ws");
|
||||
ws.onmessage = function(event) {
|
||||
var messages = document.getElementById('messages')
|
||||
var message = document.createElement('li')
|
||||
var content = document.createTextNode(event.data)
|
||||
message.appendChild(content)
|
||||
messages.appendChild(message)
|
||||
};
|
||||
function sendMessage(event) {
|
||||
var input = document.getElementById("messageText")
|
||||
ws.send(input.value)
|
||||
input.value = ''
|
||||
event.preventDefault()
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
|
||||
@app.get("/")
|
||||
async def get():
|
||||
return HTMLResponse(html)
|
||||
|
||||
|
||||
@app.websocket_route("/ws")
|
||||
async def websocket_endpoint(websocket: WebSocket):
|
||||
await websocket.accept()
|
||||
while True:
|
||||
data = await websocket.receive_text()
|
||||
await websocket.send_text(f"Message text was: {data}")
|
||||
await websocket.close()
|
||||
235
docs/tutorial/additional-responses.md
Normal file
@@ -0,0 +1,235 @@
|
||||
!!! warning
|
||||
This is a rather advanced topic.
|
||||
|
||||
If you are starting with **FastAPI**, you might not need this.
|
||||
|
||||
You can declare additional responses, with additional status codes, media types, descriptions, etc.
|
||||
|
||||
Those additional responses will be included in the OpenAPI schema, so they will also appear in the API docs.
|
||||
|
||||
But for those additional responses you have to make sure you return a `Response` like `JSONResponse` directly, with your status code and content.
|
||||
|
||||
## Additional Response with `model`
|
||||
|
||||
You can pass to your *path operation decorators* a parameter `responses`.
|
||||
|
||||
It receives a `dict`, the keys are status codes for each response, like `200`, and the values are other `dict`s with the information for each of them.
|
||||
|
||||
Each of those response `dict`s can have a key `model`, containing a Pydantic model, just like `response_model`.
|
||||
|
||||
**FastAPI** will take that model, generate its JSON Schema and include it in the correct place in OpenAPI.
|
||||
|
||||
For example, to declare another response with a status code `404` and a Pydantic model `Message`, you can write:
|
||||
|
||||
|
||||
```Python hl_lines="18 23"
|
||||
{!./src/additional_responses/tutorial001.py!}
|
||||
```
|
||||
|
||||
!!! note
|
||||
Have in mind that you have to return the `JSONResponse` directly.
|
||||
|
||||
!!! info
|
||||
The `model` key is not part of OpenAPI.
|
||||
|
||||
**FastAPI** will take the Pydantic model from there, generate the `JSON Schema`, and put it in the correct place.
|
||||
|
||||
The correct place is:
|
||||
|
||||
* In the key `content`, that has as value another JSON object (`dict`) that contains:
|
||||
* A key with the media type, e.g. `application/json`, that contains as value another JSON object, that contains:
|
||||
* A key `schema`, that has as the value the JSON Schema from the model, here's the correct place.
|
||||
* **FastAPI** adds a reference here to the global JSON Schemas in another place in your OpenAPI instead of including it directly. This way, other applications and clients can use those JSON Schemas directly, provide better code generation tools, etc.
|
||||
|
||||
The generated responses in the OpenAPI for this *path operation* will be:
|
||||
|
||||
```JSON hl_lines="3 4 5 6 7 8 9 10 11 12"
|
||||
{
|
||||
"responses": {
|
||||
"404": {
|
||||
"description": "Additional Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Message"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Item"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The schemas are referenced to another place inside the OpenAPI schema:
|
||||
|
||||
```JSON hl_lines="4 5 6 7 8 9 10 11 12 13 14 15 16"
|
||||
{
|
||||
"components": {
|
||||
"schemas": {
|
||||
"Message": {
|
||||
"title": "Message",
|
||||
"required": [
|
||||
"message"
|
||||
],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"title": "Message",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Item": {
|
||||
"title": "Item",
|
||||
"required": [
|
||||
"id",
|
||||
"value"
|
||||
],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"title": "Id",
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"title": "Value",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ValidationError": {
|
||||
"title": "ValidationError",
|
||||
"required": [
|
||||
"loc",
|
||||
"msg",
|
||||
"type"
|
||||
],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"loc": {
|
||||
"title": "Location",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"msg": {
|
||||
"title": "Message",
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"title": "Error Type",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/ValidationError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Additional media types for the main response
|
||||
|
||||
You can use this same `responses` parameter to add different media types for the same main response.
|
||||
|
||||
For example, you can add an additional media type of `image/png`, declaring that your *path operation* can return a JSON object (with media type `application/json`) or a PNG image:
|
||||
|
||||
```Python hl_lines="17 18 19 20 21 22 23 24 28"
|
||||
{!./src/additional_responses/tutorial002.py!}
|
||||
```
|
||||
|
||||
!!! note
|
||||
Notice that you have to return the image using a `FileResponse` directly.
|
||||
|
||||
## Combining information
|
||||
|
||||
You can also combine response information from multiple places, including the `response_model`, `status_code`, and `responses` parameters.
|
||||
|
||||
You can declare a `response_model`, using the default status code `200` (or a custom one if you need), and then declare additional information for that same response in `responses`, directly in the OpenAPI schema.
|
||||
|
||||
**FastAPI** will keep the additional information from `responses`, and combine it with the JSON Schema from your model.
|
||||
|
||||
For example, you can declare a response with a status code `404` that uses a Pydantic model and has a custom `description`.
|
||||
|
||||
And a response with a status code `200` that uses your `response_model`, but includes a custom `example`:
|
||||
|
||||
```Python hl_lines="20 21 22 23 24 25 26 27 28 29 30 31"
|
||||
{!./src/additional_responses/tutorial003.py!}
|
||||
```
|
||||
|
||||
It will all be combined and included in your OpenAPI, and shown in the API docs:
|
||||
|
||||
<img src="/img/tutorial/additional-responses/image01.png">
|
||||
|
||||
|
||||
## Combine predefined responses and custom ones
|
||||
|
||||
You might want to have some predefined responses that apply to many *path operations*, but you want to combine them with custom responses needed by each *path operation*.
|
||||
|
||||
For those cases, you can use the Python technique of "unpacking" a `dict` with `**dict_to_unpack`:
|
||||
|
||||
```Python
|
||||
old_dict = {
|
||||
"old key": "old value",
|
||||
"second old key": "second old value",
|
||||
}
|
||||
new_dict = {**old_dict, "new key": "new value"}
|
||||
```
|
||||
|
||||
Here, `new_dict` will contain all the key-value pairs from `old_dict` plus the new key-value pair:
|
||||
|
||||
```Python
|
||||
{
|
||||
"old key": "old value",
|
||||
"second old key": "second old value",
|
||||
"new key": "new value",
|
||||
}
|
||||
```
|
||||
|
||||
You can use that technique to re-use some predefined responses in your *path operations* and combine them with additional custom ones.
|
||||
|
||||
For example:
|
||||
|
||||
```Python hl_lines="11 12 13 14 15 24"
|
||||
{!./src/additional_responses/tutorial004.py!}
|
||||
```
|
||||
|
||||
## More information about OpenAPI responses
|
||||
|
||||
To see what exactly you can include in the responses, you can check these sections in the OpenAPI specification:
|
||||
|
||||
* <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#responsesObject" target="_blank">OpenAPI Responses Object</a>, it includes the `Response Object`.
|
||||
* <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#responseObject" target="_blank">OpenAPI Response Object</a>, you can include anything from this directly in each response inside your `responses` parameter. Including `description`, `headers`, `content` (inside of this is that you declare different media types and JSON Schemas), and `links`.
|
||||
@@ -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!}
|
||||
```
|
||||
|
||||
160
docs/tutorial/async-sql-databases.md
Normal 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>.
|
||||
96
docs/tutorial/background-tasks.md
Normal 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.
|
||||
@@ -2,6 +2,8 @@ If you are building an application or a web API, it's rarely the case that you c
|
||||
|
||||
**FastAPI** provides a convenience tool to structure your application while keeping all the flexibility.
|
||||
|
||||
!!! info
|
||||
If you come from Flask, this would be the equivalent of Flask's Blueprints.
|
||||
|
||||
## An example file structure
|
||||
|
||||
@@ -99,12 +101,21 @@ It's all the same structure as with `app/routers/users.py`.
|
||||
|
||||
But let's say that this time we are more lazy.
|
||||
|
||||
And we don't want to have to explicitly type `/items/` in every path operation, we can do it later:
|
||||
And we don't want to have to explicitly type `/items/` and `tags=["items"]` in every *path operation* (we will be able to do it later):
|
||||
|
||||
```Python hl_lines="6 11 16"
|
||||
```Python hl_lines="6 11"
|
||||
{!./src/bigger_applications/app/routers/items.py!}
|
||||
```
|
||||
|
||||
### Add some custom `tags` and `responses`
|
||||
|
||||
We are not adding the prefix `/items/` nor the `tags=["items"]` to add them later.
|
||||
|
||||
But we can add custom `tags` and `responses` that will be applied to a specific *path operation*:
|
||||
|
||||
```Python hl_lines="18 19"
|
||||
{!./src/bigger_applications/app/routers/items.py!}
|
||||
```
|
||||
|
||||
## The main `FastAPI`
|
||||
|
||||
@@ -118,17 +129,17 @@ This will be the main file in your application that ties everything together.
|
||||
|
||||
You import and create a `FastAPI` class as normally:
|
||||
|
||||
```Python hl_lines="1 6"
|
||||
```Python hl_lines="1 5"
|
||||
{!./src/bigger_applications/app/main.py!}
|
||||
```
|
||||
|
||||
### Import the `APIRouter`
|
||||
|
||||
But this time we are not adding path operations directly with the `FastAPI` `app`.
|
||||
But this time we are not adding *path operations* directly with the `FastAPI` `app`.
|
||||
|
||||
We import the `APIRouter`s from the other files:
|
||||
We import the other submodules that have `APIRouter`s:
|
||||
|
||||
```Python hl_lines="3 4"
|
||||
```Python hl_lines="3"
|
||||
{!./src/bigger_applications/app/main.py!}
|
||||
```
|
||||
|
||||
@@ -140,22 +151,21 @@ As the file `app/routers/items.py` is part of the same Python package, we can im
|
||||
The section:
|
||||
|
||||
```Python
|
||||
from .routers.items import router
|
||||
from .routers import items, users
|
||||
```
|
||||
|
||||
Means:
|
||||
|
||||
* Starting in the same package that this module (the file `app/main.py`) lives in (the directory `app/`)...
|
||||
* look for the subpackage `routers` (the directory at `app/routers/`)...
|
||||
* and from it, the submodule `items` (the file at `app/routers/items.py`)...
|
||||
* and from that submodule, import the variable `router`.
|
||||
* and from it, import the submodule `items` (the file at `app/routers/items.py`) and `users` (the file at `app/routers/users.py`)...
|
||||
|
||||
The variable `router` is the same one we created in the file `app/routers/items.py`. It's an `APIRouter`.
|
||||
The module `items` will have a variable `router` (`items.router`). This is the same one we created in the file `app/routers/items.py`. It's an `APIRouter`. The same for the module `users`.
|
||||
|
||||
We could also import it like:
|
||||
We could also import them like:
|
||||
|
||||
```Python
|
||||
from app.routers.items import router
|
||||
from app.routers import items, users
|
||||
```
|
||||
|
||||
!!! info
|
||||
@@ -168,40 +178,43 @@ from app.routers.items import router
|
||||
|
||||
### Avoid name collisions
|
||||
|
||||
We are importing a variable named `router` from the submodule `items`.
|
||||
We are importing the submodule `items` directly, instead of importing just its variable `router`.
|
||||
|
||||
But we also have another variable named `router` in the submodule `users`.
|
||||
This is because we also have another variable named `router` in the submodule `users`.
|
||||
|
||||
If we import one after the other, like:
|
||||
If we had imported one after the other, like:
|
||||
|
||||
```Python
|
||||
from .routers.items import router
|
||||
from .routers.users import router
|
||||
```
|
||||
|
||||
The `router` from `users` will overwrite the one form `items` and we won't be able to use them at the same time.
|
||||
The `router` from `users` would overwrite the one from `items` and we wouldn't be able to use them at the same time.
|
||||
|
||||
So, to be able to use both of them in the same file, we rename them while importing them using `as`:
|
||||
So, to be able to use both of them in the same file, we import the submodules directly:
|
||||
|
||||
```Python hl_lines="3 4"
|
||||
```Python hl_lines="3"
|
||||
{!./src/bigger_applications/app/main.py!}
|
||||
```
|
||||
|
||||
|
||||
### Include an `APIRouter`
|
||||
|
||||
Now, let's include the router from the submodule `users`, now in the variable `users_router`:
|
||||
Now, let's include the `router` from the submodule `users`:
|
||||
|
||||
```Python hl_lines="8"
|
||||
```Python hl_lines="7"
|
||||
{!./src/bigger_applications/app/main.py!}
|
||||
```
|
||||
|
||||
!!! info
|
||||
`users.router` contains the `APIRouter` inside of the file `app/routers/users.py`.
|
||||
|
||||
With `app.include_router()` we can add an `APIRouter` to the main `FastAPI` application.
|
||||
|
||||
It will include all the routes from that router as part of it.
|
||||
|
||||
!!! note "Technical Details"
|
||||
It will actually internally create a path operation for each path operation that was declared in the `APIRouter`.
|
||||
It will actually internally create a *path operation* for each *path operation* that was declared in the `APIRouter`.
|
||||
|
||||
So, behind the scenes, it will actually work as if everything was the same single app.
|
||||
|
||||
@@ -214,27 +227,31 @@ It will include all the routes from that router as part of it.
|
||||
So it won't affect performance.
|
||||
|
||||
|
||||
### Include an `APIRouter` with a prefix
|
||||
### Include an `APIRouter` with a `prefix`, `tags`, and `responses`
|
||||
|
||||
Now, let's include the router form the `items` submodule, now in the variable `items_router`.
|
||||
Now, let's include the router form the `items` submodule.
|
||||
|
||||
But, remember that we were lazy and didn't add `/items/` to all the path operations?
|
||||
But, remember that we were lazy and didn't add `/items/` nor `tags` to all the *path operations*?
|
||||
|
||||
We can add a prefix to all the path operations using the parameter `prefix` of `app.include_router()`.
|
||||
|
||||
As the path of each path operation has to start with `/`, like in:
|
||||
|
||||
```Python hl_lines="1"
|
||||
@router.get("/{item_id}", tags=["items"])
|
||||
@router.get("/{item_id}")
|
||||
async def read_item(item_id: str):
|
||||
...
|
||||
```
|
||||
|
||||
...the prefix must not include a final `/`.
|
||||
|
||||
So, the prefix in this case would be `/items`:
|
||||
So, the prefix in this case would be `/items`.
|
||||
|
||||
```Python hl_lines="9"
|
||||
We can also add a list of `tags` that will be applied to all the *path operations* included in this router.
|
||||
|
||||
And we can add predefined `responses` that will be included in all the *path operations* too.
|
||||
|
||||
```Python hl_lines="8 9 10 11 12 13"
|
||||
{!./src/bigger_applications/app/main.py!}
|
||||
```
|
||||
|
||||
@@ -245,8 +262,18 @@ The end result is that the item paths are now:
|
||||
|
||||
...as we intended.
|
||||
|
||||
They will be marked with a list of tags that contain a single string `"items"`.
|
||||
|
||||
The *path operation* that declared a `"custom"` tag will have both tags, `items` and `custom`.
|
||||
|
||||
These "tags" are especially useful for the automatic interactive documentation systems (using OpenAPI).
|
||||
|
||||
And all of them will include the the predefined `responses`.
|
||||
|
||||
The *path operation* that declared a custom `403` response will have both the predefined responses (`404`) and the `403` declared in it directly.
|
||||
|
||||
!!! check
|
||||
The `prefix` parameter is (as in many other cases) just a feature from **FastAPI** to help you avoid code duplication.
|
||||
The `prefix`, `tags`, and `responses` parameters are (as in many other cases) just a feature from **FastAPI** to help you avoid code duplication.
|
||||
|
||||
|
||||
!!! tip
|
||||
@@ -274,11 +301,11 @@ The end result is that the item paths are now:
|
||||
Now, run `uvicorn`, using the module `app.main` and the variable `app`:
|
||||
|
||||
```bash
|
||||
uvicorn app.main:app --debug
|
||||
uvicorn app.main:app --reload
|
||||
```
|
||||
|
||||
And open the docs at <a href="http://127.0.0.1:8000/docs" target="_blank">http://127.0.0.1:8000/docs</a>.
|
||||
|
||||
You will see the automatic API docs, including the paths from all the submodules:
|
||||
You will see the automatic API docs, including the paths from all the submodules, using the correct paths (and prefixes) and the correct tags:
|
||||
|
||||
<img src="/img/tutorial/bigger-applications/image01.png">
|
||||
|
||||
@@ -120,7 +120,7 @@ To see all the options you have, checkout the docs for <a href="https://pydantic
|
||||
|
||||
For example, as in the `Image` model we have a `url` field, we can declare it to be instead of a `str`, a Pydantic's `UrlStr`:
|
||||
|
||||
```Python hl_lines="5 11"
|
||||
```Python hl_lines="4 10"
|
||||
{!./src/body_nested_models/tutorial005.py!}
|
||||
```
|
||||
|
||||
@@ -130,7 +130,7 @@ The string will be checked to be a valid URL, and documented in JSON Schema / Op
|
||||
|
||||
You can also use Pydantic models as subtypes of `list`, `set`, etc:
|
||||
|
||||
```Python hl_lines="21"
|
||||
```Python hl_lines="20"
|
||||
{!./src/body_nested_models/tutorial006.py!}
|
||||
```
|
||||
|
||||
@@ -167,7 +167,7 @@ This will expect (convert, validate, document, etc) a JSON body like:
|
||||
|
||||
You can define arbitrarily deeply nested models:
|
||||
|
||||
```Python hl_lines="10 15 21 24 28"
|
||||
```Python hl_lines="9 14 20 23 27"
|
||||
{!./src/body_nested_models/tutorial007.py!}
|
||||
```
|
||||
|
||||
@@ -184,7 +184,7 @@ images: List[Image]
|
||||
|
||||
as in:
|
||||
|
||||
```Python hl_lines="16"
|
||||
```Python hl_lines="15"
|
||||
{!./src/body_nested_models/tutorial008.py!}
|
||||
```
|
||||
|
||||
|
||||
87
docs/tutorial/debugging.md
Normal file
@@ -0,0 +1,87 @@
|
||||
You can connect the debugger in your editor, for example with Visual Studio Code or PyCharm.
|
||||
|
||||
## Call `uvicorn`
|
||||
|
||||
In your FastAPI application, import and run `uvicorn` directly:
|
||||
|
||||
```Python hl_lines="1 15"
|
||||
{!./src/debugging/tutorial001.py!}
|
||||
```
|
||||
|
||||
### About `__name__ == "__main__"`
|
||||
|
||||
The main purpose of the `__name__ == "__main__"` is to have some code that is executed when your file is called with:
|
||||
|
||||
```bash
|
||||
python myapp.py
|
||||
```
|
||||
|
||||
but is not called when another file imports it, like in:
|
||||
|
||||
```Python
|
||||
from myapp import app
|
||||
```
|
||||
|
||||
#### More details
|
||||
|
||||
Let's say your file is named `myapp.py`.
|
||||
|
||||
If you run it with:
|
||||
|
||||
```bash
|
||||
python myapp.py
|
||||
```
|
||||
|
||||
then the internal variable `__name__` in your file, created automatically by Python, will have as value the string `"__main__"`.
|
||||
|
||||
So, the section:
|
||||
|
||||
```Python
|
||||
uvicorn.run(app, host="0.0.0.0", port=8000)
|
||||
```
|
||||
|
||||
will run.
|
||||
|
||||
---
|
||||
|
||||
This won't happen if you import that module (file).
|
||||
|
||||
So, if you have another file `importer.py` with:
|
||||
|
||||
```Python
|
||||
from myapp import app
|
||||
|
||||
# Some more code
|
||||
```
|
||||
|
||||
in that case, the automatic variable inside of `myapp.py` will not have the variable `__name__` with a value of `"__main__"`.
|
||||
|
||||
So, the line:
|
||||
|
||||
```Python
|
||||
uvicorn.run(app, host="0.0.0.0", port=8000)
|
||||
```
|
||||
|
||||
will not be executed.
|
||||
|
||||
!!! info
|
||||
For more information, check <a href="https://docs.python.org/3/library/__main__.html" target="_blank">the official Python docs</a>.
|
||||
|
||||
## Run your code with your debugger
|
||||
|
||||
Because you are running the Uvicorn server directly from your code, you can call your Python program (your FastAPI application) directly form the debugger.
|
||||
|
||||
---
|
||||
|
||||
For example, in Visual Studio Code, you can:
|
||||
|
||||
* Go to the "Debug" panel.
|
||||
* "Add configuration...".
|
||||
* Select "Python"
|
||||
* Run the debugger with the option "`Python: Current File (Integrated Terminal)`".
|
||||
|
||||
It will then start the server with your **FastAPI** code, stop at your breakpoints, etc.
|
||||
|
||||
Here's how it might look:
|
||||
|
||||
<img src="/img/tutorial/debugging/image01.png">
|
||||
43
docs/tutorial/events.md
Normal file
@@ -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>.
|
||||
90
docs/tutorial/extending-openapi.md
Normal file
@@ -0,0 +1,90 @@
|
||||
!!! warning
|
||||
This is a rather advanced feature. You probably can skip it.
|
||||
|
||||
If you are just following the tutorial - user guide, you can probably skip this section.
|
||||
|
||||
If you already know that you need to modify the generated OpenAPI schema, continue reading.
|
||||
|
||||
|
||||
There are some cases where you might need to modify the generated OpenAPI schema.
|
||||
|
||||
In this section you will see how.
|
||||
|
||||
## The normal process
|
||||
|
||||
The normal (default) process, is as follows.
|
||||
|
||||
A `FastAPI` application (instance) has an `.openapi()` method that is expected to return the OpenAPI schema.
|
||||
|
||||
As part of the application object creation, a *path operation* for `/openapi.json` (or for whatever you set your `openapi_url`) is registered.
|
||||
|
||||
It just returns a JSON response with the result of the application's `.openapi()` method.
|
||||
|
||||
By default, what the method `.openapi()` does is check the property `.openapi_schema` to see if it has contents and return them.
|
||||
|
||||
If it doesn't, it generates them using the utility function at `fastapi.openapi.utils.get_openapi`.
|
||||
|
||||
And that function `get_openapi()` receives as parameters:
|
||||
|
||||
* `title`: The OpenAPI title, shown in the docs.
|
||||
* `version`: The version of your API, e.g. `2.5.0`.
|
||||
* `openapi_version`: The version of the OpenAPI specification used. By default, the latest: `3.0.2`.
|
||||
* `description`: The description of your API.
|
||||
* `routes`: A list of routes, these are each of the registered *path operations*. They are taken from `app.routes`.
|
||||
* `openapi_prefix`: The URL prefix to be used in your OpenAPI.
|
||||
|
||||
## Overriding the defaults
|
||||
|
||||
Using the information above, you can use the same utility function to generate the OpenAPI schema and override each part that you need.
|
||||
|
||||
For example, let's add <a href="https://github.com/Rebilly/ReDoc/blob/master/docs/redoc-vendor-extensions.md#x-logo" target="_blank">ReDoc's OpenAPI extension to include a custom logo</a>.
|
||||
|
||||
### Normal **FastAPI**
|
||||
|
||||
First, write all your **FastAPI** application as normally:
|
||||
|
||||
```Python hl_lines="1 4 7 8 9"
|
||||
{!./src/extending_openapi/tutorial001.py!}
|
||||
```
|
||||
|
||||
### Generate the OpenAPI schema
|
||||
|
||||
Then, use the same utility function to generate the OpenAPI schema, inside a `custom_openapi()` function:
|
||||
|
||||
```Python hl_lines="2 15 16 17 18 19 20"
|
||||
{!./src/extending_openapi/tutorial001.py!}
|
||||
```
|
||||
|
||||
### Modify the OpenAPI schema
|
||||
|
||||
Now you can add the ReDoc extension, adding a custom `x-logo` to the `info` "object" in the OpenAPI schema:
|
||||
|
||||
```Python hl_lines="21 22 23"
|
||||
{!./src/extending_openapi/tutorial001.py!}
|
||||
```
|
||||
|
||||
### Cache the OpenAPI schema
|
||||
|
||||
You can use the property `.openapi_schema` as a "cache", to store your generated schema.
|
||||
|
||||
That way, your application won't have to generate the schema every time a user opens your API docs.
|
||||
|
||||
It will be generated only once, and then the same cached schema will be used for the next requests.
|
||||
|
||||
```Python hl_lines="13 14 24 25"
|
||||
{!./src/extending_openapi/tutorial001.py!}
|
||||
```
|
||||
|
||||
### Override the method
|
||||
|
||||
Now you can replace the `.openapi()` method with your new function.
|
||||
|
||||
```Python hl_lines="28"
|
||||
{!./src/extending_openapi/tutorial001.py!}
|
||||
```
|
||||
|
||||
### Check it
|
||||
|
||||
Once you go to <a href="http://127.0.0.1:8000/redoc" target="_blank">http://127.0.0.1:8000/redoc</a> you will see that you are using your custom logo (in this example, **FastAPI**'s logo):
|
||||
|
||||
<img src="/img/tutorial/extending-openapi/image01.png">
|
||||
@@ -109,7 +109,7 @@ UserInDB(**user_in.dict())
|
||||
|
||||
So, we get a Pydantic model from the data in another Pydantic model.
|
||||
|
||||
#### Unrapping a `dict` and extra keywords
|
||||
#### Unwrapping a `dict` and extra keywords
|
||||
|
||||
And then adding the extra keyword argument `hashed_password=hashed_password`, like in:
|
||||
|
||||
@@ -152,6 +152,28 @@ That way, we can declare just the differences between the models (with plaintext
|
||||
{!./src/extra_models/tutorial002.py!}
|
||||
```
|
||||
|
||||
## `Union` or `anyOf`
|
||||
|
||||
You can declare a response to be the `Union` of two types, that means, that the response would be any of the two.
|
||||
|
||||
It will be defined in OpenAPI with `anyOf`.
|
||||
|
||||
To do that, use the standard Python type hint <a href="https://docs.python.org/3/library/typing.html#typing.Union" target="_blank">`typing.Union`</a>:
|
||||
|
||||
```Python hl_lines="1 14 15 18 19 20 33"
|
||||
{!./src/extra_models/tutorial003.py!}
|
||||
```
|
||||
|
||||
## List of models
|
||||
|
||||
The same way, you can declare responses of lists of objects.
|
||||
|
||||
For that, use the standard Python `typing.List`:
|
||||
|
||||
```Python hl_lines="1 20"
|
||||
{!./src/extra_models/tutorial004.py!}
|
||||
```
|
||||
|
||||
## Recap
|
||||
|
||||
Use multiple Pydantic models and inherit freely for each case.
|
||||
|
||||
@@ -9,7 +9,7 @@ Copy that to a file `main.py`.
|
||||
Run the live server:
|
||||
|
||||
```bash
|
||||
uvicorn main:app --debug
|
||||
uvicorn main:app --reload
|
||||
```
|
||||
|
||||
!!! note
|
||||
@@ -17,7 +17,7 @@ uvicorn main:app --debug
|
||||
|
||||
* `main`: the file `main.py` (the Python "module").
|
||||
* `app`: the object created inside of `main.py` with the line `app = FastAPI()`.
|
||||
* `--debug`: make the server restart after code changes. Only use for development.
|
||||
* `--reload`: make the server restart after code changes. Only use for development.
|
||||
|
||||
You will see an output like:
|
||||
|
||||
@@ -69,7 +69,7 @@ A "schema" is a definition or description of something. Not the code that implem
|
||||
|
||||
In this case, OpenAPI is a specification that dictates how to define a schema of your API.
|
||||
|
||||
This OpenAPI schema would include your API paths, the posible parameters they take, etc.
|
||||
This OpenAPI schema would include your API paths, the possible parameters they take, etc.
|
||||
|
||||
#### Data "schema"
|
||||
|
||||
@@ -146,7 +146,7 @@ This will be the main point of interaction to create all your API.
|
||||
This `app` is the same one referred by `uvicorn` in the command:
|
||||
|
||||
```bash
|
||||
uvicorn main:app --debug
|
||||
uvicorn main:app --reload
|
||||
```
|
||||
|
||||
If you create your app like:
|
||||
@@ -158,7 +158,7 @@ If you create your app like:
|
||||
And put it in a file `main.py`, then you would call `uvicorn` like:
|
||||
|
||||
```bash
|
||||
uvicorn main:my_awesome_api --debug
|
||||
uvicorn main:my_awesome_api --reload
|
||||
```
|
||||
|
||||
### Step 3: create a path operation
|
||||
@@ -311,4 +311,4 @@ There are many other objects and models that will be automatically converted to
|
||||
* Create an `app` instance.
|
||||
* Write a **path operation decorator** (like `@app.get("/")`).
|
||||
* Write a **path operation function** (like `def root(): ...` above).
|
||||
* Run the debugging server (like `uvicorn main:app --debug`).
|
||||
* Run the development server (like `uvicorn main:app --reload`).
|
||||
@@ -96,4 +96,4 @@ For example, you could override the default exception handler with:
|
||||
!!! info
|
||||
Note that in this example we set the exception handler with Starlette's `HTTPException` instead of FastAPI's `HTTPException`.
|
||||
|
||||
This would ensure that if you use a plug-in or any other third-party tool that raises Starlette's `HTTPException` directly, it will be catched by your exception handler.
|
||||
This would ensure that if you use a plug-in or any other third-party tool that raises Starlette's `HTTPException` directly, it will be caught by your exception handler.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
This tutorial shows you how to use **FastAPI** with all its features, step by step.
|
||||
|
||||
Eeach section gradually builds on the previous ones, but it's structured to separate topics, so that you can go directly to any specific one to solve your specific API needs.
|
||||
Each section gradually builds on the previous ones, but it's structured to separate topics, so that you can go directly to any specific one to solve your specific API needs.
|
||||
|
||||
It is also built to work as a future reference.
|
||||
|
||||
@@ -13,7 +13,7 @@ All the code blocks can be copied and used directly (they are actually tested Py
|
||||
To run any of the examples, copy the code to a file `main.py`, and start `uvicorn` with:
|
||||
|
||||
```bash
|
||||
uvicorn main:app --debug
|
||||
uvicorn main:app --reload
|
||||
```
|
||||
|
||||
It is **HIGHLY encouraged** that you write or copy the code, edit it and run it locally.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
**FastAPI** allows you to declare additonal information and validation for your parameters.
|
||||
**FastAPI** allows you to declare additional information and validation for your parameters.
|
||||
|
||||
Let's take this application as example:
|
||||
|
||||
@@ -124,6 +124,43 @@ So, when you need to declare a value as required while using `Query`, you can us
|
||||
|
||||
This will let **FastAPI** know that this parameter is required.
|
||||
|
||||
## Query parameter list / multiple values
|
||||
|
||||
When you define a query parameter explicitly with `Query` you can also declare it to receive a list of values, or said in other way, to receive multiple values.
|
||||
|
||||
For example, to declare a query parameter `q` that can appear multiple times in the URL, you can write:
|
||||
|
||||
```Python hl_lines="9"
|
||||
{!./src/query_params_str_validations/tutorial011.py!}
|
||||
```
|
||||
|
||||
Then, with a URL like:
|
||||
|
||||
```
|
||||
http://localhost:8000/items/?q=foo&q=bar
|
||||
```
|
||||
|
||||
you would receive the multiple `q` *query parameters'* values (`foo` and `bar`) in a Python `list` inside your *path operation function*, in the *function parameter* `q`.
|
||||
|
||||
So, the response to that URL would be:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"q": [
|
||||
"foo",
|
||||
"bar"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
!!! tip
|
||||
To declare a query parameter with a type of `list`, like in the example above, you need to explicitly use `Query`, otherwise it would be interpreted as a request body.
|
||||
|
||||
|
||||
The interactive API docs will update accordingly, to allow multiple values:
|
||||
|
||||
<img src="/img/tutorial/query-params-str-validations/image02.png">
|
||||
|
||||
## Declare more metadata
|
||||
|
||||
You can add more information about the parameter.
|
||||
|
||||
@@ -2,7 +2,7 @@ You can define files to be uploaded by the client using `File`.
|
||||
|
||||
## Import `File`
|
||||
|
||||
Import `File` from `fastapi`:
|
||||
Import `File` and `UploadFile` from `fastapi`:
|
||||
|
||||
```Python hl_lines="1"
|
||||
{!./src/request_files/tutorial001.py!}
|
||||
@@ -16,14 +16,78 @@ Create file parameters the same way you would for `Body` or `Form`:
|
||||
{!./src/request_files/tutorial001.py!}
|
||||
```
|
||||
|
||||
The files will be uploaded as form data and you will receive the contents as `bytes`.
|
||||
|
||||
!!! info
|
||||
`File` is a class that inherits directly from `Form`.
|
||||
|
||||
!!! info
|
||||
To declare File bodies, you need to use `File`, because otherwise the parameters would be interpreted as query parameters or body (JSON) parameters.
|
||||
|
||||
The files will be uploaded as "form data".
|
||||
|
||||
If you declare the type of your *path operation function* parameter as `bytes`, **FastAPI** will read the file for you and you will receive the contents as `bytes`.
|
||||
|
||||
Have in mind that this means that the whole contents will be stored in memory. This will work well for small files.
|
||||
|
||||
But there are several cases in where you might benefit from using `UploadFile`.
|
||||
|
||||
|
||||
## `File` parameters with `UploadFile`
|
||||
|
||||
Define a `File` parameter with a type of `UploadFile`:
|
||||
|
||||
```Python hl_lines="12"
|
||||
{!./src/request_files/tutorial001.py!}
|
||||
```
|
||||
|
||||
Using `UploadFile` has several advantages over `bytes`:
|
||||
|
||||
* It uses a "spooled" file:
|
||||
* A file stored in memory up to a maximum size limit, and after passing this limit it will be stored in disk.
|
||||
* This means that it will work well for large files like images, videos, large binaries, etc. All without consuming all the memory.
|
||||
* You can get metadata from the uploaded file.
|
||||
* It has a <a href="https://docs.python.org/3/glossary.html#term-file-like-object" target="_blank">file-like</a> `async` interface.
|
||||
* It exposes an actual Python <a href="https://docs.python.org/3/library/tempfile.html#tempfile.SpooledTemporaryFile" target="_blank">`SpooledTemporaryFile`</a> object that you can pass directly to other libraries that expect a file-like object.
|
||||
|
||||
|
||||
### `UploadFile`
|
||||
|
||||
`UploadFile` has the following attributes:
|
||||
|
||||
* `filename`: A `str` with the original file name that was uploaded (e.g. `myimage.jpg`).
|
||||
* `content_type`: A `str` with the content type (MIME type / media type) (e.g. `image/jpeg`).
|
||||
* `file`: A <a href="https://docs.python.org/3/library/tempfile.html#tempfile.SpooledTemporaryFile" target="_blank">`SpooledTemporaryFile`</a> (a <a href="https://docs.python.org/3/glossary.html#term-file-like-object" target="_blank">file-like</a> object). This is the actual Python file that you can pass directly to other functions or libraries that expect a "file-like" object.
|
||||
|
||||
|
||||
`UploadFile` has the following `async` methods. They all call the corresponding file methods underneath (using the internal `SpooledTemporaryFile`).
|
||||
|
||||
* `write(data)`: Writes `data` (`str` or `bytes`) to the file.
|
||||
* `read(size)`: Reads `size` (`int`) bytes/characters of the file.
|
||||
* `seek(offset)`: Goes to the byte position `offset` (`int`) in the file.
|
||||
* E.g., `await myfile.seek(0)` would go to the start of the file.
|
||||
* This is especially useful if you run `await myfile.read()` once and then need to read the contents again.
|
||||
* `close()`: Closes the file.
|
||||
|
||||
As all these methods are `async` methods, you need to "await" them.
|
||||
|
||||
For example, inside of an `async` *path operation function* you can get the contents with:
|
||||
|
||||
```Python
|
||||
contents = await myfile.read()
|
||||
```
|
||||
|
||||
If you are inside of a normal `def` *path operation function*, you can access the `UploadFile.file` directly, for example:
|
||||
|
||||
```Python
|
||||
contents = myfile.file.read()
|
||||
```
|
||||
|
||||
!!! note "`async` Technical Details"
|
||||
When you use the `async` methods, **FastAPI** runs the file methods in a threadpool and awaits for them.
|
||||
|
||||
|
||||
!!! note "Starlette Technical Details"
|
||||
**FastAPI**'s `UploadFile` inherits directly from **Starlette**'s `UploadFile`, but adds some necessary parts to make it compatible with **Pydantic** and the other parts of FastAPI.
|
||||
|
||||
## "Form Data"?
|
||||
|
||||
The way HTML forms (`<form></form>`) sends the data to the server normally uses a "special" encoding for that data, it's different from JSON.
|
||||
@@ -35,7 +99,7 @@ The way HTML forms (`<form></form>`) sends the data to the server normally uses
|
||||
|
||||
But when the form includes files, it is encoded as `multipart/form-data`. If you use `File`, **FastAPI** will know it has to get the files from the correct part of the body.
|
||||
|
||||
If you want to read more about these encondings and form fields, head to the <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST" target="_blank"><abbr title="Mozilla Developer Network">MDN</abbr> web docs for <code>POST</code></a>.
|
||||
If you want to read more about these encodings and form fields, head to the <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST" target="_blank"><abbr title="Mozilla Developer Network">MDN</abbr> web docs for <code>POST</code></a>.
|
||||
|
||||
|
||||
!!! warning
|
||||
|
||||
@@ -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`.
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ The way HTML forms (`<form></form>`) sends the data to the server normally uses
|
||||
|
||||
But when the form includes files, it is encoded as `multipart/form-data`. You'll read about handling files in the next chapter.
|
||||
|
||||
If you want to read more about these encondings and form fields, head to the <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST" target="_blank"><abbr title="Mozilla Developer Network">MDN</abbr> web docs for <code>POST</code></a>.
|
||||
If you want to read more about these encodings and form fields, head to the <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST" target="_blank"><abbr title="Mozilla Developer Network">MDN</abbr> web docs for <code>POST</code></a>.
|
||||
|
||||
|
||||
!!! warning
|
||||
|
||||
@@ -24,6 +24,9 @@ But most importantly:
|
||||
|
||||
* Will limit the output data to that of the model. We'll see how that's important below.
|
||||
|
||||
!!! note "Technical Details"
|
||||
The response model is declared in this parameter instead of as a function return type annotation, because the path function may not actually return that response model but rather return a `dict`, database object or some other model, and then use the `response_model` to perform the field limiting and serialization.
|
||||
|
||||
## Return the same input data
|
||||
|
||||
Here we are declaring a `UserIn` model, it will contain a plaintext password:
|
||||
@@ -40,7 +43,7 @@ And we are using this model to declare our input and the same model to declare o
|
||||
|
||||
Now, whenever a browser is creating a user with a password, the API will return the same password in the response.
|
||||
|
||||
In this case, it might not be a problem, becase the user himself is sending the password.
|
||||
In this case, it might not be a problem, because the user himself is sending the password.
|
||||
|
||||
But if we use the same model for another path operation, we could be sending the passwords of our users to every client.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -30,7 +30,7 @@ So, we can have sub-dependencies using `Security` too.
|
||||
|
||||
`get_current_user` will have a `Security` dependency with the same `oauth2_scheme` we created before.
|
||||
|
||||
The same as we were doing before in the path operation direclty, our new dependency will receive a `token` as a `str` from the `Security` dependency:
|
||||
The same as we were doing before in the path operation directly, our new dependency will receive a `token` as a `str` from the `Security` dependency:
|
||||
|
||||
```Python hl_lines="25"
|
||||
{!./src/security/tutorial002.py!}
|
||||
|
||||
@@ -22,7 +22,7 @@ If you want to play with JWT tokens and see how they work, check <a href="https:
|
||||
|
||||
## Install `PyJWT`
|
||||
|
||||
We need to install `PyJWT` to generate and verity the JWT tokens in Python:
|
||||
We need to install `PyJWT` to generate and verify the JWT tokens in Python:
|
||||
|
||||
```bash
|
||||
pip install pyjwt
|
||||
@@ -198,7 +198,7 @@ Many packages that simplify it a lot have to make many compromises with the data
|
||||
|
||||
**FastAPI** doesn't make any compromise with any database, data model or tool.
|
||||
|
||||
It gives you all the flexibility to chose the ones that fit your project the best.
|
||||
It gives you all the flexibility to choose the ones that fit your project the best.
|
||||
|
||||
And you can use directly many well maintained and widely used packages like `passlib` and `pyjwt`, because **FastAPI** doesn't require any complex mechanisms to integrate external packages.
|
||||
|
||||
|
||||
@@ -24,14 +24,14 @@ The form field name is `scope` (in singular), but it is actually a long string w
|
||||
|
||||
Each "scope" is just a string (without spaces).
|
||||
|
||||
They are normally used to declare specific security permissions, for exampe:
|
||||
They are normally used to declare specific security permissions, for example:
|
||||
|
||||
* `"users:read"` or `"users:write"` are common examples.
|
||||
* `instagram_basic` is used by Facebook / Instagram.
|
||||
* `https://www.googleapis.com/auth/drive` is used by Google.
|
||||
|
||||
!!! info
|
||||
In OAuth2 a "scope" is just a string that declares a specific permision required.
|
||||
In OAuth2 a "scope" is just a string that declares a specific permission required.
|
||||
|
||||
It doesn't matter if it has other characters like `:`, or if it is a URL.
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ For now, don't pay attention to the rest, only the imports:
|
||||
|
||||
Define the database that SQLAlchemy should "connect" to:
|
||||
|
||||
```Python hl_lines="8"
|
||||
```Python hl_lines="9"
|
||||
{!./src/sql_databases/tutorial001.py!}
|
||||
```
|
||||
|
||||
@@ -59,7 +59,7 @@ SQLALCHEMY_DATABASE_URI = "postgresql://user:password@postgresserver/db"
|
||||
|
||||
## Create the SQLAlchemy `engine`
|
||||
|
||||
```Python hl_lines="11 12 13"
|
||||
```Python hl_lines="12 13 14"
|
||||
{!./src/sql_databases/tutorial001.py!}
|
||||
```
|
||||
|
||||
@@ -86,11 +86,11 @@ This object (class) is not a connection to the database yet, but once we create
|
||||
|
||||
We name it `SessionLocal` to distinguish it form the `Session` we are importing from SQLAlchemy.
|
||||
|
||||
We will use `Session` to declare types later and getter better editor support and completion.
|
||||
We will use `Session` to declare types later and to get better editor support and completion.
|
||||
|
||||
For now, create the `SessionLocal`:
|
||||
|
||||
```Python hl_lines="14"
|
||||
```Python hl_lines="15"
|
||||
{!./src/sql_databases/tutorial001.py!}
|
||||
```
|
||||
|
||||
@@ -108,10 +108,17 @@ A "middleware" is a function that is always executed for each request, and have
|
||||
|
||||
This middleware (just a function) will create a new SQLAlchemy `SessionLocal` for each request, add it to the request and then close it once the request is finished.
|
||||
|
||||
```Python hl_lines="67 68 69 70 71 72"
|
||||
```Python hl_lines="68 69 70 71 72 73 74 75 76"
|
||||
{!./src/sql_databases/tutorial001.py!}
|
||||
```
|
||||
|
||||
!!! info
|
||||
We put the creation of the `SessionLocal()` and handling of the requests in a `try` block.
|
||||
|
||||
And then we close it in the `finally` block.
|
||||
|
||||
This way we make sure the database session is always closed after the request. Even if there was an exception in the middle.
|
||||
|
||||
### About `request.state`
|
||||
|
||||
<a href="https://www.starlette.io/requests/#other-state" target="_blank">`request.state` is a property of each Starlette `Request` object</a>, it is there to store arbitrary objects attached to the request itself, like the database session in this case.
|
||||
@@ -126,11 +133,11 @@ And when using the dependency in a path operation function, we declare it with t
|
||||
|
||||
This will then give us better editor support inside the path operation function, because the editor will know that the `db` parameter is of type `Session`.
|
||||
|
||||
```Python hl_lines="53 54 68"
|
||||
```Python hl_lines="54 55 69"
|
||||
{!./src/sql_databases/tutorial001.py!}
|
||||
```
|
||||
|
||||
!!! info "Technical Detail"
|
||||
!!! info "Technical Details"
|
||||
The parameter `db` is actually of type `SessionLocal`, but this class (created with `sessionmaker()`) is a "proxy" of a SQLAlchemy `Session`, so, the editor doesn't really know what methods are provided.
|
||||
|
||||
But by declaring the type as `Session`, the editor now can know the available methods (`.add()`, `.query()`, `.commit()`, etc) and can provide better support (like completion). The type declaration doesn't affect the actual object.
|
||||
@@ -145,13 +152,13 @@ That way you don't have to declare them explicitly in every model.
|
||||
|
||||
So, your models will behave very similarly to, for example, Flask-SQLAlchemy.
|
||||
|
||||
```Python hl_lines="17 18 19 20 21"
|
||||
```Python hl_lines="18 19 20 21 22"
|
||||
{!./src/sql_databases/tutorial001.py!}
|
||||
```
|
||||
|
||||
## Create the SQLAlchemy `Base` model
|
||||
|
||||
```Python hl_lines="24"
|
||||
```Python hl_lines="25"
|
||||
{!./src/sql_databases/tutorial001.py!}
|
||||
```
|
||||
|
||||
@@ -161,7 +168,7 @@ Now this is finally code specific to your app.
|
||||
|
||||
Here's a user model that will be a table in the database:
|
||||
|
||||
```Python hl_lines="27 28 29 30 31"
|
||||
```Python hl_lines="28 29 30 31 32"
|
||||
{!./src/sql_databases/tutorial001.py!}
|
||||
```
|
||||
|
||||
@@ -169,7 +176,7 @@ Here's a user model that will be a table in the database:
|
||||
|
||||
In a very simplistic way, initialize your database (create the tables, etc) and make sure you have a first user:
|
||||
|
||||
```Python hl_lines="34 36 38 39 40 41 42 44"
|
||||
```Python hl_lines="35 37 39 40 41 42 43 45"
|
||||
{!./src/sql_databases/tutorial001.py!}
|
||||
```
|
||||
|
||||
@@ -197,7 +204,7 @@ Also, as all the functionality is self-contained in the same code, you can copy
|
||||
|
||||
By creating a function that is only dedicated to getting your user from a `user_id` (or any other parameter) independent of your path operation function, you can more easily re-use it in multiple parts and also add <abbr title="Automated tests, written in code, that check if another piece of code is working correctly.">unit tests</abbr> for it:
|
||||
|
||||
```Python hl_lines="48 49"
|
||||
```Python hl_lines="49 50"
|
||||
{!./src/sql_databases/tutorial001.py!}
|
||||
```
|
||||
|
||||
@@ -207,7 +214,7 @@ Now, finally, here's the standard **FastAPI** code.
|
||||
|
||||
Create your app and path operation function:
|
||||
|
||||
```Python hl_lines="58 61 62 63 64"
|
||||
```Python hl_lines="59 62 63 64 65"
|
||||
{!./src/sql_databases/tutorial001.py!}
|
||||
```
|
||||
|
||||
@@ -243,10 +250,13 @@ user = get_user(db_session, user_id=user_id)
|
||||
|
||||
Then we should declare the path operation without `async def`, just with a normal `def`:
|
||||
|
||||
```Python hl_lines="62"
|
||||
```Python hl_lines="63"
|
||||
{!./src/sql_databases/tutorial001.py!}
|
||||
```
|
||||
|
||||
!!! note "Very Technical Details"
|
||||
If you are curious and have a deep technical knowledge, you can check <a href="https://fastapi.tiangolo.com/async/#very-technical-details" target="_blank">the very technical details of how this `async def` vs `def` is handled</a>.
|
||||
|
||||
## Migrations
|
||||
|
||||
Because we are using SQLAlchemy directly and we don't require any kind of plug-in for it to work with **FastAPI**, we could integrate database <abbr title="Automatically updating the database to have any new column we define in our models.">migrations</abbr> with <a href="https://alembic.sqlalchemy.org" target="_blank">Alembic</a> directly.
|
||||
@@ -267,7 +277,7 @@ You can copy it, let's say, to a file `main.py`.
|
||||
Then you can run it with Uvicorn:
|
||||
|
||||
```bash
|
||||
uvicorn main:app --debug
|
||||
uvicorn main:app --reload
|
||||
```
|
||||
|
||||
And then, you can open your browser at <a href="http://127.0.0.1:8000/docs" target="_blank">http://127.0.0.1:8000/docs</a>.
|
||||
@@ -297,7 +307,7 @@ That's something that you can improve in this example application, here's the cu
|
||||
}
|
||||
```
|
||||
|
||||
## Interact with the database direclty
|
||||
## Interact with the database directly
|
||||
|
||||
If you want to explore the SQLite database (file) directly, independently of FastAPI, to debug its contents, add tables, columns, records, modify data, etc. you can use <a href="https://sqlitebrowser.org/" target="_blank">DB Browser for SQLite</a>.
|
||||
|
||||
|
||||
@@ -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>.
|
||||
|
||||
@@ -21,7 +21,7 @@ Although any other parameter declared normally (for example, the body with a Pyd
|
||||
|
||||
But there are specific cases where it's useful to get the `Request` object.
|
||||
|
||||
## Use the `Request` object direclty
|
||||
## Use the `Request` object directly
|
||||
|
||||
Let's imagine you want to get the client's IP address/host inside of your *path operation function*.
|
||||
|
||||
|
||||
93
docs/tutorial/websockets.md
Normal file
@@ -0,0 +1,93 @@
|
||||
|
||||
You can use <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API" target="_blank">WebSockets</a> with **FastAPI**.
|
||||
|
||||
## WebSockets client
|
||||
|
||||
### In production
|
||||
|
||||
In your production system, you probably have a frontend created with a modern framework like React, Vue.js or Angular.
|
||||
|
||||
And to communicate using WebSockets with your backend you would probably use your frontend's utilities.
|
||||
|
||||
Or you might have a native mobile application that communicates with your WebSocket backend directly, in native code.
|
||||
|
||||
Or you might have any other way to communicate with the WebSocket endpoint.
|
||||
|
||||
---
|
||||
|
||||
But for this example, we'll use a very simple HTML document with some JavaScript, all inside a long string.
|
||||
|
||||
This, of course, is not optimal and you wouldn't use it for production.
|
||||
|
||||
In production you would have one of the options above.
|
||||
|
||||
But it's the simplest way to focus on the server-side of WebSockets and have a working example:
|
||||
|
||||
```Python hl_lines="2 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 42 43 44"
|
||||
{!./src/websockets/tutorial001.py!}
|
||||
```
|
||||
|
||||
## Create a `websocket_route`
|
||||
|
||||
In your **FastAPI** application, create a `websocket_route`:
|
||||
|
||||
```Python hl_lines="3 47 48"
|
||||
{!./src/websockets/tutorial001.py!}
|
||||
```
|
||||
|
||||
!!! tip
|
||||
In this example we are importing `WebSocket` from `starlette.websockets` to use it in the type declaration in the WebSocket route function.
|
||||
|
||||
That is not required, but it's recommended as it will provide you completion and checks inside the function.
|
||||
|
||||
|
||||
!!! info
|
||||
This `websocket_route` we are using comes directly from <a href="https://www.starlette.io/applications/" target="_blank">Starlette</a>.
|
||||
|
||||
That's why the naming convention is not the same as with other API path operations (`get`, `post`, etc).
|
||||
|
||||
|
||||
## Await for messages and send messages
|
||||
|
||||
In your WebSocket route you can `await` for messages and send messages.
|
||||
|
||||
```Python hl_lines="49 50 51 52 53"
|
||||
{!./src/websockets/tutorial001.py!}
|
||||
```
|
||||
|
||||
You can receive and send binary, text, and JSON data.
|
||||
|
||||
To learn more about the options, check Starlette's documentation for:
|
||||
|
||||
* <a href="https://www.starlette.io/applications/" target="_blank">Applications (`websocket_route`)</a>.
|
||||
* <a href="https://www.starlette.io/websockets/" target="_blank">The `WebSocket` class</a>.
|
||||
* <a href="https://www.starlette.io/endpoints/#websocketendpoint" target="_blank">Class-based WebSocket handling</a>.
|
||||
|
||||
|
||||
## Test it
|
||||
|
||||
If your file is named `main.py`, run your application with:
|
||||
|
||||
```bash
|
||||
uvicorn main:app --reload
|
||||
```
|
||||
|
||||
Open your browser at <a href="http://127.0.0.1:8000" target="_blank">http://127.0.0.1:8000</a>.
|
||||
|
||||
You will see a simple page like:
|
||||
|
||||
<img src="/img/tutorial/websockets/image01.png">
|
||||
|
||||
You can type messages in the input box, and send them:
|
||||
|
||||
<img src="/img/tutorial/websockets/image02.png">
|
||||
|
||||
And your **FastAPI** application with WebSockets will respond back:
|
||||
|
||||
<img src="/img/tutorial/websockets/image03.png">
|
||||
|
||||
You can send (and receive) many messages:
|
||||
|
||||
<img src="/img/tutorial/websockets/image04.png">
|
||||
|
||||
And all of them will use the same WebSocket connection.
|
||||
@@ -1,8 +1,11 @@
|
||||
"""FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
|
||||
|
||||
__version__ = "0.6.2"
|
||||
__version__ = "0.12.1"
|
||||
|
||||
from starlette.background import BackgroundTasks
|
||||
|
||||
from .applications import FastAPI
|
||||
from .routing import APIRouter
|
||||
from .params import Body, Path, Query, Header, Cookie, Form, File, Security, Depends
|
||||
from .datastructures import UploadFile
|
||||
from .exceptions import HTTPException
|
||||
from .params import Body, Cookie, Depends, File, Form, Header, Path, Query, Security
|
||||
from .routing import APIRouter
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import Any, Callable, Dict, List, Optional, Type
|
||||
from typing import Any, Callable, Dict, List, Optional, Type, Union
|
||||
|
||||
from fastapi import routing
|
||||
from fastapi.openapi.docs import get_redoc_html, get_swagger_ui_html
|
||||
@@ -114,6 +114,7 @@ class FastAPI(Starlette):
|
||||
summary: str = None,
|
||||
description: str = None,
|
||||
response_description: str = "Successful Response",
|
||||
responses: Dict[Union[int, str], Dict[str, Any]] = None,
|
||||
deprecated: bool = None,
|
||||
methods: List[str] = None,
|
||||
operation_id: str = None,
|
||||
@@ -130,6 +131,7 @@ class FastAPI(Starlette):
|
||||
summary=summary,
|
||||
description=description,
|
||||
response_description=response_description,
|
||||
responses=responses or {},
|
||||
deprecated=deprecated,
|
||||
methods=methods,
|
||||
operation_id=operation_id,
|
||||
@@ -148,6 +150,7 @@ class FastAPI(Starlette):
|
||||
summary: str = None,
|
||||
description: str = None,
|
||||
response_description: str = "Successful Response",
|
||||
responses: Dict[Union[int, str], Dict[str, Any]] = None,
|
||||
deprecated: bool = None,
|
||||
methods: List[str] = None,
|
||||
operation_id: str = None,
|
||||
@@ -165,6 +168,7 @@ class FastAPI(Starlette):
|
||||
summary=summary,
|
||||
description=description,
|
||||
response_description=response_description,
|
||||
responses=responses or {},
|
||||
deprecated=deprecated,
|
||||
methods=methods,
|
||||
operation_id=operation_id,
|
||||
@@ -176,8 +180,17 @@ class FastAPI(Starlette):
|
||||
|
||||
return decorator
|
||||
|
||||
def include_router(self, router: routing.APIRouter, *, prefix: str = "") -> None:
|
||||
self.router.include_router(router, prefix=prefix)
|
||||
def include_router(
|
||||
self,
|
||||
router: routing.APIRouter,
|
||||
*,
|
||||
prefix: str = "",
|
||||
tags: List[str] = None,
|
||||
responses: Dict[Union[int, str], Dict[str, Any]] = None,
|
||||
) -> None:
|
||||
self.router.include_router(
|
||||
router, prefix=prefix, tags=tags, responses=responses or {}
|
||||
)
|
||||
|
||||
def get(
|
||||
self,
|
||||
@@ -189,6 +202,7 @@ class FastAPI(Starlette):
|
||||
summary: str = None,
|
||||
description: str = None,
|
||||
response_description: str = "Successful Response",
|
||||
responses: Dict[Union[int, str], Dict[str, Any]] = None,
|
||||
deprecated: bool = None,
|
||||
operation_id: str = None,
|
||||
include_in_schema: bool = True,
|
||||
@@ -203,6 +217,7 @@ class FastAPI(Starlette):
|
||||
summary=summary,
|
||||
description=description,
|
||||
response_description=response_description,
|
||||
responses=responses or {},
|
||||
deprecated=deprecated,
|
||||
operation_id=operation_id,
|
||||
include_in_schema=include_in_schema,
|
||||
@@ -220,6 +235,7 @@ class FastAPI(Starlette):
|
||||
summary: str = None,
|
||||
description: str = None,
|
||||
response_description: str = "Successful Response",
|
||||
responses: Dict[Union[int, str], Dict[str, Any]] = None,
|
||||
deprecated: bool = None,
|
||||
operation_id: str = None,
|
||||
include_in_schema: bool = True,
|
||||
@@ -234,6 +250,7 @@ class FastAPI(Starlette):
|
||||
summary=summary,
|
||||
description=description,
|
||||
response_description=response_description,
|
||||
responses=responses or {},
|
||||
deprecated=deprecated,
|
||||
operation_id=operation_id,
|
||||
include_in_schema=include_in_schema,
|
||||
@@ -251,6 +268,7 @@ class FastAPI(Starlette):
|
||||
summary: str = None,
|
||||
description: str = None,
|
||||
response_description: str = "Successful Response",
|
||||
responses: Dict[Union[int, str], Dict[str, Any]] = None,
|
||||
deprecated: bool = None,
|
||||
operation_id: str = None,
|
||||
include_in_schema: bool = True,
|
||||
@@ -265,6 +283,7 @@ class FastAPI(Starlette):
|
||||
summary=summary,
|
||||
description=description,
|
||||
response_description=response_description,
|
||||
responses=responses or {},
|
||||
deprecated=deprecated,
|
||||
operation_id=operation_id,
|
||||
include_in_schema=include_in_schema,
|
||||
@@ -282,6 +301,7 @@ class FastAPI(Starlette):
|
||||
summary: str = None,
|
||||
description: str = None,
|
||||
response_description: str = "Successful Response",
|
||||
responses: Dict[Union[int, str], Dict[str, Any]] = None,
|
||||
deprecated: bool = None,
|
||||
operation_id: str = None,
|
||||
include_in_schema: bool = True,
|
||||
@@ -296,6 +316,7 @@ class FastAPI(Starlette):
|
||||
summary=summary,
|
||||
description=description,
|
||||
response_description=response_description,
|
||||
responses=responses or {},
|
||||
deprecated=deprecated,
|
||||
operation_id=operation_id,
|
||||
include_in_schema=include_in_schema,
|
||||
@@ -313,6 +334,7 @@ class FastAPI(Starlette):
|
||||
summary: str = None,
|
||||
description: str = None,
|
||||
response_description: str = "Successful Response",
|
||||
responses: Dict[Union[int, str], Dict[str, Any]] = None,
|
||||
deprecated: bool = None,
|
||||
operation_id: str = None,
|
||||
include_in_schema: bool = True,
|
||||
@@ -327,6 +349,7 @@ class FastAPI(Starlette):
|
||||
summary=summary,
|
||||
description=description,
|
||||
response_description=response_description,
|
||||
responses=responses or {},
|
||||
deprecated=deprecated,
|
||||
operation_id=operation_id,
|
||||
include_in_schema=include_in_schema,
|
||||
@@ -344,6 +367,7 @@ class FastAPI(Starlette):
|
||||
summary: str = None,
|
||||
description: str = None,
|
||||
response_description: str = "Successful Response",
|
||||
responses: Dict[Union[int, str], Dict[str, Any]] = None,
|
||||
deprecated: bool = None,
|
||||
operation_id: str = None,
|
||||
include_in_schema: bool = True,
|
||||
@@ -358,6 +382,7 @@ class FastAPI(Starlette):
|
||||
summary=summary,
|
||||
description=description,
|
||||
response_description=response_description,
|
||||
responses=responses or {},
|
||||
deprecated=deprecated,
|
||||
operation_id=operation_id,
|
||||
include_in_schema=include_in_schema,
|
||||
@@ -375,6 +400,7 @@ class FastAPI(Starlette):
|
||||
summary: str = None,
|
||||
description: str = None,
|
||||
response_description: str = "Successful Response",
|
||||
responses: Dict[Union[int, str], Dict[str, Any]] = None,
|
||||
deprecated: bool = None,
|
||||
operation_id: str = None,
|
||||
include_in_schema: bool = True,
|
||||
@@ -389,6 +415,7 @@ class FastAPI(Starlette):
|
||||
summary=summary,
|
||||
description=description,
|
||||
response_description=response_description,
|
||||
responses=responses or {},
|
||||
deprecated=deprecated,
|
||||
operation_id=operation_id,
|
||||
include_in_schema=include_in_schema,
|
||||
@@ -406,6 +433,7 @@ class FastAPI(Starlette):
|
||||
summary: str = None,
|
||||
description: str = None,
|
||||
response_description: str = "Successful Response",
|
||||
responses: Dict[Union[int, str], Dict[str, Any]] = None,
|
||||
deprecated: bool = None,
|
||||
operation_id: str = None,
|
||||
include_in_schema: bool = True,
|
||||
@@ -420,6 +448,7 @@ class FastAPI(Starlette):
|
||||
summary=summary,
|
||||
description=description,
|
||||
response_description=response_description,
|
||||
responses=responses or {},
|
||||
deprecated=deprecated,
|
||||
operation_id=operation_id,
|
||||
include_in_schema=include_in_schema,
|
||||
|
||||
15
fastapi/datastructures.py
Normal file
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -3,7 +3,18 @@ import inspect
|
||||
from copy import deepcopy
|
||||
from datetime import date, datetime, time, timedelta
|
||||
from decimal import Decimal
|
||||
from typing import Any, Callable, Dict, List, Mapping, Sequence, Tuple, Type, Union
|
||||
from typing import (
|
||||
Any,
|
||||
Callable,
|
||||
Dict,
|
||||
List,
|
||||
Mapping,
|
||||
Optional,
|
||||
Sequence,
|
||||
Tuple,
|
||||
Type,
|
||||
Union,
|
||||
)
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi import params
|
||||
@@ -16,7 +27,9 @@ from pydantic.errors import MissingError
|
||||
from pydantic.fields import Field, Required, Shape
|
||||
from pydantic.schema import get_annotation_from_schema
|
||||
from pydantic.utils import lenient_issubclass
|
||||
from starlette.background import BackgroundTasks
|
||||
from starlette.concurrency import run_in_threadpool
|
||||
from starlette.datastructures import UploadFile
|
||||
from starlette.requests import Headers, QueryParams, Request
|
||||
|
||||
param_supported_types = (
|
||||
@@ -87,7 +100,6 @@ def get_dependant(*, path: str, call: Callable, name: str = None) -> Dependant:
|
||||
lenient_issubclass(param.annotation, param_supported_types)
|
||||
or param.annotation == param.empty
|
||||
), f"Path params must be of one of the supported types"
|
||||
param = signature_params[param_name]
|
||||
add_param_to_fields(
|
||||
param=param,
|
||||
dependant=dependant,
|
||||
@@ -124,6 +136,8 @@ def get_dependant(*, path: str, call: Callable, name: str = None) -> Dependant:
|
||||
)
|
||||
elif lenient_issubclass(param.annotation, Request):
|
||||
dependant.request_param_name = param_name
|
||||
elif lenient_issubclass(param.annotation, BackgroundTasks):
|
||||
dependant.background_tasks_param_name = param_name
|
||||
elif not isinstance(param.default, params.Depends):
|
||||
add_param_to_body_fields(param=param, dependant=dependant)
|
||||
return dependant
|
||||
@@ -214,13 +228,20 @@ def is_coroutine_callable(call: Callable) -> bool:
|
||||
|
||||
|
||||
async def solve_dependencies(
|
||||
*, request: Request, dependant: Dependant, body: Dict[str, Any] = None
|
||||
) -> Tuple[Dict[str, Any], List[ErrorWrapper]]:
|
||||
*,
|
||||
request: Request,
|
||||
dependant: Dependant,
|
||||
body: Dict[str, Any] = None,
|
||||
background_tasks: BackgroundTasks = None,
|
||||
) -> Tuple[Dict[str, Any], List[ErrorWrapper], Optional[BackgroundTasks]]:
|
||||
values: Dict[str, Any] = {}
|
||||
errors: List[ErrorWrapper] = []
|
||||
for sub_dependant in dependant.dependencies:
|
||||
sub_values, sub_errors = await solve_dependencies(
|
||||
request=request, dependant=sub_dependant, body=body
|
||||
sub_values, sub_errors, background_tasks = await solve_dependencies(
|
||||
request=request,
|
||||
dependant=sub_dependant,
|
||||
body=body,
|
||||
background_tasks=background_tasks,
|
||||
)
|
||||
if sub_errors:
|
||||
errors.extend(sub_errors)
|
||||
@@ -257,7 +278,11 @@ async def solve_dependencies(
|
||||
errors.extend(body_errors)
|
||||
if dependant.request_param_name:
|
||||
values[dependant.request_param_name] = request
|
||||
return values, errors
|
||||
if dependant.background_tasks_param_name:
|
||||
if background_tasks is None:
|
||||
background_tasks = BackgroundTasks()
|
||||
values[dependant.background_tasks_param_name] = background_tasks
|
||||
return values, errors, background_tasks
|
||||
|
||||
|
||||
def request_params_to_args(
|
||||
@@ -323,6 +348,12 @@ async def request_body_to_args(
|
||||
else:
|
||||
values[field.name] = deepcopy(field.default)
|
||||
continue
|
||||
if (
|
||||
isinstance(field.schema, params.File)
|
||||
and lenient_issubclass(field.type_, bytes)
|
||||
and isinstance(value, UploadFile)
|
||||
):
|
||||
value = await value.read()
|
||||
v_, errors_ = field.validate(value, values, loc=("body", field.alias))
|
||||
if isinstance(errors_, ErrorWrapper):
|
||||
errors.append(errors_)
|
||||
@@ -333,6 +364,21 @@ async def request_body_to_args(
|
||||
return values, errors
|
||||
|
||||
|
||||
def get_schema_compatible_field(*, field: Field) -> Field:
|
||||
if lenient_issubclass(field.type_, UploadFile):
|
||||
return Field(
|
||||
name=field.name,
|
||||
type_=bytes,
|
||||
class_validators=field.class_validators,
|
||||
model_config=field.model_config,
|
||||
default=field.default,
|
||||
required=field.required,
|
||||
alias=field.alias,
|
||||
schema=field.schema,
|
||||
)
|
||||
return field
|
||||
|
||||
|
||||
def get_body_field(*, dependant: Dependant, name: str) -> Field:
|
||||
flat_dependant = get_flat_dependant(dependant)
|
||||
if not flat_dependant.body_params:
|
||||
@@ -340,11 +386,11 @@ def get_body_field(*, dependant: Dependant, name: str) -> Field:
|
||||
first_param = flat_dependant.body_params[0]
|
||||
embed = getattr(first_param.schema, "embed", None)
|
||||
if len(flat_dependant.body_params) == 1 and not embed:
|
||||
return first_param
|
||||
return get_schema_compatible_field(field=first_param)
|
||||
model_name = "Body_" + name
|
||||
BodyModel = create_model(model_name)
|
||||
for f in flat_dependant.body_params:
|
||||
BodyModel.__fields__[f.name] = f
|
||||
BodyModel.__fields__[f.name] = get_schema_compatible_field(field=f)
|
||||
required = any(True for f in flat_dependant.body_params if f.required)
|
||||
if any(isinstance(f.schema, params.File) for f in flat_dependant.body_params):
|
||||
BodySchema: Type[params.Body] = params.File
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -8,6 +8,7 @@ def get_swagger_ui_html(*, openapi_url: str, title: str) -> HTMLResponse:
|
||||
<html>
|
||||
<head>
|
||||
<link type="text/css" rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swagger-ui-dist@3/swagger-ui.css">
|
||||
<link rel="shortcut icon" href="https://fastapi.tiangolo.com/img/favicon.png">
|
||||
<title>
|
||||
"""
|
||||
+ title
|
||||
@@ -55,6 +56,7 @@ def get_redoc_html(*, openapi_url: str, title: str) -> HTMLResponse:
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
|
||||
<link rel="shortcut icon" href="https://fastapi.tiangolo.com/img/favicon.png">
|
||||
|
||||
<!--
|
||||
ReDoc doesn't change outer page styles
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -178,6 +178,23 @@ def get_openapi_path(
|
||||
definitions[
|
||||
"HTTPValidationError"
|
||||
] = validation_error_response_definition
|
||||
if route.responses:
|
||||
for (additional_status_code, response) in route.responses.items():
|
||||
assert isinstance(
|
||||
response, dict
|
||||
), "An additional response must be a dict"
|
||||
field = route.response_fields.get(additional_status_code)
|
||||
if field:
|
||||
response_schema, _ = field_schema(
|
||||
field, model_name_map=model_name_map, ref_prefix=REF_PREFIX
|
||||
)
|
||||
response.setdefault("content", {}).setdefault(
|
||||
"application/json", {}
|
||||
)["schema"] = response_schema
|
||||
response.setdefault("description", "Additional Response")
|
||||
operation.setdefault("responses", {})[
|
||||
str(additional_status_code)
|
||||
] = response
|
||||
status_code = str(route.status_code)
|
||||
response_schema = {"type": "string"}
|
||||
if lenient_issubclass(route.content_type, JSONResponse):
|
||||
@@ -189,13 +206,14 @@ def get_openapi_path(
|
||||
)
|
||||
else:
|
||||
response_schema = {}
|
||||
content = {route.content_type.media_type: {"schema": response_schema}}
|
||||
operation["responses"] = {
|
||||
status_code: {
|
||||
"description": route.response_description,
|
||||
"content": content,
|
||||
}
|
||||
}
|
||||
operation.setdefault("responses", {}).setdefault(status_code, {})[
|
||||
"description"
|
||||
] = route.response_description
|
||||
operation.setdefault("responses", {}).setdefault(
|
||||
status_code, {}
|
||||
).setdefault("content", {}).setdefault(route.content_type.media_type, {})[
|
||||
"schema"
|
||||
] = response_schema
|
||||
if all_route_params or route.body_field:
|
||||
operation["responses"][str(HTTP_422_UNPROCESSABLE_ENTITY)] = {
|
||||
"description": "Validation Error",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import asyncio
|
||||
import inspect
|
||||
import logging
|
||||
from typing import Any, Callable, List, Optional, Type
|
||||
from typing import Any, Callable, Dict, List, Optional, Type, Union
|
||||
|
||||
from fastapi import params
|
||||
from fastapi.dependencies.models import Dependant
|
||||
@@ -15,7 +15,6 @@ from pydantic.utils import lenient_issubclass
|
||||
from starlette import routing
|
||||
from starlette.concurrency import run_in_threadpool
|
||||
from starlette.exceptions import HTTPException
|
||||
from starlette.formparsers import UploadFile
|
||||
from starlette.requests import Request
|
||||
from starlette.responses import JSONResponse, Response
|
||||
from starlette.routing import compile_path, get_name, request_response
|
||||
@@ -57,10 +56,7 @@ def get_app(
|
||||
raw_body = await request.form()
|
||||
form_fields = {}
|
||||
for field, value in raw_body.items():
|
||||
if isinstance(value, UploadFile):
|
||||
form_fields[field] = await value.read()
|
||||
else:
|
||||
form_fields[field] = value
|
||||
form_fields[field] = value
|
||||
if form_fields:
|
||||
body = form_fields
|
||||
else:
|
||||
@@ -72,7 +68,7 @@ def get_app(
|
||||
raise HTTPException(
|
||||
status_code=400, detail="There was an error parsing the body"
|
||||
)
|
||||
values, errors = await solve_dependencies(
|
||||
values, errors, background_tasks = await solve_dependencies(
|
||||
request=request, dependant=dependant, body=body
|
||||
)
|
||||
if errors:
|
||||
@@ -87,11 +83,17 @@ def get_app(
|
||||
else:
|
||||
raw_response = await run_in_threadpool(dependant.call, **values)
|
||||
if isinstance(raw_response, Response):
|
||||
if raw_response.background is None:
|
||||
raw_response.background = background_tasks
|
||||
return raw_response
|
||||
response_data = serialize_response(
|
||||
field=response_field, response=raw_response
|
||||
)
|
||||
return content_type(content=response_data, status_code=status_code)
|
||||
return content_type(
|
||||
content=response_data,
|
||||
status_code=status_code,
|
||||
background=background_tasks,
|
||||
)
|
||||
|
||||
return app
|
||||
|
||||
@@ -108,6 +110,7 @@ class APIRoute(routing.Route):
|
||||
summary: str = None,
|
||||
description: str = None,
|
||||
response_description: str = "Successful Response",
|
||||
responses: Dict[Union[int, str], Dict[str, Any]] = None,
|
||||
deprecated: bool = None,
|
||||
name: str = None,
|
||||
methods: List[str] = None,
|
||||
@@ -141,6 +144,30 @@ class APIRoute(routing.Route):
|
||||
self.summary = summary
|
||||
self.description = description or self.endpoint.__doc__
|
||||
self.response_description = response_description
|
||||
self.responses = responses or {}
|
||||
response_fields = {}
|
||||
for additional_status_code, response in self.responses.items():
|
||||
assert isinstance(response, dict), "An additional response must be a dict"
|
||||
model = response.get("model")
|
||||
if model:
|
||||
assert lenient_issubclass(
|
||||
model, BaseModel
|
||||
), "A response model must be a Pydantic model"
|
||||
response_name = f"Response_{additional_status_code}_{self.name}"
|
||||
response_field = Field(
|
||||
name=response_name,
|
||||
type_=model,
|
||||
class_validators=None,
|
||||
default=None,
|
||||
required=False,
|
||||
model_config=UnconstrainedConfig,
|
||||
schema=Schema(None),
|
||||
)
|
||||
response_fields[additional_status_code] = response_field
|
||||
if response_fields:
|
||||
self.response_fields: Dict[Union[int, str], Field] = response_fields
|
||||
else:
|
||||
self.response_fields = {}
|
||||
self.deprecated = deprecated
|
||||
if methods is None:
|
||||
methods = ["GET"]
|
||||
@@ -178,6 +205,7 @@ class APIRouter(routing.Router):
|
||||
summary: str = None,
|
||||
description: str = None,
|
||||
response_description: str = "Successful Response",
|
||||
responses: Dict[Union[int, str], Dict[str, Any]] = None,
|
||||
deprecated: bool = None,
|
||||
methods: List[str] = None,
|
||||
operation_id: str = None,
|
||||
@@ -194,6 +222,7 @@ class APIRouter(routing.Router):
|
||||
summary=summary,
|
||||
description=description,
|
||||
response_description=response_description,
|
||||
responses=responses or {},
|
||||
deprecated=deprecated,
|
||||
methods=methods,
|
||||
operation_id=operation_id,
|
||||
@@ -213,6 +242,7 @@ class APIRouter(routing.Router):
|
||||
summary: str = None,
|
||||
description: str = None,
|
||||
response_description: str = "Successful Response",
|
||||
responses: Dict[Union[int, str], Dict[str, Any]] = None,
|
||||
deprecated: bool = None,
|
||||
methods: List[str] = None,
|
||||
operation_id: str = None,
|
||||
@@ -230,6 +260,7 @@ class APIRouter(routing.Router):
|
||||
summary=summary,
|
||||
description=description,
|
||||
response_description=response_description,
|
||||
responses=responses or {},
|
||||
deprecated=deprecated,
|
||||
methods=methods,
|
||||
operation_id=operation_id,
|
||||
@@ -241,23 +272,34 @@ class APIRouter(routing.Router):
|
||||
|
||||
return decorator
|
||||
|
||||
def include_router(self, router: "APIRouter", *, prefix: str = "") -> None:
|
||||
def include_router(
|
||||
self,
|
||||
router: "APIRouter",
|
||||
*,
|
||||
prefix: str = "",
|
||||
tags: List[str] = None,
|
||||
responses: Dict[Union[int, str], Dict[str, Any]] = None,
|
||||
) -> None:
|
||||
if prefix:
|
||||
assert prefix.startswith("/"), "A path prefix must start with '/'"
|
||||
assert not prefix.endswith(
|
||||
"/"
|
||||
), "A path prefix must not end with '/', as the routes will start with '/'"
|
||||
if responses is None:
|
||||
responses = {}
|
||||
for route in router.routes:
|
||||
if isinstance(route, APIRoute):
|
||||
combined_responses = {**responses, **route.responses}
|
||||
self.add_api_route(
|
||||
prefix + route.path,
|
||||
route.endpoint,
|
||||
response_model=route.response_model,
|
||||
status_code=route.status_code,
|
||||
tags=route.tags or [],
|
||||
tags=(route.tags or []) + (tags or []),
|
||||
summary=route.summary,
|
||||
description=route.description,
|
||||
response_description=route.response_description,
|
||||
responses=combined_responses,
|
||||
deprecated=route.deprecated,
|
||||
methods=route.methods,
|
||||
operation_id=route.operation_id,
|
||||
@@ -273,6 +315,10 @@ class APIRouter(routing.Router):
|
||||
include_in_schema=route.include_in_schema,
|
||||
name=route.name,
|
||||
)
|
||||
elif isinstance(route, routing.WebSocketRoute):
|
||||
self.add_websocket_route(
|
||||
prefix + route.path, route.endpoint, name=route.name
|
||||
)
|
||||
|
||||
def get(
|
||||
self,
|
||||
@@ -284,6 +330,7 @@ class APIRouter(routing.Router):
|
||||
summary: str = None,
|
||||
description: str = None,
|
||||
response_description: str = "Successful Response",
|
||||
responses: Dict[Union[int, str], Dict[str, Any]] = None,
|
||||
deprecated: bool = None,
|
||||
operation_id: str = None,
|
||||
include_in_schema: bool = True,
|
||||
@@ -298,6 +345,7 @@ class APIRouter(routing.Router):
|
||||
summary=summary,
|
||||
description=description,
|
||||
response_description=response_description,
|
||||
responses=responses or {},
|
||||
deprecated=deprecated,
|
||||
methods=["GET"],
|
||||
operation_id=operation_id,
|
||||
@@ -316,6 +364,7 @@ class APIRouter(routing.Router):
|
||||
summary: str = None,
|
||||
description: str = None,
|
||||
response_description: str = "Successful Response",
|
||||
responses: Dict[Union[int, str], Dict[str, Any]] = None,
|
||||
deprecated: bool = None,
|
||||
operation_id: str = None,
|
||||
include_in_schema: bool = True,
|
||||
@@ -330,6 +379,7 @@ class APIRouter(routing.Router):
|
||||
summary=summary,
|
||||
description=description,
|
||||
response_description=response_description,
|
||||
responses=responses or {},
|
||||
deprecated=deprecated,
|
||||
methods=["PUT"],
|
||||
operation_id=operation_id,
|
||||
@@ -348,6 +398,7 @@ class APIRouter(routing.Router):
|
||||
summary: str = None,
|
||||
description: str = None,
|
||||
response_description: str = "Successful Response",
|
||||
responses: Dict[Union[int, str], Dict[str, Any]] = None,
|
||||
deprecated: bool = None,
|
||||
operation_id: str = None,
|
||||
include_in_schema: bool = True,
|
||||
@@ -362,6 +413,7 @@ class APIRouter(routing.Router):
|
||||
summary=summary,
|
||||
description=description,
|
||||
response_description=response_description,
|
||||
responses=responses or {},
|
||||
deprecated=deprecated,
|
||||
methods=["POST"],
|
||||
operation_id=operation_id,
|
||||
@@ -380,6 +432,7 @@ class APIRouter(routing.Router):
|
||||
summary: str = None,
|
||||
description: str = None,
|
||||
response_description: str = "Successful Response",
|
||||
responses: Dict[Union[int, str], Dict[str, Any]] = None,
|
||||
deprecated: bool = None,
|
||||
operation_id: str = None,
|
||||
include_in_schema: bool = True,
|
||||
@@ -394,6 +447,7 @@ class APIRouter(routing.Router):
|
||||
summary=summary,
|
||||
description=description,
|
||||
response_description=response_description,
|
||||
responses=responses or {},
|
||||
deprecated=deprecated,
|
||||
methods=["DELETE"],
|
||||
operation_id=operation_id,
|
||||
@@ -412,6 +466,7 @@ class APIRouter(routing.Router):
|
||||
summary: str = None,
|
||||
description: str = None,
|
||||
response_description: str = "Successful Response",
|
||||
responses: Dict[Union[int, str], Dict[str, Any]] = None,
|
||||
deprecated: bool = None,
|
||||
operation_id: str = None,
|
||||
include_in_schema: bool = True,
|
||||
@@ -426,6 +481,7 @@ class APIRouter(routing.Router):
|
||||
summary=summary,
|
||||
description=description,
|
||||
response_description=response_description,
|
||||
responses=responses or {},
|
||||
deprecated=deprecated,
|
||||
methods=["OPTIONS"],
|
||||
operation_id=operation_id,
|
||||
@@ -444,6 +500,7 @@ class APIRouter(routing.Router):
|
||||
summary: str = None,
|
||||
description: str = None,
|
||||
response_description: str = "Successful Response",
|
||||
responses: Dict[Union[int, str], Dict[str, Any]] = None,
|
||||
deprecated: bool = None,
|
||||
operation_id: str = None,
|
||||
include_in_schema: bool = True,
|
||||
@@ -458,6 +515,7 @@ class APIRouter(routing.Router):
|
||||
summary=summary,
|
||||
description=description,
|
||||
response_description=response_description,
|
||||
responses=responses or {},
|
||||
deprecated=deprecated,
|
||||
methods=["HEAD"],
|
||||
operation_id=operation_id,
|
||||
@@ -476,6 +534,7 @@ class APIRouter(routing.Router):
|
||||
summary: str = None,
|
||||
description: str = None,
|
||||
response_description: str = "Successful Response",
|
||||
responses: Dict[Union[int, str], Dict[str, Any]] = None,
|
||||
deprecated: bool = None,
|
||||
operation_id: str = None,
|
||||
include_in_schema: bool = True,
|
||||
@@ -490,6 +549,7 @@ class APIRouter(routing.Router):
|
||||
summary=summary,
|
||||
description=description,
|
||||
response_description=response_description,
|
||||
responses=responses or {},
|
||||
deprecated=deprecated,
|
||||
methods=["PATCH"],
|
||||
operation_id=operation_id,
|
||||
@@ -508,6 +568,7 @@ class APIRouter(routing.Router):
|
||||
summary: str = None,
|
||||
description: str = None,
|
||||
response_description: str = "Successful Response",
|
||||
responses: Dict[Union[int, str], Dict[str, Any]] = None,
|
||||
deprecated: bool = None,
|
||||
operation_id: str = None,
|
||||
include_in_schema: bool = True,
|
||||
@@ -522,6 +583,7 @@ class APIRouter(routing.Router):
|
||||
summary=summary,
|
||||
description=description,
|
||||
response_description=response_description,
|
||||
responses=responses or {},
|
||||
deprecated=deprecated,
|
||||
methods=["TRACE"],
|
||||
operation_id=operation_id,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from typing import Optional
|
||||
|
||||
from fastapi.openapi.models import APIKey, APIKeyIn
|
||||
from fastapi.security.base import SecurityBase
|
||||
from starlette.exceptions import HTTPException
|
||||
@@ -10,42 +12,54 @@ class APIKeyBase(SecurityBase):
|
||||
|
||||
|
||||
class APIKeyQuery(APIKeyBase):
|
||||
def __init__(self, *, name: str, scheme_name: str = None):
|
||||
self.model = APIKey(**{"in": APIKeyIn.query}, name=name)
|
||||
def __init__(self, *, name: str, scheme_name: str = None, auto_error: bool = True):
|
||||
self.model: APIKey = APIKey(**{"in": APIKeyIn.query}, name=name)
|
||||
self.scheme_name = scheme_name or self.__class__.__name__
|
||||
self.auto_error = auto_error
|
||||
|
||||
async def __call__(self, request: Request) -> str:
|
||||
async def __call__(self, request: Request) -> Optional[str]:
|
||||
api_key: str = request.query_params.get(self.model.name)
|
||||
if not api_key:
|
||||
raise HTTPException(
|
||||
status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
|
||||
)
|
||||
if self.auto_error:
|
||||
raise HTTPException(
|
||||
status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
|
||||
)
|
||||
else:
|
||||
return None
|
||||
return api_key
|
||||
|
||||
|
||||
class APIKeyHeader(APIKeyBase):
|
||||
def __init__(self, *, name: str, scheme_name: str = None):
|
||||
self.model = APIKey(**{"in": APIKeyIn.header}, name=name)
|
||||
def __init__(self, *, name: str, scheme_name: str = None, auto_error: bool = True):
|
||||
self.model: APIKey = APIKey(**{"in": APIKeyIn.header}, name=name)
|
||||
self.scheme_name = scheme_name or self.__class__.__name__
|
||||
self.auto_error = auto_error
|
||||
|
||||
async def __call__(self, request: Request) -> str:
|
||||
async def __call__(self, request: Request) -> Optional[str]:
|
||||
api_key: str = request.headers.get(self.model.name)
|
||||
if not api_key:
|
||||
raise HTTPException(
|
||||
status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
|
||||
)
|
||||
if self.auto_error:
|
||||
raise HTTPException(
|
||||
status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
|
||||
)
|
||||
else:
|
||||
return None
|
||||
return api_key
|
||||
|
||||
|
||||
class APIKeyCookie(APIKeyBase):
|
||||
def __init__(self, *, name: str, scheme_name: str = None):
|
||||
self.model = APIKey(**{"in": APIKeyIn.cookie}, name=name)
|
||||
def __init__(self, *, name: str, scheme_name: str = None, auto_error: bool = True):
|
||||
self.model: APIKey = APIKey(**{"in": APIKeyIn.cookie}, name=name)
|
||||
self.scheme_name = scheme_name or self.__class__.__name__
|
||||
self.auto_error = auto_error
|
||||
|
||||
async def __call__(self, request: Request) -> str:
|
||||
async def __call__(self, request: Request) -> Optional[str]:
|
||||
api_key: str = request.cookies.get(self.model.name)
|
||||
if not api_key:
|
||||
raise HTTPException(
|
||||
status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
|
||||
)
|
||||
if self.auto_error:
|
||||
raise HTTPException(
|
||||
status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
|
||||
)
|
||||
else:
|
||||
return None
|
||||
return api_key
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import binascii
|
||||
from base64 import b64decode
|
||||
from typing import Optional
|
||||
|
||||
from fastapi.openapi.models import (
|
||||
HTTPBase as HTTPBaseModel,
|
||||
@@ -24,27 +25,38 @@ class HTTPAuthorizationCredentials(BaseModel):
|
||||
|
||||
|
||||
class HTTPBase(SecurityBase):
|
||||
def __init__(self, *, scheme: str, scheme_name: str = None):
|
||||
def __init__(
|
||||
self, *, scheme: str, scheme_name: str = None, auto_error: bool = True
|
||||
):
|
||||
self.model = HTTPBaseModel(scheme=scheme)
|
||||
self.scheme_name = scheme_name or self.__class__.__name__
|
||||
self.auto_error = auto_error
|
||||
|
||||
async def __call__(self, request: Request) -> str:
|
||||
async def __call__(
|
||||
self, request: Request
|
||||
) -> Optional[HTTPAuthorizationCredentials]:
|
||||
authorization: str = request.headers.get("Authorization")
|
||||
scheme, credentials = get_authorization_scheme_param(authorization)
|
||||
if not (authorization and scheme and credentials):
|
||||
raise HTTPException(
|
||||
status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
|
||||
)
|
||||
if self.auto_error:
|
||||
raise HTTPException(
|
||||
status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
|
||||
)
|
||||
else:
|
||||
return None
|
||||
return HTTPAuthorizationCredentials(scheme=scheme, credentials=credentials)
|
||||
|
||||
|
||||
class HTTPBasic(HTTPBase):
|
||||
def __init__(self, *, scheme_name: str = None, realm: str = None):
|
||||
def __init__(
|
||||
self, *, scheme_name: str = None, realm: str = None, auto_error: bool = True
|
||||
):
|
||||
self.model = HTTPBaseModel(scheme="basic")
|
||||
self.scheme_name = scheme_name or self.__class__.__name__
|
||||
self.realm = realm
|
||||
self.auto_error = auto_error
|
||||
|
||||
async def __call__(self, request: Request) -> str:
|
||||
async def __call__(self, request: Request) -> Optional[HTTPBasicCredentials]:
|
||||
authorization: str = request.headers.get("Authorization")
|
||||
scheme, param = get_authorization_scheme_param(authorization)
|
||||
# before implementing headers with 401 errors, wait for: https://github.com/encode/starlette/issues/295
|
||||
@@ -53,9 +65,12 @@ class HTTPBasic(HTTPBase):
|
||||
status_code=HTTP_403_FORBIDDEN, detail="Invalid authentication credentials"
|
||||
)
|
||||
if not authorization or scheme.lower() != "basic":
|
||||
raise HTTPException(
|
||||
status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
|
||||
)
|
||||
if self.auto_error:
|
||||
raise HTTPException(
|
||||
status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
|
||||
)
|
||||
else:
|
||||
return None
|
||||
try:
|
||||
data = b64decode(param).decode("ascii")
|
||||
except (ValueError, UnicodeDecodeError, binascii.Error):
|
||||
@@ -67,17 +82,29 @@ class HTTPBasic(HTTPBase):
|
||||
|
||||
|
||||
class HTTPBearer(HTTPBase):
|
||||
def __init__(self, *, bearerFormat: str = None, scheme_name: str = None):
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
bearerFormat: str = None,
|
||||
scheme_name: str = None,
|
||||
auto_error: bool = True
|
||||
):
|
||||
self.model = HTTPBearerModel(bearerFormat=bearerFormat)
|
||||
self.scheme_name = scheme_name or self.__class__.__name__
|
||||
self.auto_error = auto_error
|
||||
|
||||
async def __call__(self, request: Request) -> str:
|
||||
async def __call__(
|
||||
self, request: Request
|
||||
) -> Optional[HTTPAuthorizationCredentials]:
|
||||
authorization: str = request.headers.get("Authorization")
|
||||
scheme, credentials = get_authorization_scheme_param(authorization)
|
||||
if not (authorization and scheme and credentials):
|
||||
raise HTTPException(
|
||||
status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
|
||||
)
|
||||
if self.auto_error:
|
||||
raise HTTPException(
|
||||
status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
|
||||
)
|
||||
else:
|
||||
return None
|
||||
if scheme.lower() != "bearer":
|
||||
raise HTTPException(
|
||||
status_code=HTTP_403_FORBIDDEN,
|
||||
@@ -87,17 +114,23 @@ class HTTPBearer(HTTPBase):
|
||||
|
||||
|
||||
class HTTPDigest(HTTPBase):
|
||||
def __init__(self, *, scheme_name: str = None):
|
||||
def __init__(self, *, scheme_name: str = None, auto_error: bool = True):
|
||||
self.model = HTTPBaseModel(scheme="digest")
|
||||
self.scheme_name = scheme_name or self.__class__.__name__
|
||||
self.auto_error = auto_error
|
||||
|
||||
async def __call__(self, request: Request) -> str:
|
||||
async def __call__(
|
||||
self, request: Request
|
||||
) -> Optional[HTTPAuthorizationCredentials]:
|
||||
authorization: str = request.headers.get("Authorization")
|
||||
scheme, credentials = get_authorization_scheme_param(authorization)
|
||||
if not (authorization and scheme and credentials):
|
||||
raise HTTPException(
|
||||
status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
|
||||
)
|
||||
if self.auto_error:
|
||||
raise HTTPException(
|
||||
status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
|
||||
)
|
||||
else:
|
||||
return None
|
||||
if scheme.lower() != "digest":
|
||||
raise HTTPException(
|
||||
status_code=HTTP_403_FORBIDDEN,
|
||||
|
||||
@@ -113,32 +113,49 @@ class OAuth2PasswordRequestFormStrict(OAuth2PasswordRequestForm):
|
||||
|
||||
class OAuth2(SecurityBase):
|
||||
def __init__(
|
||||
self, *, flows: OAuthFlowsModel = OAuthFlowsModel(), scheme_name: str = None
|
||||
self,
|
||||
*,
|
||||
flows: OAuthFlowsModel = OAuthFlowsModel(),
|
||||
scheme_name: str = None,
|
||||
auto_error: bool = True
|
||||
):
|
||||
self.model = OAuth2Model(flows=flows)
|
||||
self.scheme_name = scheme_name or self.__class__.__name__
|
||||
self.auto_error = auto_error
|
||||
|
||||
async def __call__(self, request: Request) -> str:
|
||||
async def __call__(self, request: Request) -> Optional[str]:
|
||||
authorization: str = request.headers.get("Authorization")
|
||||
if not authorization:
|
||||
raise HTTPException(
|
||||
status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
|
||||
)
|
||||
if self.auto_error:
|
||||
raise HTTPException(
|
||||
status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
|
||||
)
|
||||
else:
|
||||
return None
|
||||
return authorization
|
||||
|
||||
|
||||
class OAuth2PasswordBearer(OAuth2):
|
||||
def __init__(self, tokenUrl: str, scheme_name: str = None, scopes: dict = None):
|
||||
def __init__(
|
||||
self,
|
||||
tokenUrl: str,
|
||||
scheme_name: str = None,
|
||||
scopes: dict = None,
|
||||
auto_error: bool = True,
|
||||
):
|
||||
if not scopes:
|
||||
scopes = {}
|
||||
flows = OAuthFlowsModel(password={"tokenUrl": tokenUrl, "scopes": scopes})
|
||||
super().__init__(flows=flows, scheme_name=scheme_name)
|
||||
super().__init__(flows=flows, scheme_name=scheme_name, auto_error=auto_error)
|
||||
|
||||
async def __call__(self, request: Request) -> str:
|
||||
async def __call__(self, request: Request) -> Optional[str]:
|
||||
authorization: str = request.headers.get("Authorization")
|
||||
scheme, param = get_authorization_scheme_param(authorization)
|
||||
if not authorization or scheme.lower() != "bearer":
|
||||
raise HTTPException(
|
||||
status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
|
||||
)
|
||||
if self.auto_error:
|
||||
raise HTTPException(
|
||||
status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
|
||||
)
|
||||
else:
|
||||
return None
|
||||
return param
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from typing import Optional
|
||||
|
||||
from fastapi.openapi.models import OpenIdConnect as OpenIdConnectModel
|
||||
from fastapi.security.base import SecurityBase
|
||||
from starlette.exceptions import HTTPException
|
||||
@@ -6,14 +8,20 @@ from starlette.status import HTTP_403_FORBIDDEN
|
||||
|
||||
|
||||
class OpenIdConnect(SecurityBase):
|
||||
def __init__(self, *, openIdConnectUrl: str, scheme_name: str = None):
|
||||
def __init__(
|
||||
self, *, openIdConnectUrl: str, scheme_name: str = None, auto_error: bool = True
|
||||
):
|
||||
self.model = OpenIdConnectModel(openIdConnectUrl=openIdConnectUrl)
|
||||
self.scheme_name = scheme_name or self.__class__.__name__
|
||||
self.auto_error = auto_error
|
||||
|
||||
async def __call__(self, request: Request) -> str:
|
||||
async def __call__(self, request: Request) -> Optional[str]:
|
||||
authorization: str = request.headers.get("Authorization")
|
||||
if not authorization:
|
||||
raise HTTPException(
|
||||
status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
|
||||
)
|
||||
if self.auto_error:
|
||||
raise HTTPException(
|
||||
status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
|
||||
)
|
||||
else:
|
||||
return None
|
||||
return authorization
|
||||
|
||||
@@ -30,6 +30,8 @@ def get_flat_models_from_routes(
|
||||
body_fields_from_routes.append(route.body_field)
|
||||
if route.response_field:
|
||||
responses_from_routes.append(route.response_field)
|
||||
if route.response_fields:
|
||||
responses_from_routes.extend(route.response_fields.values())
|
||||
flat_models = get_flat_models_from_fields(
|
||||
body_fields_from_routes + responses_from_routes
|
||||
)
|
||||
|
||||
@@ -44,6 +44,7 @@ nav:
|
||||
- Path Operation Configuration: 'tutorial/path-operation-configuration.md'
|
||||
- Path Operation Advanced Configuration: 'tutorial/path-operation-advanced-configuration.md'
|
||||
- Custom Response: 'tutorial/custom-response.md'
|
||||
- Additional Responses: 'tutorial/additional-responses.md'
|
||||
- Dependencies:
|
||||
- First Steps: 'tutorial/dependencies/first-steps.md'
|
||||
- Classes as Dependencies: 'tutorial/dependencies/classes-as-dependencies.md'
|
||||
@@ -57,15 +58,22 @@ nav:
|
||||
- OAuth2 with Password (and hashing), Bearer with JWT tokens: 'tutorial/security/oauth2-jwt.md'
|
||||
- Using the Request Directly: 'tutorial/using-request-directly.md'
|
||||
- SQL (Relational) Databases: 'tutorial/sql-databases.md'
|
||||
- Async SQL (Relational) Databases: 'tutorial/async-sql-databases.md'
|
||||
- NoSQL (Distributed / Big Data) Databases: 'tutorial/nosql-databases.md'
|
||||
- Bigger Applications - Multiple Files: 'tutorial/bigger-applications.md'
|
||||
- Background Tasks: 'tutorial/background-tasks.md'
|
||||
- Sub Applications - Behind a Proxy: 'tutorial/sub-applications-proxy.md'
|
||||
- Application Configuration: 'tutorial/application-configuration.md'
|
||||
- GraphQL: 'tutorial/graphql.md'
|
||||
- WebSockets: 'tutorial/websockets.md'
|
||||
- 'Events: startup - shutdown': 'tutorial/events.md'
|
||||
- Debugging: 'tutorial/debugging.md'
|
||||
- Extending OpenAPI: 'tutorial/extending-openapi.md'
|
||||
- Concurrency and async / await: 'async.md'
|
||||
- Deployment: 'deployment.md'
|
||||
- Project Generation - Template: 'project-generation.md'
|
||||
- Alternatives, Inspiration and Comparisons: 'alternatives.md'
|
||||
- History, Design and Future: 'history-design-future.md'
|
||||
- Benchmarks: 'benchmarks.md'
|
||||
- Help FastAPI - Get Help: 'help-fastapi.md'
|
||||
- Development - Contributing: 'contributing.md'
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ classifiers = [
|
||||
]
|
||||
requires = [
|
||||
"starlette ==0.11.1",
|
||||
"pydantic >=0.17,<=0.18.2"
|
||||
"pydantic >=0.17,<=0.21.0"
|
||||
]
|
||||
description-file = "README.md"
|
||||
requires-python = ">=3.6"
|
||||
@@ -37,7 +37,8 @@ test = [
|
||||
"isort",
|
||||
"requests",
|
||||
"email_validator",
|
||||
"sqlalchemy"
|
||||
"sqlalchemy",
|
||||
"databases[sqlite]",
|
||||
]
|
||||
doc = [
|
||||
"mkdocs",
|
||||
@@ -57,7 +58,6 @@ all = [
|
||||
"pyyaml",
|
||||
"graphene",
|
||||
"ujson",
|
||||
"ujson",
|
||||
"email_validator",
|
||||
"uvicorn",
|
||||
]
|
||||
|
||||