mirror of
https://github.com/fastapi/fastapi.git
synced 2025-12-26 15:51:02 -05:00
Compare commits
48 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8e3a7699a3 | ||
|
|
8998ccaffb | ||
|
|
2b7f201a44 | ||
|
|
192ebba2a2 | ||
|
|
8880c4cb03 | ||
|
|
6324be684f | ||
|
|
c705685394 | ||
|
|
945f401d8e | ||
|
|
f216d340ec | ||
|
|
a4558e7053 | ||
|
|
298f8478e2 | ||
|
|
b86d163470 | ||
|
|
9e2d37b89c | ||
|
|
97adadd9e1 | ||
|
|
26e3dffb37 | ||
|
|
aa7b4bd101 | ||
|
|
ffc4c716c0 | ||
|
|
ef7b6e8eaf | ||
|
|
596243f4a5 | ||
|
|
766bf1c5aa | ||
|
|
9e748dbca4 | ||
|
|
cefe6cf92c | ||
|
|
be3953499f | ||
|
|
546d233dec | ||
|
|
61dd36a945 | ||
|
|
27f9d55c3e | ||
|
|
906cc60f65 | ||
|
|
69afaf256f | ||
|
|
4ab349a2a8 | ||
|
|
9c258107b4 | ||
|
|
29a4f90bcd | ||
|
|
361fd00777 | ||
|
|
4c3cf31730 | ||
|
|
aad6b123f7 | ||
|
|
e40e87c662 | ||
|
|
84de980977 | ||
|
|
08484603ee | ||
|
|
ab6dd60997 | ||
|
|
c9ef7bd6dc | ||
|
|
88ece95a30 | ||
|
|
366c5db0bb | ||
|
|
46e3811f8d | ||
|
|
613e211d20 | ||
|
|
500f2b2ad4 | ||
|
|
5cf7718657 | ||
|
|
727b656c8d | ||
|
|
cc7102e9b8 | ||
|
|
5123915fe4 |
@@ -10,7 +10,7 @@ python:
|
||||
|
||||
install:
|
||||
- pip install flit
|
||||
- flit install
|
||||
- flit install --symlink
|
||||
|
||||
script:
|
||||
- bash scripts/test.sh
|
||||
@@ -20,6 +20,7 @@ after_script:
|
||||
|
||||
deploy:
|
||||
provider: script
|
||||
script: bash scripts/trigger-docker.sh
|
||||
script: bash scripts/deploy.sh
|
||||
on:
|
||||
branch: master
|
||||
tags: true
|
||||
python: "3.6"
|
||||
|
||||
2
Pipfile
2
Pipfile
@@ -26,7 +26,7 @@ uvicorn = "*"
|
||||
|
||||
[packages]
|
||||
starlette = "==0.11.1"
|
||||
pydantic = "==0.21.0"
|
||||
pydantic = "==0.23.0"
|
||||
databases = {extras = ["sqlite"],version = "*"}
|
||||
|
||||
[requires]
|
||||
|
||||
198
Pipfile.lock
generated
198
Pipfile.lock
generated
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "24b3b7b88d3cbe671ddbe296e64c15f8558f0e5d5df977200119872a363aac13"
|
||||
"sha256": "02367d250c6327eac80dfcd8e5ccfa49bcdca0332bc757d527c3db27643baa0d"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
@@ -18,34 +18,33 @@
|
||||
"default": {
|
||||
"aiocontextvars": {
|
||||
"hashes": [
|
||||
"sha256:1e0ff5837c8b01c36a1107acdd0baf7853ebdf6c9fc43e8e311f4be37ac2038a",
|
||||
"sha256:6ff7aee14f549d52f0446cbb84d0deddcd3fc677bcf8fbc2ce13f5756d2064dc"
|
||||
"sha256:885daf8261818767d8f7cbd79f9d4482d118f024b6586ef6e67980236a27bfa3",
|
||||
"sha256:f027372dc48641f683c559f247bd84962becaacdc9ba711d583c3871fb5652aa"
|
||||
],
|
||||
"markers": "python_version < '3.7'",
|
||||
"version": "==0.2.1"
|
||||
"version": "==0.2.2"
|
||||
},
|
||||
"aiosqlite": {
|
||||
"hashes": [
|
||||
"sha256:af4fed9e778756fa0ffffc7a8b14c4d7b1a57155dc5669f18e45107313f6019e"
|
||||
"sha256:ad84fbd7516ca7065d799504fc41d6845c938e5306d1b7dd960caaeda12e22a9"
|
||||
],
|
||||
"version": "==0.9.0"
|
||||
"version": "==0.10.0"
|
||||
},
|
||||
"contextvars": {
|
||||
"hashes": [
|
||||
"sha256:2341042e1c03a271813e07dba29b6b60fa85c1005ea5ed1638a076cf50b4d625"
|
||||
"sha256:f38c908aaa59c14335eeea12abea5f443646216c4e29380d7bf34d2018e2c39e"
|
||||
],
|
||||
"markers": "python_version < '3.7'",
|
||||
"version": "==2.3"
|
||||
"version": "==2.4"
|
||||
},
|
||||
"databases": {
|
||||
"extras": [
|
||||
"sqlite"
|
||||
],
|
||||
"hashes": [
|
||||
"sha256:4a0f15669c390a04b439972426350c0ae921ddc08c42bd54f125eb2fb86ee728"
|
||||
"sha256:d365cff2035c5177ef5fd8c5abf6671da01189521da64848a01251c870daf48f"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.2.0"
|
||||
"version": "==0.2.2"
|
||||
},
|
||||
"dataclasses": {
|
||||
"hashes": [
|
||||
@@ -57,33 +56,38 @@
|
||||
},
|
||||
"immutables": {
|
||||
"hashes": [
|
||||
"sha256:1e4f4513254ef11e0230a558ee0dcb4551b914993c330005d15338da595d3750",
|
||||
"sha256:228e38dc7a810ba4ff88909908ac47f840e5dc6c4c0da6b25009c626a9ae771c",
|
||||
"sha256:2ae88fbfe1d04f4e5859c924e97313edf70e72b4f19871bf329b96a67ede9ba0",
|
||||
"sha256:2d32b61c222cba1dd11f0faff67c7fb6204ef1982454e1b5b001d4b79966ef17",
|
||||
"sha256:35af186bfac5b62522fdf2cab11120d7b0547f405aa399b6a1e443cf5f5e318c",
|
||||
"sha256:63023fa0cceedc62e0d1535cd4ca7a1f6df3120a6d8e5c34e89037402a6fd809",
|
||||
"sha256:6bf5857f42a96331fd0929c357dc0b36a72f339f3b6acaf870b149c96b141f69",
|
||||
"sha256:7bb1590024a032c7a57f79faf8c8ff5e91340662550d2980e0177f67e66e9c9c",
|
||||
"sha256:7c090687d7e623d4eca22962635b5e1a1ee2d6f9a9aca2f3fb5a184a1ffef1f2",
|
||||
"sha256:bc36a0a8749881eebd753f696b081bd51145e4d77291d671d2e2f622e5b65d2f",
|
||||
"sha256:d9fc6a236018d99af6453ead945a6bb55f98d14b1801a2c229dd993edc753a00"
|
||||
"sha256:10861f2a2b86139f0c91d5073392d76117f37e84f912dc47c943c23a64008cc7",
|
||||
"sha256:3e23eeb4bc55d57b2a97bef4c1a2891bbb731050b4167c855545797d45e84e45",
|
||||
"sha256:4373876879f147986808f71e6ca02380192a279e8b8d45832f6fed4e7f717562",
|
||||
"sha256:46f9122da033fecf84d7f4c6257aec780f370b20f3ce6bc521702b63ee3d99f7",
|
||||
"sha256:5104db6102e53702af45c6b0af36e45a80970123b11a80c14e0fce48444cdbe3",
|
||||
"sha256:59274bcb631f4fdc9731e9a4a96d16d96b3a17e29fd5e46516518f38406f678f",
|
||||
"sha256:65a9c624e50ca5c50464dbf432996b5c4f056a411bcff5690ef4cab59f913f99",
|
||||
"sha256:b64e0672497b884d21170ca61c693da8488d77f043650efa7911378cbbad0f2c",
|
||||
"sha256:b70655dba00742b033310933066a2202e1cfbbb0f63841b4597cd8787974b242",
|
||||
"sha256:c3d8c238a6f9b60355578579563773348674b6da63c1a0d7394384ed341f3d41",
|
||||
"sha256:cd66bcd11b6a1c1a80fb8d90e25870ff2d5c705ab5eb9666355a33d3fef6ac70",
|
||||
"sha256:d59310fc4f97c1ff8c3660cb98032db266ac0c285a86ca7a512e8e84a95f44c9",
|
||||
"sha256:d71d1c822498646143270580dd6f743bb31ab89ae0ded8b2307c356d3a00f1c0",
|
||||
"sha256:f53da698b42db83cfb1f5073560838051430798c8d8e34a57a27031edbc3041d",
|
||||
"sha256:f958ba15745e30d3a38e3c9fcead8496037135bb21c78c0f925c104abba3a6fa",
|
||||
"sha256:ff95e2aa618eed1a0ef4479938f18f3522c89562b9bbb59d677597c0337569dd"
|
||||
],
|
||||
"version": "==0.6"
|
||||
"version": "==0.9"
|
||||
},
|
||||
"pydantic": {
|
||||
"hashes": [
|
||||
"sha256:93fa585402e7c8c01623ea8af6ca23363e8b4c6a020b7a2de9e99fa29d642d50",
|
||||
"sha256:eb441dd50779347a450494c437db3ecbb13c1f3854497df879662782af516c5c"
|
||||
"sha256:1205cd1213e8acee40a9ad7160b24de74484fd79ec3f09150b255896a3f506ab",
|
||||
"sha256:58b71804e9a6b4e1ccf8b3dbbca8c0f9cf4b494e5bea219a96e2e2ecb5af688e"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.21.0"
|
||||
"version": "==0.23.0"
|
||||
},
|
||||
"sqlalchemy": {
|
||||
"hashes": [
|
||||
"sha256:781fb7b9d194ed3fc596b8f0dd4623ff160e3e825dd8c15472376a438c19598b"
|
||||
"sha256:91c54ca8345008fceaec987e10924bf07dcab36c442925357e5a467b36a38319"
|
||||
],
|
||||
"version": "==1.3.1"
|
||||
"version": "==1.3.3"
|
||||
},
|
||||
"starlette": {
|
||||
"hashes": [
|
||||
@@ -216,10 +220,10 @@
|
||||
},
|
||||
"defusedxml": {
|
||||
"hashes": [
|
||||
"sha256:24d7f2f94f7f3cb6061acb215685e5125fbcdc40a857eff9de22518820b0a4f4",
|
||||
"sha256:702a91ade2968a82beb0db1e0766a6a273f33d4616a6ce8cde475d8e09853b20"
|
||||
"sha256:6687150770438374ab581bb7a1b327a847dd9c5749e396102de3fad4e8a3ef93",
|
||||
"sha256:f684034d135af4c6cbb949b8a4d2ed61634515257a67299e5f940fbaa34377f5"
|
||||
],
|
||||
"version": "==0.5.0"
|
||||
"version": "==0.6.0"
|
||||
},
|
||||
"dnspython": {
|
||||
"hashes": [
|
||||
@@ -277,6 +281,7 @@
|
||||
"hashes": [
|
||||
"sha256:e00cbd7ba01ff748e494248183abc6e153f49181169d8a3d41bb49132ca01dfc"
|
||||
],
|
||||
"markers": "sys_platform != 'win32' and sys_platform != 'cygwin' and platform_python_implementation != 'pypy'",
|
||||
"version": "==0.0.13"
|
||||
},
|
||||
"idna": {
|
||||
@@ -317,11 +322,11 @@
|
||||
},
|
||||
"isort": {
|
||||
"hashes": [
|
||||
"sha256:08f8e3f0f0b7249e9fad7e5c41e2113aba44969798a26452ee790c06f155d4ec",
|
||||
"sha256:4e9e9c4bd1acd66cf6c36973f29b031ec752cbfd991c69695e4e259f9a756927"
|
||||
"sha256:01cb7e1ca5e6c5b3f235f0385057f70558b70d2f00320208825fa62887292f43",
|
||||
"sha256:268067462aed7eb2a1e237fcb287852f22077de3fb07964e87e00f829eea2d1a"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.3.16"
|
||||
"version": "==4.3.17"
|
||||
},
|
||||
"jedi": {
|
||||
"hashes": [
|
||||
@@ -332,10 +337,10 @@
|
||||
},
|
||||
"jinja2": {
|
||||
"hashes": [
|
||||
"sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd",
|
||||
"sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4"
|
||||
"sha256:065c4f02ebe7f7cf559e49ee5a95fb800a9e4528727aec6f24402a5374c65013",
|
||||
"sha256:14dd6caf1527abb21f08f86c784eac40853ba93edb79552aa1e4b8aef1b61c7b"
|
||||
],
|
||||
"version": "==2.10"
|
||||
"version": "==2.10.1"
|
||||
},
|
||||
"jsonschema": {
|
||||
"hashes": [
|
||||
@@ -383,10 +388,10 @@
|
||||
},
|
||||
"markdown": {
|
||||
"hashes": [
|
||||
"sha256:c00429bd503a47ec88d5e30a751e147dcb4c6889663cd3e2ba0afe858e009baa",
|
||||
"sha256:d02e0f9b04c500cde6637c11ad7c72671f359b87b9fe924b2383649d8841db7c"
|
||||
"sha256:fc4a6f69a656b8d858d7503bda633f4dd63c2d70cf80abdc6eafa64c4ae8c250",
|
||||
"sha256:fe463ff51e679377e3624984c829022e2cfb3be5518726b06f608a07a3aad680"
|
||||
],
|
||||
"version": "==3.0.1"
|
||||
"version": "==3.1"
|
||||
},
|
||||
"markdown-include": {
|
||||
"hashes": [
|
||||
@@ -452,27 +457,36 @@
|
||||
},
|
||||
"mkdocs-material": {
|
||||
"hashes": [
|
||||
"sha256:0b394aa034b25a09a5874ae2a6ccc426fd81f5764e0991217b169e31cb0c1c0e",
|
||||
"sha256:f5bb80a2c16d045d380edb2c5b05636af1bb709cb859bfaa9d01063a11df803f"
|
||||
"sha256:8a572f4b3358b9c0e11af8ae319ba4f3747ebb61e2393734d875133b0d2f7891",
|
||||
"sha256:91210776db541283dd4b7beb5339c190aa69de78ad661aa116a8aa97dd73c803"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.1.0"
|
||||
"version": "==4.1.2"
|
||||
},
|
||||
"more-itertools": {
|
||||
"hashes": [
|
||||
"sha256:0125e8f60e9e031347105eb1682cef932f5e97d7b9a1a28d9bf00c22a5daef40",
|
||||
"sha256:590044e3942351a1bdb1de960b739ff4ce277960f2425ad4509446dbace8d9d1"
|
||||
"sha256:2112d2ca570bb7c3e53ea1a35cd5df42bb0fd10c45f0fb97178679c3c03d64c7",
|
||||
"sha256:c3e4748ba1aad8dba30a4886b0b1a2004f9a863837b8654e7059eebf727afa5a"
|
||||
],
|
||||
"markers": "python_version > '2.7'",
|
||||
"version": "==6.0.0"
|
||||
"version": "==7.0.0"
|
||||
},
|
||||
"mypy": {
|
||||
"hashes": [
|
||||
"sha256:308c274eb8482fbf16006f549137ddc0d69e5a589465e37b99c4564414363ca7",
|
||||
"sha256:e80fd6af34614a0e898a57f14296d0dacb584648f0339c2e000ddbf0f4cc2f8d"
|
||||
"sha256:2afe51527b1f6cdc4a5f34fc90473109b22bf7f21086ba3e9451857cf11489e6",
|
||||
"sha256:56a16df3e0abb145d8accd5dbb70eba6c4bd26e2f89042b491faa78c9635d1e2",
|
||||
"sha256:5764f10d27b2e93c84f70af5778941b8f4aa1379b2430f85c827e0f5464e8714",
|
||||
"sha256:5bbc86374f04a3aa817622f98e40375ccb28c4836f36b66706cf3c6ccce86eda",
|
||||
"sha256:6a9343089f6377e71e20ca734cd8e7ac25d36478a9df580efabfe9059819bf82",
|
||||
"sha256:6c9851bc4a23dc1d854d3f5dfd5f20a016f8da86bcdbb42687879bb5f86434b0",
|
||||
"sha256:b8e85956af3fcf043d6f87c91cbe8705073fc67029ba6e22d3468bfee42c4823",
|
||||
"sha256:b9a0af8fae490306bc112229000aa0c2ccc837b49d29a5c42e088c132a2334dd",
|
||||
"sha256:bbf643528e2a55df2c1587008d6e3bda5c0445f1240dfa85129af22ae16d7a9a",
|
||||
"sha256:c46ab3438bd21511db0f2c612d89d8344154c0c9494afc7fbc932de514cf8d15",
|
||||
"sha256:f7a83d6bd805855ef83ec605eb01ab4fa42bcef254b13631e451cbb44914a9b0"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.670"
|
||||
"version": "==0.701"
|
||||
},
|
||||
"mypy-extensions": {
|
||||
"hashes": [
|
||||
@@ -497,10 +511,10 @@
|
||||
},
|
||||
"notebook": {
|
||||
"hashes": [
|
||||
"sha256:18a98858c0331fb65a60f2ebb6439f8c0c4defd14ca363731b6cabc7f61624b4",
|
||||
"sha256:cc027a62be0f7756e0ef3d2d98458c4d7f4b3566449fb1a05891207f5bd9a1bf"
|
||||
"sha256:573e0ae650c5d76b18b6e564ba6d21bf321d00847de1d215b418acb64f056eb8",
|
||||
"sha256:f64fa6624d2323fbef6210a621817d6505a45d0d4a9367f1843b20a38a4666ee"
|
||||
],
|
||||
"version": "==5.7.6"
|
||||
"version": "==5.7.8"
|
||||
},
|
||||
"pandocfilters": {
|
||||
"hashes": [
|
||||
@@ -510,18 +524,18 @@
|
||||
},
|
||||
"parso": {
|
||||
"hashes": [
|
||||
"sha256:4580328ae3f548b358f4901e38c0578229186835f0fa0846e47369796dd5bcc9",
|
||||
"sha256:68406ebd7eafe17f8e40e15a84b56848eccbf27d7c1feb89e93d8fca395706db"
|
||||
"sha256:17cc2d7a945eb42c3569d4564cdf49bde221bc2b552af3eca9c1aad517dcdd33",
|
||||
"sha256:2e9574cb12e7112a87253e14e2c380ce312060269d04bd018478a3c92ea9a376"
|
||||
],
|
||||
"version": "==0.3.4"
|
||||
"version": "==0.4.0"
|
||||
},
|
||||
"pexpect": {
|
||||
"hashes": [
|
||||
"sha256:2a8e88259839571d1251d278476f3eec5db26deb73a70be5ed5dc5435e418aba",
|
||||
"sha256:3fbd41d4caf27fa4a377bfd16fef87271099463e6fa73e92a52f92dfee5d425b"
|
||||
"sha256:2094eefdfcf37a1fdbfb9aa090862c1a4878e5c7e0e7e7088bdb511c558e5cd1",
|
||||
"sha256:9e2c1fd0e6ee3a49b28f95d4b33bc389c89b20af6a1255906e90ff1262ce62eb"
|
||||
],
|
||||
"markers": "sys_platform != 'win32'",
|
||||
"version": "==4.6.0"
|
||||
"version": "==4.7.0"
|
||||
},
|
||||
"pickleshare": {
|
||||
"hashes": [
|
||||
@@ -602,11 +616,11 @@
|
||||
},
|
||||
"pytest": {
|
||||
"hashes": [
|
||||
"sha256:592eaa2c33fae68c7d75aacf042efc9f77b27c08a6224a4f59beab8d9a420523",
|
||||
"sha256:ad3ad5c450284819ecde191a654c09b0ec72257a2c711b9633d677c71c9850c4"
|
||||
"sha256:3773f4c235918987d51daf1db66d51c99fac654c81d6f2f709a046ab446d5e5d",
|
||||
"sha256:b7802283b70ca24d7119b32915efa7c409982f59913c1a6c0640aacf118b95f5"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.3.1"
|
||||
"version": "==4.4.1"
|
||||
},
|
||||
"pytest-cov": {
|
||||
"hashes": [
|
||||
@@ -713,16 +727,16 @@
|
||||
},
|
||||
"sqlalchemy": {
|
||||
"hashes": [
|
||||
"sha256:781fb7b9d194ed3fc596b8f0dd4623ff160e3e825dd8c15472376a438c19598b"
|
||||
"sha256:91c54ca8345008fceaec987e10924bf07dcab36c442925357e5a467b36a38319"
|
||||
],
|
||||
"version": "==1.3.1"
|
||||
"version": "==1.3.3"
|
||||
},
|
||||
"terminado": {
|
||||
"hashes": [
|
||||
"sha256:55abf9ade563b8f9be1f34e4233c7b7bde726059947a593322e8a553cc4c067a",
|
||||
"sha256:65011551baff97f5414c67018e908110693143cfbaeb16831b743fe7cad8b927"
|
||||
"sha256:d9d012de63acb8223ac969c17c3043337c2fcfd28f3aea1ee429b345d01ef460",
|
||||
"sha256:de08e141f83c3a0798b050ecb097ab6259c3f0331b2f7b7750c9075ced2c20c2"
|
||||
],
|
||||
"version": "==0.8.1"
|
||||
"version": "==0.8.2"
|
||||
},
|
||||
"testpath": {
|
||||
"hashes": [
|
||||
@@ -759,27 +773,28 @@
|
||||
},
|
||||
"typed-ast": {
|
||||
"hashes": [
|
||||
"sha256:035a54ede6ce1380599b2ce57844c6554666522e376bd111eb940fbc7c3dad23",
|
||||
"sha256:037c35f2741ce3a9ac0d55abfcd119133cbd821fffa4461397718287092d9d15",
|
||||
"sha256:049feae7e9f180b64efacbdc36b3af64a00393a47be22fa9cb6794e68d4e73d3",
|
||||
"sha256:19228f7940beafc1ba21a6e8e070e0b0bfd1457902a3a81709762b8b9039b88d",
|
||||
"sha256:2ea681e91e3550a30c2265d2916f40a5f5d89b59469a20f3bad7d07adee0f7a6",
|
||||
"sha256:3a6b0a78af298d82323660df5497bcea0f0a4a25a0b003afd0ce5af049bd1f60",
|
||||
"sha256:5385da8f3b801014504df0852bf83524599df890387a3c2b17b7caa3d78b1773",
|
||||
"sha256:606d8afa07eef77280c2bf84335e24390055b478392e1975f96286d99d0cb424",
|
||||
"sha256:69245b5b23bbf7fb242c9f8f08493e9ecd7711f063259aefffaeb90595d62287",
|
||||
"sha256:6f6d839ab09830d59b7fa8fb6917023d8cb5498ee1f1dbd82d37db78eb76bc99",
|
||||
"sha256:730888475f5ac0e37c1de4bd05eeb799fdb742697867f524dc8a4cd74bcecc23",
|
||||
"sha256:9819b5162ffc121b9e334923c685b0d0826154e41dfe70b2ede2ce29034c71d8",
|
||||
"sha256:9e60ef9426efab601dd9aa120e4ff560f4461cf8442e9c0a2b92548d52800699",
|
||||
"sha256:af5fbdde0690c7da68e841d7fc2632345d570768ea7406a9434446d7b33b0ee1",
|
||||
"sha256:b64efdbdf3bbb1377562c179f167f3bf301251411eb5ac77dec6b7d32bcda463",
|
||||
"sha256:bac5f444c118aeb456fac1b0b5d14c6a71ea2a42069b09c176f75e9bd4c186f6",
|
||||
"sha256:bda9068aafb73859491e13b99b682bd299c1b5fd50644d697533775828a28ee0",
|
||||
"sha256:d659517ca116e6750101a1326107d3479028c5191f0ecee3c7203c50f5b915b0",
|
||||
"sha256:eddd3fb1f3e0f82e5915a899285a39ee34ce18fd25d89582bc89fc9fb16cd2c6"
|
||||
"sha256:04894d268ba6eab7e093d43107869ad49e7b5ef40d1a94243ea49b352061b200",
|
||||
"sha256:16616ece19daddc586e499a3d2f560302c11f122b9c692bc216e821ae32aa0d0",
|
||||
"sha256:252fdae740964b2d3cdfb3f84dcb4d6247a48a6abe2579e8029ab3be3cdc026c",
|
||||
"sha256:2af80a373af123d0b9f44941a46df67ef0ff7a60f95872412a145f4500a7fc99",
|
||||
"sha256:2c88d0a913229a06282b285f42a31e063c3bf9071ff65c5ea4c12acb6977c6a7",
|
||||
"sha256:2ea99c029ebd4b5a308d915cc7fb95b8e1201d60b065450d5d26deb65d3f2bc1",
|
||||
"sha256:3d2e3ab175fc097d2a51c7a0d3fda442f35ebcc93bb1d7bd9b95ad893e44c04d",
|
||||
"sha256:4766dd695548a15ee766927bf883fb90c6ac8321be5a60c141f18628fb7f8da8",
|
||||
"sha256:56b6978798502ef66625a2e0f80cf923da64e328da8bbe16c1ff928c70c873de",
|
||||
"sha256:5cddb6f8bce14325b2863f9d5ac5c51e07b71b462361fd815d1d7706d3a9d682",
|
||||
"sha256:644ee788222d81555af543b70a1098f2025db38eaa99226f3a75a6854924d4db",
|
||||
"sha256:64cf762049fc4775efe6b27161467e76d0ba145862802a65eefc8879086fc6f8",
|
||||
"sha256:68c362848d9fb71d3c3e5f43c09974a0ae319144634e7a47db62f0f2a54a7fa7",
|
||||
"sha256:6c1f3c6f6635e611d58e467bf4371883568f0de9ccc4606f17048142dec14a1f",
|
||||
"sha256:b213d4a02eec4ddf622f4d2fbc539f062af3788d1f332f028a2e19c42da53f15",
|
||||
"sha256:bb27d4e7805a7de0e35bd0cb1411bc85f807968b2b0539597a49a23b00a622ae",
|
||||
"sha256:c9d414512eaa417aadae7758bc118868cd2396b0e6138c1dd4fda96679c079d3",
|
||||
"sha256:f0937165d1e25477b01081c4763d2d9cdc3b18af69cb259dd4f640c9b900fe5e",
|
||||
"sha256:fb96a6e2c11059ecf84e6741a319f93f683e440e341d4489c9b161eca251cf2a",
|
||||
"sha256:fc71d2d6ae56a091a8d94f33ec9d0f2001d1cb1db423d8b4355debfe9ce689b7"
|
||||
],
|
||||
"version": "==1.3.1"
|
||||
"version": "==1.3.4"
|
||||
},
|
||||
"ujson": {
|
||||
"hashes": [
|
||||
@@ -790,17 +805,17 @@
|
||||
},
|
||||
"urllib3": {
|
||||
"hashes": [
|
||||
"sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39",
|
||||
"sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22"
|
||||
"sha256:4c291ca23bbb55c76518905869ef34bdd5f0e46af7afe6861e8375643ffee1a0",
|
||||
"sha256:9a247273df709c4fedb38c711e44292304f73f39ab01beda9f6b9fc375669ac3"
|
||||
],
|
||||
"version": "==1.24.1"
|
||||
"version": "==1.24.2"
|
||||
},
|
||||
"uvicorn": {
|
||||
"hashes": [
|
||||
"sha256:d700b65169820fc260f39402b7f966c178691daaa40cb376cad99d7cd737f772"
|
||||
"sha256:181d47abddedd0f6e23eaeed97976bdce9ea1dbff0ec12385309cf4835783f6a"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.7.0b1"
|
||||
"version": "==0.7.0"
|
||||
},
|
||||
"uvloop": {
|
||||
"hashes": [
|
||||
@@ -815,6 +830,7 @@
|
||||
"sha256:c48692bf4587ce281d641087658eca275a5ad3b63c78297bbded96570ae9ce8f",
|
||||
"sha256:fefc3b2b947c99737c348887db2c32e539160dcbeb7af9aa6b53db7a283538fe"
|
||||
],
|
||||
"markers": "sys_platform != 'win32' and sys_platform != 'cygwin' and platform_python_implementation != 'pypy'",
|
||||
"version": "==0.12.2"
|
||||
},
|
||||
"wcwidth": {
|
||||
|
||||
24
README.md
24
README.md
@@ -43,6 +43,28 @@ The key features are:
|
||||
|
||||
<small>* estimation based on tests on an internal development team, building production applications.</small>
|
||||
|
||||
## Opinions
|
||||
|
||||
"*[...] I'm using **FastAPI** a ton these days. [...] I'm actually planning to use it for all of my team's **ML services at Microsoft**. Some of them are getting integrated into the core **Windows** product and some **Office** products.*"
|
||||
|
||||
<div style="text-align: right; margin-right: 10%;">Kabir Khan - <strong>Microsoft</strong> <a href="https://github.com/tiangolo/fastapi/pull/26" target="_blank"><small>(ref)</small></a></div>
|
||||
|
||||
---
|
||||
|
||||
"*I’m over the moon excited about **FastAPI**. It’s so fun!*"
|
||||
|
||||
<div style="text-align: right; margin-right: 10%;">Brian Okken - <strong><a href="https://pythonbytes.fm/episodes/show/123/time-to-right-the-py-wrongs?time_in_sec=855" target="_blank">Python Bytes</a> podcast host</strong> <a href="https://twitter.com/brianokken/status/1112220079972728832" target="_blank"><small>(ref)</small></a></div>
|
||||
|
||||
---
|
||||
|
||||
"*Honestly, what you've built looks super solid and polished. In many ways, it's what I wanted **Hug** to be - it's really inspiring to see someone build that.*"
|
||||
|
||||
<div style="text-align: right; margin-right: 10%;">Timothy Crosley - <strong><a href="http://www.hug.rest/" target="_blank">Hug</a> creator</strong> <a href="https://news.ycombinator.com/item?id=19455465" target="_blank"><small>(ref)</small></a></div>
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
## Requirements
|
||||
|
||||
@@ -198,7 +220,7 @@ def read_item(item_id: int, q: str = None):
|
||||
|
||||
|
||||
@app.put("/items/{item_id}")
|
||||
def create_item(item_id: int, item: Item):
|
||||
def update_item(item_id: int, item: Item):
|
||||
return {"item_name": item.name, "item_id": item_id}
|
||||
```
|
||||
|
||||
|
||||
@@ -236,6 +236,19 @@ It was one of the first extremely fast Python frameworks based on `asyncio`. It
|
||||
|
||||
That's why **FastAPI** is based on Starlette, as it is the fastest framework available (tested by third-party benchmarks).
|
||||
|
||||
### <a href="https://falconframework.org/" target="_blank">Falcon</a>
|
||||
|
||||
Falcon is another high performance Python framework, it is designed to be minimal, and work as the foundation of other frameworks like Hug.
|
||||
|
||||
It uses the previous standard for Python web frameworks (WSGI) which is synchronous, so it can't handle Websockets and other use cases. Nevertheless, it also has a very good performance.
|
||||
|
||||
It is designed to have functions that receive two parameters, one "request" and one "response". Then you "read" parts from the request, and "write" parts to the response. Because of this design, it is not possible to declare request parameters and bodies with standard Python type hints as function parameters.
|
||||
|
||||
So, data validation, serialization, and documentation, have to be done in code, not automatically. Or they have to be implemented as a framework on top of Falcon, like Hug. This same distinction happens in other frameworks that are inspired by Falcon's design, of having one request object and one response object as parameters.
|
||||
|
||||
!!! check "Inspired **FastAPI** to"
|
||||
Find ways to get great performance.
|
||||
|
||||
### <a href="https://moltenframework.com/" target="_blank">Molten</a>
|
||||
|
||||
I discovered Molten in the first stages of building **FastAPI**. And it has quite similar ideas:
|
||||
@@ -257,11 +270,34 @@ Routes are declared in a single place, using functions declared in other places
|
||||
|
||||
This actually inspired updating parts of Pydantic, to support the same validation declaration style (all this functionality is now already available in Pydantic).
|
||||
|
||||
### <a href="http://www.hug.rest/" target="_blank">Hug</a>
|
||||
|
||||
Hug was one of the first frameworks to implement the declaration of API parameter types using Python type hints. This was a great idea that inspired other tools to do the same.
|
||||
|
||||
It used custom types in its declarations instead of standard Python types, but it was still a huge step forward.
|
||||
|
||||
It also was one of the first frameworks to generate a custom schema declaring the whole API in JSON.
|
||||
|
||||
It was not based on a standard like OpenAPI and JSON Schema. So it wouldn't be straightforward to integrate it with other tools, like Swagger UI. But again, it was a very innovative idea.
|
||||
|
||||
It has an interesting, uncommon feature: using the same framework, it's possible to create APIs and also CLIs.
|
||||
|
||||
As it is based on the previous standard for synchronous Python web frameworks (WSGI), it can't handle Websockets and other things, although it still has high performance too.
|
||||
|
||||
!!! info
|
||||
Hug was created by Timothy Crosley, the same creator of <a href="https://github.com/timothycrosley/isort" target="_blank">`isort`</a>, a great tool to automatically sort imports in Python files.
|
||||
|
||||
!!! check "Ideas inspired in **FastAPI**"
|
||||
Hug inspired parts of APIStar, and was one of the tools I found most promising, alongside APIStar.
|
||||
|
||||
Hug helped inspiring **FastAPI** to use Python type hints to declare parameters, and to generate a schema defining the API automatically.
|
||||
|
||||
|
||||
### <a href="https://github.com/encode/apistar" target="_blank">APIStar</a> (<= 0.5)
|
||||
|
||||
Right before deciding to build **FastAPI** I found **APIStar** server. It had almost everything I was looking for and had a great design.
|
||||
|
||||
It was actually the first implementation of a framework using Python type hints to declare parameters and requests that I ever saw (before NestJS and Molten).
|
||||
It was one of the first implementations of a framework using Python type hints to declare parameters and requests that I ever saw (before NestJS and Molten). I found it more or less at the same time as Hug. But APIStar used the OpenAPI standard.
|
||||
|
||||
It had automatic data validation, data serialization and OpenAPI schema generation based on the same type hints in several places.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
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). (*)
|
||||
|
||||
But when checking benchmarks and comparisons you should have the following in mind.
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 85 KiB |
BIN
docs/img/tutorial/security/image12.png
Normal file
BIN
docs/img/tutorial/security/image12.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 68 KiB |
@@ -43,6 +43,28 @@ The key features are:
|
||||
|
||||
<small>* estimation based on tests on an internal development team, building production applications.</small>
|
||||
|
||||
## Opinions
|
||||
|
||||
"*[...] I'm using **FastAPI** a ton these days. [...] I'm actually planning to use it for all of my team's **ML services at Microsoft**. Some of them are getting integrated into the core **Windows** product and some **Office** products.*"
|
||||
|
||||
<div style="text-align: right; margin-right: 10%;">Kabir Khan - <strong>Microsoft</strong> <a href="https://github.com/tiangolo/fastapi/pull/26" target="_blank"><small>(ref)</small></a></div>
|
||||
|
||||
---
|
||||
|
||||
"*I’m over the moon excited about **FastAPI**. It’s so fun!*"
|
||||
|
||||
<div style="text-align: right; margin-right: 10%;">Brian Okken - <strong><a href="https://pythonbytes.fm/episodes/show/123/time-to-right-the-py-wrongs?time_in_sec=855" target="_blank">Python Bytes</a> podcast host</strong> <a href="https://twitter.com/brianokken/status/1112220079972728832" target="_blank"><small>(ref)</small></a></div>
|
||||
|
||||
---
|
||||
|
||||
"*Honestly, what you've built looks super solid and polished. In many ways, it's what I wanted **Hug** to be - it's really inspiring to see someone build that.*"
|
||||
|
||||
<div style="text-align: right; margin-right: 10%;">Timothy Crosley - <strong><a href="http://www.hug.rest/" target="_blank">Hug</a> creator</strong> <a href="https://news.ycombinator.com/item?id=19455465" target="_blank"><small>(ref)</small></a></div>
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
## Requirements
|
||||
|
||||
@@ -198,7 +220,7 @@ def read_item(item_id: int, q: str = None):
|
||||
|
||||
|
||||
@app.put("/items/{item_id}")
|
||||
def create_item(item_id: int, item: Item):
|
||||
def update_item(item_id: int, item: Item):
|
||||
return {"item_name": item.name, "item_id": item_id}
|
||||
```
|
||||
|
||||
|
||||
@@ -1,5 +1,63 @@
|
||||
## Next release
|
||||
|
||||
## 0.19.0
|
||||
|
||||
* Rename *path operation decorator* parameter `content_type` to `response_class`. PR <a href="https://github.com/tiangolo/fastapi/pull/183" target="_blank">#183</a>.
|
||||
|
||||
* Add docs:
|
||||
* How to use the `jsonable_encoder` in <a href="https://fastapi.tiangolo.com/tutorial/tutorial/encoder/" target="_blank">JSON compatible encoder</a>.
|
||||
* How to <a href="https://fastapi.tiangolo.com/tutorial/tutorial/response-directly/" target="_blank">Return a Response directly</a>.
|
||||
* Update how to use a <a href="https://fastapi.tiangolo.com/tutorial/tutorial/custom-response/" target="_blank">Custom Response Class</a>.
|
||||
* PR <a href="https://github.com/tiangolo/fastapi/pull/184" target="_blank">#184</a>.
|
||||
|
||||
## 0.18.0
|
||||
|
||||
* Add docs for <a href="https://fastapi.tiangolo.com/tutorial/custom-response/" target="_blank">HTTP Basic Auth</a>. PR <a href="https://github.com/tiangolo/fastapi/pull/177" target="_blank">#177</a>.
|
||||
|
||||
* Upgrade HTTP Basic Auth handling with automatic headers (automatic browser login prompt). PR <a href="https://github.com/tiangolo/fastapi/pull/175" target="_blank">#175</a>.
|
||||
|
||||
* Update dependencies for security. PR <a href="https://github.com/tiangolo/fastapi/pull/174" target="_blank">#174</a>.
|
||||
|
||||
* Add docs for <a href="https://fastapi.tiangolo.com/tutorial/middleware/" target="_blank">Middleware</a>. PR <a href="https://github.com/tiangolo/fastapi/pull/173" target="_blank">#173</a>.
|
||||
|
||||
## 0.17.0
|
||||
|
||||
* Make Flit publish from CI. PR <a href="https://github.com/tiangolo/fastapi/pull/170" target="_blank">#170</a>.
|
||||
|
||||
* Add documentation about handling <a href="https://fastapi.tiangolo.com/tutorial/cors/" target="_blank">CORS (Cross-Origin Resource Sharing)</a>. PR <a href="https://github.com/tiangolo/fastapi/pull/169" target="_blank">#169</a>.
|
||||
|
||||
* By default, encode by alias. This allows using Pydantic `alias` parameters working by default. PR <a href="https://github.com/tiangolo/fastapi/pull/168" target="_blank">#168</a>.
|
||||
|
||||
## 0.16.0
|
||||
|
||||
* Upgrade *path operation* `doctsring` parsing to support proper Markdown descriptions. New documentation at <a href="https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#description-from-docstring" target="_blank">Path Operation Configuration</a>. PR <a href="https://github.com/tiangolo/fastapi/pull/163" target="_blank">#163</a>.
|
||||
|
||||
* Refactor internal usage of Pydantic to use correct data types. PR <a href="https://github.com/tiangolo/fastapi/pull/164" target="_blank">#164</a>.
|
||||
|
||||
* Upgrade Pydantic to version `0.23`. PR <a href="https://github.com/tiangolo/fastapi/pull/160" target="_blank">#160</a> by <a href="https://github.com/euri10" target="_blank">@euri10</a>.
|
||||
|
||||
* Fix typo in Tutorial about Extra Models. PR <a href="https://github.com/tiangolo/fastapi/pull/159" target="_blank">#159</a> by <a href="https://github.com/danielmichaels" target="_blank">@danielmichaels</a>.
|
||||
|
||||
* Fix <a href="https://fastapi.tiangolo.com/tutorial/query-params/" target="_blank">Query Parameters</a> URL examples in docs. PR <a href="https://github.com/tiangolo/fastapi/pull/157" target="_blank">#157</a> by <a href="https://github.com/hayata-yamamoto" target="_blank">@hayata-yamamoto</a>.
|
||||
|
||||
## 0.15.0
|
||||
|
||||
* Add support for multiple file uploads (as a single form field). New docs at: <a href="https://fastapi.tiangolo.com/tutorial/request-files/#multiple-file-uploads" target="_blank">Multiple file uploads</a>. PR <a href="https://github.com/tiangolo/fastapi/pull/158" target="_blank">#158</a>.
|
||||
|
||||
* Add docs for: <a href="https://fastapi.tiangolo.com/tutorial/additional-status-codes/" target="_blank">Additional Status Codes</a>. PR <a href="https://github.com/tiangolo/fastapi/pull/156" target="_blank">#156</a>.
|
||||
|
||||
## 0.14.0
|
||||
|
||||
* Improve automatically generated names of path operations in OpenAPI (in API docs). A function `read_items` instead of having a generated name "Read Items Get" will have "Read Items". PR <a href="https://github.com/tiangolo/fastapi/pull/155" target="_blank">#155</a>.
|
||||
|
||||
* Add docs for: <a href="https://fastapi.tiangolo.com/tutorial/testing/" target="_blank">Testing **FastAPI**</a>. PR <a href="https://github.com/tiangolo/fastapi/pull/151" target="_blank">#151</a>.
|
||||
|
||||
* Update `/docs` Swagger UI to enable deep linking. This allows sharing the URL pointing directly to the path operation documentation in the docs. PR <a href="https://github.com/tiangolo/fastapi/pull/148" target="_blank">#148</a> by <a href="https://github.com/wshayes" target="_blank">@wshayes</a>.
|
||||
|
||||
* Update development dependencies, `Pipfile.lock`. PR <a href="https://github.com/tiangolo/fastapi/pull/150" target="_blank">#150</a>.
|
||||
|
||||
* Include Falcon and Hug in: <a href="https://fastapi.tiangolo.com/alternatives/" target="_blank">Alternatives, Inspiration and Comparisons</a>.
|
||||
|
||||
## 0.13.0
|
||||
|
||||
* Improve/upgrade OAuth2 scopes support with `SecurityScopes`:
|
||||
|
||||
20
docs/src/additional_status_codes/tutorial001.py
Normal file
20
docs/src/additional_status_codes/tutorial001.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from fastapi import Body, FastAPI
|
||||
from starlette.responses import JSONResponse
|
||||
from starlette.status import HTTP_201_CREATED
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
items = {"foo": {"name": "Fighters", "size": 6}, "bar": {"name": "Tenders", "size": 3}}
|
||||
|
||||
|
||||
@app.put("/items/{item_id}")
|
||||
async def upsert_item(item_id: str, name: str = Body(None), size: int = Body(None)):
|
||||
if item_id in items:
|
||||
item = items[item_id]
|
||||
item["name"] = name
|
||||
item["size"] = size
|
||||
return item
|
||||
else:
|
||||
item = {"name": name, "size": size}
|
||||
items[item_id] = item
|
||||
return JSONResponse(status_code=HTTP_201_CREATED, content=item)
|
||||
0
docs/src/app_testing/__init__.py
Normal file
0
docs/src/app_testing/__init__.py
Normal file
8
docs/src/app_testing/main.py
Normal file
8
docs/src/app_testing/main.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from fastapi import FastAPI
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.get("/")
|
||||
async def read_main():
|
||||
return {"msg": "Hello World"}
|
||||
11
docs/src/app_testing/test_main.py
Normal file
11
docs/src/app_testing/test_main.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from starlette.testclient import TestClient
|
||||
|
||||
from .main import app
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
def test_read_main():
|
||||
response = client.get("/")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"msg": "Hello World"}
|
||||
18
docs/src/app_testing/tutorial001.py
Normal file
18
docs/src/app_testing/tutorial001.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from fastapi import FastAPI
|
||||
from starlette.testclient import TestClient
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.get("/")
|
||||
async def read_main():
|
||||
return {"msg": "Hello World"}
|
||||
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
def test_read_main():
|
||||
response = client.get("/")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"msg": "Hello World"}
|
||||
31
docs/src/app_testing/tutorial002.py
Normal file
31
docs/src/app_testing/tutorial002.py
Normal file
@@ -0,0 +1,31 @@
|
||||
from fastapi import FastAPI
|
||||
from starlette.testclient import TestClient
|
||||
from starlette.websockets import WebSocket
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.get("/")
|
||||
async def read_main():
|
||||
return {"msg": "Hello World"}
|
||||
|
||||
|
||||
@app.websocket_route("/ws")
|
||||
async def websocket(websocket: WebSocket):
|
||||
await websocket.accept()
|
||||
await websocket.send_json({"msg": "Hello WebSocket"})
|
||||
await websocket.close()
|
||||
|
||||
|
||||
def test_read_main():
|
||||
client = TestClient(app)
|
||||
response = client.get("/")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"msg": "Hello World"}
|
||||
|
||||
|
||||
def test_websocket():
|
||||
client = TestClient(app)
|
||||
with client.websocket_connect("/ws") as websocket:
|
||||
data = websocket.receive_json()
|
||||
assert data == {"msg": "Hello WebSocket"}
|
||||
24
docs/src/app_testing/tutorial003.py
Normal file
24
docs/src/app_testing/tutorial003.py
Normal file
@@ -0,0 +1,24 @@
|
||||
from fastapi import FastAPI
|
||||
from starlette.testclient import TestClient
|
||||
|
||||
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_items(item_id: str):
|
||||
return items[item_id]
|
||||
|
||||
|
||||
def test_read_items():
|
||||
with TestClient(app) as client:
|
||||
response = client.get("/items/foo")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"name": "Fighters"}
|
||||
19
docs/src/cors/tutorial001.py
Normal file
19
docs/src/cors/tutorial001.py
Normal file
@@ -0,0 +1,19 @@
|
||||
from fastapi import FastAPI
|
||||
from starlette.middleware.cors import CORSMiddleware
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
origins = [
|
||||
"http://localhost.tiangolo.com",
|
||||
"https://localhost.tiangolo.com",
|
||||
"http:localhost",
|
||||
"http:localhost:8080",
|
||||
]
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=origins,
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
@@ -4,6 +4,6 @@ from starlette.responses import UJSONResponse
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.get("/items/", content_type=UJSONResponse)
|
||||
@app.get("/items/", response_class=UJSONResponse)
|
||||
async def read_items():
|
||||
return [{"item_id": "Foo"}]
|
||||
|
||||
@@ -4,7 +4,7 @@ from starlette.responses import HTMLResponse
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.get("/items/", content_type=HTMLResponse)
|
||||
@app.get("/items/", response_class=HTMLResponse)
|
||||
async def read_items():
|
||||
return """
|
||||
<html>
|
||||
|
||||
@@ -18,6 +18,6 @@ def generate_html_response():
|
||||
return HTMLResponse(content=html_content, status_code=200)
|
||||
|
||||
|
||||
@app.get("/items/", content_type=HTMLResponse)
|
||||
@app.get("/items/", response_class=HTMLResponse)
|
||||
async def read_items():
|
||||
return generate_html_response()
|
||||
|
||||
22
docs/src/encoder/tutorial001.py
Normal file
22
docs/src/encoder/tutorial001.py
Normal file
@@ -0,0 +1,22 @@
|
||||
from datetime import datetime
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from pydantic import BaseModel
|
||||
|
||||
fake_db = {}
|
||||
|
||||
|
||||
class Item(BaseModel):
|
||||
title: str
|
||||
timestamp: datetime
|
||||
description: str = None
|
||||
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.put("/items/{id}")
|
||||
def update_item(id: str, item: Item):
|
||||
json_compatible_item_data = jsonable_encoder(item)
|
||||
fake_db[id] = json_compatible_item_data
|
||||
@@ -12,5 +12,5 @@ async def startup_event():
|
||||
|
||||
|
||||
@app.get("/items/{item_id}")
|
||||
async def read_item(item_id: str):
|
||||
async def read_items(item_id: str):
|
||||
return items[item_id]
|
||||
|
||||
@@ -6,7 +6,7 @@ items = {"foo": "The Foo Wrestlers"}
|
||||
|
||||
|
||||
@app.get("/items/{item_id}")
|
||||
async def create_item(item_id: str):
|
||||
async def read_item(item_id: str):
|
||||
if item_id not in items:
|
||||
raise HTTPException(status_code=404, detail="Item not found")
|
||||
return {"item": items[item_id]}
|
||||
|
||||
@@ -6,7 +6,7 @@ items = {"foo": "The Foo Wrestlers"}
|
||||
|
||||
|
||||
@app.get("/items-header/{item_id}")
|
||||
async def create_item_header(item_id: str):
|
||||
async def read_item_header(item_id: str):
|
||||
if item_id not in items:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
|
||||
15
docs/src/middleware/tutorial001.py
Normal file
15
docs/src/middleware/tutorial001.py
Normal file
@@ -0,0 +1,15 @@
|
||||
import time
|
||||
|
||||
from fastapi import FastAPI
|
||||
from starlette.requests import Request
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.middleware("http")
|
||||
async def add_process_time_header(request: Request, call_next):
|
||||
start_time = time.time()
|
||||
response = await call_next(request)
|
||||
process_time = time.time() - start_time
|
||||
response.headers["X-Process-Time"] = str(process_time)
|
||||
return response
|
||||
@@ -18,11 +18,11 @@ class Item(BaseModel):
|
||||
async def create_item(*, item: Item):
|
||||
"""
|
||||
Create an item with all the information:
|
||||
|
||||
* name: each item must have a name
|
||||
* description: a long description
|
||||
* price: required
|
||||
* tax: if the item doesn't have tax, you can omit this
|
||||
* tags: a set of unique tag strings for this item
|
||||
|
||||
- **name**: each item must have a name
|
||||
- **description**: a long description
|
||||
- **price**: required
|
||||
- **tax**: if the item doesn't have tax, you can omit this
|
||||
- **tags**: a set of unique tag strings for this item
|
||||
"""
|
||||
return item
|
||||
|
||||
@@ -23,11 +23,11 @@ class Item(BaseModel):
|
||||
async def create_item(*, item: Item):
|
||||
"""
|
||||
Create an item with all the information:
|
||||
|
||||
* name: each item must have a name
|
||||
* description: a long description
|
||||
* price: required
|
||||
* tax: if the item doesn't have tax, you can omit this
|
||||
* tags: a set of unique tag strings for this item
|
||||
|
||||
- **name**: each item must have a name
|
||||
- **description**: a long description
|
||||
- **price**: required
|
||||
- **tax**: if the item doesn't have tax, you can omit this
|
||||
- **tags**: a set of unique tag strings for this item
|
||||
"""
|
||||
return item
|
||||
|
||||
33
docs/src/request_files/tutorial002.py
Normal file
33
docs/src/request_files/tutorial002.py
Normal file
@@ -0,0 +1,33 @@
|
||||
from typing import List
|
||||
|
||||
from fastapi import FastAPI, File, UploadFile
|
||||
from starlette.responses import HTMLResponse
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.post("/files/")
|
||||
async def create_files(files: List[bytes] = File(...)):
|
||||
return {"file_sizes": [len(file) for file in files]}
|
||||
|
||||
|
||||
@app.post("/uploadfiles/")
|
||||
async def create_upload_files(files: List[UploadFile] = File(...)):
|
||||
return {"filenames": [file.filename for file in files]}
|
||||
|
||||
|
||||
@app.get("/")
|
||||
async def main():
|
||||
content = """
|
||||
<body>
|
||||
<form action="/files/" enctype="multipart/form-data" method="post">
|
||||
<input name="files" type="file" multiple>
|
||||
<input type="submit">
|
||||
</form>
|
||||
<form action="/uploadfiles/" enctype="multipart/form-data" method="post">
|
||||
<input name="files" type="file" multiple>
|
||||
<input type="submit">
|
||||
</form>
|
||||
</body>
|
||||
"""
|
||||
return HTMLResponse(content=content)
|
||||
21
docs/src/response_directly/tutorial001.py
Normal file
21
docs/src/response_directly/tutorial001.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from datetime import datetime
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from pydantic import BaseModel
|
||||
from starlette.responses import JSONResponse
|
||||
|
||||
|
||||
class Item(BaseModel):
|
||||
title: str
|
||||
timestamp: datetime
|
||||
description: str = None
|
||||
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.put("/items/{id}")
|
||||
def update_item(id: str, item: Item):
|
||||
json_compatible_item_data = jsonable_encoder(item)
|
||||
return JSONResponse(content=json_compatible_item_data)
|
||||
20
docs/src/response_directly/tutorial002.py
Normal file
20
docs/src/response_directly/tutorial002.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from fastapi import FastAPI
|
||||
from starlette.responses import Response
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.get("/legacy/")
|
||||
def get_legacy_data():
|
||||
data = """
|
||||
<?xml version="1.0"?>
|
||||
<shampoo>
|
||||
<Header>
|
||||
Apply shampoo here.
|
||||
<Header>
|
||||
<Body>
|
||||
You'll have to use soap here.
|
||||
</Body>
|
||||
</shampoo>
|
||||
"""
|
||||
return Response(content=data, media_type="application/xml")
|
||||
@@ -112,7 +112,7 @@ async def get_current_active_user(current_user: User = Depends(get_current_user)
|
||||
|
||||
|
||||
@app.post("/token", response_model=Token)
|
||||
async def route_login_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
|
||||
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
|
||||
user = authenticate_user(fake_users_db, form_data.username, form_data.password)
|
||||
if not user:
|
||||
raise HTTPException(status_code=400, detail="Incorrect username or password")
|
||||
|
||||
@@ -138,7 +138,7 @@ async def get_current_active_user(
|
||||
|
||||
|
||||
@app.post("/token", response_model=Token)
|
||||
async def route_login_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
|
||||
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
|
||||
user = authenticate_user(fake_users_db, form_data.username, form_data.password)
|
||||
if not user:
|
||||
raise HTTPException(status_code=400, detail="Incorrect username or password")
|
||||
|
||||
11
docs/src/security/tutorial006.py
Normal file
11
docs/src/security/tutorial006.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from fastapi import Depends, FastAPI
|
||||
from fastapi.security import HTTPBasic, HTTPBasicCredentials
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
security = HTTPBasic()
|
||||
|
||||
|
||||
@app.get("/users/me")
|
||||
def read_current_user(credentials: HTTPBasicCredentials = Depends(security)):
|
||||
return {"username": credentials.username, "password": credentials.password}
|
||||
22
docs/src/security/tutorial007.py
Normal file
22
docs/src/security/tutorial007.py
Normal file
@@ -0,0 +1,22 @@
|
||||
from fastapi import Depends, FastAPI, HTTPException
|
||||
from fastapi.security import HTTPBasic, HTTPBasicCredentials
|
||||
from starlette.status import HTTP_401_UNAUTHORIZED
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
security = HTTPBasic()
|
||||
|
||||
|
||||
def get_current_username(credentials: HTTPBasicCredentials = Depends(security)):
|
||||
if credentials.username != "foo" or credentials.password != "password":
|
||||
raise HTTPException(
|
||||
status_code=HTTP_401_UNAUTHORIZED,
|
||||
detail="Incorrect email or password",
|
||||
headers={"WWW-Authenticate": "Basic"},
|
||||
)
|
||||
return credentials.username
|
||||
|
||||
|
||||
@app.get("/users/me")
|
||||
def read_current_user(username: str = Depends(get_current_username)):
|
||||
return {"username": username}
|
||||
30
docs/tutorial/additional-status-codes.md
Normal file
30
docs/tutorial/additional-status-codes.md
Normal file
@@ -0,0 +1,30 @@
|
||||
By default, **FastAPI** will return the responses using Starlette's `JSONResponse`, putting the content you return from your *path operation* inside of that `JSONResponse`.
|
||||
|
||||
It will use the default status code or the one you set in your *path operation*.
|
||||
|
||||
## Additional status codes
|
||||
|
||||
If you want to return additional status codes apart from the main one, you can do that by returning a `Response` directly, like a `JSONResponse`, and set the additional status code directly.
|
||||
|
||||
For example, let's say that you want to have a *path operation* that allows to update items, and returns HTTP status codes of 200 "OK" when successful.
|
||||
|
||||
But you also want it to accept new items. And when the items didn't exist before, it creates them, and returns an HTTP status code of 201 "Created".
|
||||
|
||||
To achieve that, import `JSONResponse`, and return your content there directly, setting the `status_code` that you want:
|
||||
|
||||
```Python hl_lines="2 20"
|
||||
{!./src/additional_status_codes/tutorial001.py!}
|
||||
```
|
||||
|
||||
!!! warning
|
||||
When you return a `Response` directly, like in the example above, it will be returned directly.
|
||||
|
||||
It won't be serialized with a model, etc.
|
||||
|
||||
Make sure it has the data you want it to have, and that the values are valid JSON (if you are using `JSONResponse`).
|
||||
|
||||
## OpenAPI and API docs
|
||||
|
||||
If you return additional status codes and responses directly, they won't be included in the OpenAPI schema (the API docs), because FastAPI doesn't have a way to know before hand what you are going to return.
|
||||
|
||||
But you can document that in your code, using: <a href="https://fastapi.tiangolo.com/tutorial/additional-responses/" target="_blank">Additional Responses</a>.
|
||||
55
docs/tutorial/cors.md
Normal file
55
docs/tutorial/cors.md
Normal file
@@ -0,0 +1,55 @@
|
||||
<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" target="_blank">CORS or "Cross-Origin Resource Sharing"</a> refers to the situations when a frontend running in a browser has JavaScript code that communicates with a backend, and the backend is in a different "origin" than the frontend.
|
||||
|
||||
## Origin
|
||||
|
||||
An origin is the combination of protocol (`http`, `https`), domain (`myapp.com`, `localhost`, `localhost.tiangolo.com`), and port (`80`, `443`, `8080`).
|
||||
|
||||
So, all these are different origins:
|
||||
|
||||
* `http://localhost`
|
||||
* `https://localhost`
|
||||
* `http://localhost:8080`
|
||||
|
||||
Even if they are all in `localhost`, they use different protocols or ports, so, they are different "origins".
|
||||
|
||||
## Steps
|
||||
|
||||
So, let's say you have a frontend running in your browser at `http://localhost:8080`, and its JavaScript is trying to communicate with a backend running at `http://localhost` (because we don't specify a port, the browser will assume the default port `80`).
|
||||
|
||||
Then, the browser will send an HTTP `OPTIONS` request to the backend, and if the backend sends the appropriate headers authorizing the communication from this different origin (`http://localhost:8080`) then the browser will let the JavaScript in the frontend send its request to the backend.
|
||||
|
||||
To achieve this, the backend must have a list of "allowed origins".
|
||||
|
||||
In this case, it would have to include `http://localhost:8080` for the frontend to work correctly.
|
||||
|
||||
## Wildcards
|
||||
|
||||
It's also possible to declare the list as `"*"` (a "wildcard") to say that all are allowed.
|
||||
|
||||
But that will only allow certain types of communication, excluding everything that involves credentials: Cookies, Authorization headers like those used with Bearer Tokens, etc.
|
||||
|
||||
So, for everything to work correctly, it's better to specify explicitly the allowed origins.
|
||||
|
||||
## Use `CORSMiddleware`
|
||||
|
||||
You can configure it in your **FastAPI** application using Starlette's <a href="https://www.starlette.io/middleware/#corsmiddleware" target="_blank">`CORSMiddleware`</a>.
|
||||
|
||||
* Import it form Starlette.
|
||||
* Create a list of allowed origins (as strings).
|
||||
* Add it as a "middleware" to your **FastAPI** application.
|
||||
|
||||
You can also specify if your backend allows:
|
||||
|
||||
* Credentials (Authorization headers, Cookies, etc).
|
||||
* Specific HTTP methods (`POST`, `PUT`) or all of them with the wildcard `"*"`.
|
||||
* Specific HTTP headers or all of them with the wildcard `"*"`.
|
||||
|
||||
```Python hl_lines="2 6 7 8 9 10 11 13 14 15 16 17 18 19"
|
||||
{!./src/cors/tutorial001.py!}
|
||||
```
|
||||
|
||||
## More info
|
||||
|
||||
For more details of what you can specify in `CORSMiddleware`, check <a href="https://www.starlette.io/middleware/#corsmiddleware" target="_blank">Starlette's `CORSMiddleware` docs</a>.
|
||||
|
||||
For more info about <abbr title="Cross-Origin Resource Sharing">CORS</abbr>, check the <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" target="_blank">Mozilla CORS documentation</a>.
|
||||
@@ -1,98 +1,88 @@
|
||||
!!! warning
|
||||
This is a rather advanced topic.
|
||||
|
||||
|
||||
If you are starting with **FastAPI**, you might not need this.
|
||||
|
||||
By default, **FastAPI** will return the responses using Starlette's `JSONResponse`.
|
||||
|
||||
But you can override it.
|
||||
You can override it by returning a `Response` directly, <a href="https://fastapi.tiangolo.com/tutorial/response-directly/" target="_blank">as seen in a previous section</a>.
|
||||
|
||||
But if you return a `Response` directly, the data won't be automatically converted, and the documentation won't be automatically generated (for example, including the specific "media type", in the HTTP header `Content-Type`).
|
||||
|
||||
But you can also declare the `Response` that you want to be used, in the *path operation decorator*.
|
||||
|
||||
The contents that you return from your *path operation function* will be put inside of that `Response`.
|
||||
|
||||
And if that `Response` has a JSON media type (`application/json`), like is the case with the `JSONResponse` and `UJSONResponse`, the data you return will be automatically converted (and filtered) with any Pydantic `response_model` that you declared in the *path operation decorator*.
|
||||
|
||||
## Use `UJSONResponse`
|
||||
|
||||
For example, if you are squeezing performance, you can use `ujson` and set the response to be Starlette's `UJSONResponse`.
|
||||
For example, if you are squeezing performance, you can install and use `ujson` and set the response to be Starlette's `UJSONResponse`.
|
||||
|
||||
### Import `UJSONResponse`
|
||||
Import the `Response` class (sub-class) you want to use and declare it in the *path operation decorator*.
|
||||
|
||||
```Python hl_lines="2"
|
||||
```Python hl_lines="2 7"
|
||||
{!./src/custom_response/tutorial001.py!}
|
||||
```
|
||||
|
||||
!!! note
|
||||
Notice that you import it directly from `starlette.responses`, not from `fastapi`.
|
||||
|
||||
### Make your path operation use it
|
||||
|
||||
Make your path operation use `UJSONResponse` as the response class using the parameter `content_type`:
|
||||
|
||||
```Python hl_lines="7"
|
||||
{!./src/custom_response/tutorial001.py!}
|
||||
```
|
||||
|
||||
!!! info
|
||||
The parameter is called `content_type` because it will also be used to define the "media type" of the response.
|
||||
The parameter `response_class` will also be used to define the "media type" of the response.
|
||||
|
||||
And will be documented as such in OpenAPI.
|
||||
In this case, the HTTP header `Content-Type` will be set to `application/json`.
|
||||
|
||||
And it will be documented as such in OpenAPI.
|
||||
|
||||
## HTML Response
|
||||
|
||||
To return a response with HTML directly from **FastAPI**, use `HTMLResponse`.
|
||||
|
||||
### Import `HTMLResponse`
|
||||
* Import `HTMLResponse`.
|
||||
* Pass `HTMLResponse` as the parameter `content_type` of your path operation.
|
||||
|
||||
```Python hl_lines="2"
|
||||
```Python hl_lines="2 7"
|
||||
{!./src/custom_response/tutorial002.py!}
|
||||
```
|
||||
|
||||
!!! note
|
||||
Notice that you import it directly from `starlette.responses`, not from `fastapi`.
|
||||
|
||||
|
||||
### Define your `content_type` class
|
||||
|
||||
Pass `HTMLResponse` as the parameter `content_type` of your path operation:
|
||||
|
||||
```Python hl_lines="7"
|
||||
{!./src/custom_response/tutorial002.py!}
|
||||
```
|
||||
|
||||
!!! info
|
||||
The parameter is called `content_type` because it will also be used to define the "media type" of the response.
|
||||
The parameter `response_class` will also be used to define the "media type" of the response.
|
||||
|
||||
In this case, the HTTP header `Content-Type` will be set to `text/html`.
|
||||
|
||||
And it will be documented as such in OpenAPI.
|
||||
|
||||
|
||||
### Return a Starlette `Response`
|
||||
|
||||
You can also override the response directly in your path operation.
|
||||
|
||||
If you return an object that is an instance of Starlette's `Response`, it will be used as the response directly.
|
||||
As seen in <a href="https://fastapi.tiangolo.com/tutorial/response-directly/" target="_blank">another section</a>, you can also override the response directly in your path operation, by returning it.
|
||||
|
||||
The same example from above, returning an `HTMLResponse`, could look like:
|
||||
|
||||
```Python hl_lines="7"
|
||||
```Python hl_lines="2 7 19"
|
||||
{!./src/custom_response/tutorial003.py!}
|
||||
```
|
||||
|
||||
!!! info
|
||||
Of course, the `Content-Type` header will come from the the `Response` object your returned.
|
||||
|
||||
!!! warning
|
||||
A `Response` returned directly by your path operation function won't be documented in OpenAPI and won't be visible in the automatic interactive docs.
|
||||
A `Response` returned directly by your path operation function won't be documented in OpenAPI (for example, the `Content-Type` won't be documented) and won't be visible in the automatic interactive docs.
|
||||
|
||||
!!! info
|
||||
Of course, the actual `Content-Type` header, status code, etc, will come from the `Response` object your returned.
|
||||
|
||||
### Document in OpenAPI and override `Response`
|
||||
|
||||
If you want to override the response from inside of the function but at the same time document the "media type" in OpenAPI, you can use the `content_type` parameter AND return a `Response` object.
|
||||
If you want to override the response from inside of the function but at the same time document the "media type" in OpenAPI, you can use the `response_class` parameter AND return a `Response` object.
|
||||
|
||||
The `content_type` class will then be used only to document the OpenAPI path operation, but your `Response` will be used as is.
|
||||
The `response_class` will then be used only to document the OpenAPI path operation, but your `Response` will be used as is.
|
||||
|
||||
#### Return an `HTMLResponse` directly
|
||||
|
||||
For example, it could be something like:
|
||||
|
||||
```Python hl_lines="7 23"
|
||||
```Python hl_lines="7 23 21"
|
||||
{!./src/custom_response/tutorial004.py!}
|
||||
```
|
||||
|
||||
@@ -100,16 +90,10 @@ In this example, the function `generate_html_response()` already generates a Sta
|
||||
|
||||
By returning the result of calling `generate_html_response()`, you are already returning a `Response` that will override the default **FastAPI** behavior.
|
||||
|
||||
#### Declare `HTMLResponse` as `content_type`
|
||||
|
||||
But by declaring it also in the path operation decorator:
|
||||
|
||||
```Python hl_lines="21"
|
||||
{!./src/custom_response/tutorial004.py!}
|
||||
```
|
||||
|
||||
#### OpenAPI knows how to document it
|
||||
|
||||
...**FastAPI** will be able to document it in OpenAPI and in the interactive docs as HTML with `text/html`:
|
||||
But as you passed the `HTMLResponse` in the `response_class`, **FastAPI** will know how to document it in OpenAPI and the interactive docs as HTML with `text/html`:
|
||||
|
||||
<img src="/img/tutorial/custom-response/image01.png">
|
||||
|
||||
## Additional documentation
|
||||
|
||||
You can also declare the media type and many other details in OpenAPI using `responses`: <a href="https://fastapi.tiangolo.com/tutorial/additional-responses/" target="_blank">Additional Responses in OpenAPI</a>.
|
||||
|
||||
32
docs/tutorial/encoder.md
Normal file
32
docs/tutorial/encoder.md
Normal file
@@ -0,0 +1,32 @@
|
||||
There are some cases where you might need to convert a data type (like a Pydantic model) to something compatible with JSON (like a `dict`, `list`, etc).
|
||||
|
||||
For example, if you need to store it in a database.
|
||||
|
||||
For that, **FastAPI** provides a `jsonable_encoder()` function.
|
||||
|
||||
## Using the `jsonable_encoder`
|
||||
|
||||
Let's imagine that you have a database `fake_db` that only receives JSON compatible data.
|
||||
|
||||
For example, it doesn't receive `datetime` objects, as those are not compatible with JSON.
|
||||
|
||||
So, a `datetime` object would have to be converted to a `str` containing the data in <a href="https://en.wikipedia.org/wiki/ISO_8601" target="_blank">ISO format</a>.
|
||||
|
||||
The same way, this database wouldn't receive a Pydantic model (an object with attributes), only a `dict`.
|
||||
|
||||
You can use `jsonable_encoder` for that.
|
||||
|
||||
It receives an object, like a Pydantic model, and returns a JSON compatible version:
|
||||
|
||||
```Python hl_lines="4 21"
|
||||
{!./src/encoder/tutorial001.py!}
|
||||
```
|
||||
|
||||
In this example, it would convert the Pydantic model to a `dict`, and the `datetime` to a `str`.
|
||||
|
||||
The result of calling it is something that can be encoded with the Python standard <a href="https://docs.python.org/3/library/json.html#json.dumps" target="_blank">`json.dumps()`</a>.
|
||||
|
||||
It doesn't return a large `str` containing the data in JSON format (as a string). It returns a Python standard data structure (e.g. a `dict`) with values and sub-values that are all compatible with JSON.
|
||||
|
||||
!!! note
|
||||
`jsonable_encoder` is actually used by **FastAPI** internally to convert data. But it is useful in many other scenarios.
|
||||
@@ -3,7 +3,7 @@ Continuing with the previous example, it will be common to have more than one re
|
||||
This is especially the case for user models, because:
|
||||
|
||||
* The **input model** needs to be able to have a password.
|
||||
* The **output model** should do not have a password.
|
||||
* The **output model** should not have a password.
|
||||
* The **database model** would probably need to have a hashed password.
|
||||
|
||||
!!! danger
|
||||
|
||||
54
docs/tutorial/middleware.md
Normal file
54
docs/tutorial/middleware.md
Normal file
@@ -0,0 +1,54 @@
|
||||
You can add middleware to **FastAPI** applications.
|
||||
|
||||
A "middleware" is a function that works with every **request** before it is processed by any specific *path operation*. And also with every **response** before returning it.
|
||||
|
||||
* It takes each **request** that comes to your application.
|
||||
* It can then do something to that **request** or run any needed code.
|
||||
* Then it passes the **request** to be processed by the rest of the application (by some *path operation*).
|
||||
* It then takes the **response** generated by the application (by some *path operation*).
|
||||
* It can do something to that **response** or run any needed code.
|
||||
* Then it returns the **response**.
|
||||
|
||||
## Create a middleware
|
||||
|
||||
To create a middleware you use the decorator `@app.middleware("http")` on top of a function.
|
||||
|
||||
The middleware function receives:
|
||||
|
||||
* The `request`.
|
||||
* A function `call_next` that will receive the `request` as a parameter.
|
||||
* This function will pass the `request` to the corresponding *path operation*.
|
||||
* Then it returns the `response` generated by the corresponding *path operation*.
|
||||
* You can then modify further the `response` before returning it.
|
||||
|
||||
```Python hl_lines="9 10 12 15"
|
||||
{!./src/middleware/tutorial001.py!}
|
||||
```
|
||||
|
||||
!!! tip
|
||||
This technique is used in the tutorial about <a href="https://fastapi.tiangolo.com/tutorial/sql-databases/" target="_blank">SQL (Relational) Databases</a>.
|
||||
|
||||
### Before and after the `response`
|
||||
|
||||
You can add code to be run with the `request`, before any *path operation* receives it.
|
||||
|
||||
And also after the `response` is generated, before returning it.
|
||||
|
||||
For example, you could add a custom header `X-Process-Time` containing the time in seconds that it took to process the request and generate a response:
|
||||
|
||||
```Python hl_lines="11 13 14"
|
||||
{!./src/middleware/tutorial001.py!}
|
||||
```
|
||||
|
||||
## Starlette's Middleware
|
||||
|
||||
You can also add any other <a href="https://www.starlette.io/middleware/" target="_blank">Starlette Middleware</a>.
|
||||
|
||||
These are classes instead of plain functions.
|
||||
|
||||
Including:
|
||||
|
||||
* `CORSMiddleware` (described in the next section).
|
||||
* `GZipMiddleware`.
|
||||
* `SentryMiddleware`.
|
||||
* ...and others.
|
||||
@@ -42,6 +42,8 @@ You can add a `summary` and `description`:
|
||||
|
||||
As descriptions tend to be long and cover multiple lines, you can declare the path operation description in the function <abbr title="a multi-line string as the first expression inside a function (not assigned to any variable) used for documentation">docstring</abbr> and **FastAPI** will read it from there.
|
||||
|
||||
You can write <a href="https://en.wikipedia.org/wiki/Markdown" target="_blank">Markdown</a> in the docstring, it will be interpreted and displayed correctly (taking into account docstring indentation).
|
||||
|
||||
```Python hl_lines="19 20 21 22 23 24 25 26 27"
|
||||
{!./src/path_operation_configuration/tutorial004.py!}
|
||||
```
|
||||
@@ -50,9 +52,6 @@ It will be used in the interactive docs:
|
||||
|
||||
<img src="/img/tutorial/path-operation-configuration/image02.png">
|
||||
|
||||
!!! info
|
||||
OpenAPI specifies that descriptions can be written in Markdown syntax, but the interactive documentation systems included still don't support it at the time of writing this, although they have it in their plans.
|
||||
|
||||
## Response description
|
||||
|
||||
You can specify the response description with the parameter `response_description`:
|
||||
|
||||
@@ -81,31 +81,31 @@ You can also declare `bool` types, and they will be converted:
|
||||
In this case, if you go to:
|
||||
|
||||
```
|
||||
http://127.0.0.1:8000/items/?short=1
|
||||
http://127.0.0.1:8000/items/foo?short=1
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```
|
||||
http://127.0.0.1:8000/items/?short=True
|
||||
http://127.0.0.1:8000/items/foo?short=True
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```
|
||||
http://127.0.0.1:8000/items/?short=true
|
||||
http://127.0.0.1:8000/items/foo?short=true
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```
|
||||
http://127.0.0.1:8000/items/?short=on
|
||||
http://127.0.0.1:8000/items/foo?short=on
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```
|
||||
http://127.0.0.1:8000/items/?short=yes
|
||||
http://127.0.0.1:8000/items/foo?short=yes
|
||||
```
|
||||
|
||||
or any other case variation (uppercase, first letter in uppercase, etc), your function will see the parameter `short` with a `bool` value of `True`. Otherwise as `False`.
|
||||
|
||||
@@ -43,7 +43,7 @@ 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.
|
||||
* This means that it will work well for large files like images, videos, large binaries, etc. 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.
|
||||
@@ -107,6 +107,27 @@ The way HTML forms (`<form></form>`) sends the data to the server normally uses
|
||||
|
||||
This is not a limitation of **FastAPI**, it's part of the HTTP protocol.
|
||||
|
||||
## Multiple file uploads
|
||||
|
||||
It's possible to upload several files at the same time.
|
||||
|
||||
They would be associated to the same "form field" sent using "form data".
|
||||
|
||||
To use that, declare a `List` of `bytes` or `UploadFile`:
|
||||
|
||||
```Python hl_lines="10 15"
|
||||
{!./src/request_files/tutorial002.py!}
|
||||
```
|
||||
|
||||
You will receive, as declared, a `list` of `bytes` or `UploadFile`s.
|
||||
|
||||
!!! note
|
||||
Notice that, as of 2019-04-14, Swagger UI doesn't support multiple file uploads in the same form field. For more information, check <a href="https://github.com/swagger-api/swagger-ui/issues/4276" target="_blank">#4276</a> and <a href="https://github.com/swagger-api/swagger-ui/issues/3641" target="_blank">#3641</a>.
|
||||
|
||||
Nevertheless, **FastAPI** is already compatible with it, using the standard OpenAPI.
|
||||
|
||||
So, whenever Swagger UI supports multi-file uploads, or any other tools that supports OpenAPI, they will be compatible with **FastAPI**.
|
||||
|
||||
## Recap
|
||||
|
||||
Use `File` to declare files to be uploaded as input parameters (as form data).
|
||||
|
||||
63
docs/tutorial/response-directly.md
Normal file
63
docs/tutorial/response-directly.md
Normal file
@@ -0,0 +1,63 @@
|
||||
When you create a **FastAPI** *path operation* you can normally return any data from it: a `dict`, a `list`, a Pydantic model, a database model, etc.
|
||||
|
||||
By default, **FastAPI** would automatically convert that return value to JSON using the <a href="https://fastapi.tiangolo.com/tutorial/encoder/" target="_blank">`jsonable_encoder`</a>.
|
||||
|
||||
Then, behind the scenes, it would put that JSON-compatible data (e.g. a `dict`) inside of a Starlette `JSONResponse` that would be used to send the response to the client.
|
||||
|
||||
But you can return a `JSONResponse` directly from your *path operations*.
|
||||
|
||||
It might be useful, for example, to return custom headers or cookies.
|
||||
|
||||
## Starlette `Response`
|
||||
|
||||
In fact, you can return any <a href="https://www.starlette.io/responses/" target="_blank">Starlette `Response`</a> or any sub-class of it.
|
||||
|
||||
!!! tip
|
||||
`JSONResponse` itself is a sub-class of `Response`.
|
||||
|
||||
And when you return a Starlette `Response`, **FastAPI** will pass it directly.
|
||||
|
||||
It won't do any data conversion with Pydantic models, it won't convert the contents to any type, etc.
|
||||
|
||||
This gives you a lot of flexibility. You can return any data type, override any data declaration or validation, etc.
|
||||
|
||||
## Using the `jsonable_encoder` in a `Response`
|
||||
|
||||
Because **FastAPI** doesn't do any change to a `Response` you return, you have to make sure it's contents are ready for it.
|
||||
|
||||
For example, you cannot put a Pydantic model in a `JSONResponse` without first converting it to a `dict` with all the data types (like `datetime`, `UUID`, etc) converted to JSON-compatible types.
|
||||
|
||||
For those cases, you can use the `jsonable_encoder` to convert your data before passing it to a response:
|
||||
|
||||
```Python hl_lines="4 6 20 21"
|
||||
{!./src/response_directly/tutorial001.py!}
|
||||
```
|
||||
|
||||
!!! note
|
||||
Notice that you import it directly from `starlette.responses`, not from `fastapi`.
|
||||
|
||||
## Returning a custom `Response`
|
||||
|
||||
The example above shows all the parts you need, but it's not very useful yet, as you could have just returned the `item` directly, and **FastAPI** would put it in a `JSONResponse` for you, converting it to a `dict`, etc. All that by default.
|
||||
|
||||
Now, let's see how you could use that to return a custom response.
|
||||
|
||||
Let's say you want to return a response that is not available in the default <a href="https://www.starlette.io/responses/" target="_blank">Starlette `Response`s</a>.
|
||||
|
||||
Let's say that you want to return <a href="https://en.wikipedia.org/wiki/XML" target="_blank">XML</a>.
|
||||
|
||||
You could put your XML content in a string, put it in a Starlette Response, and return it:
|
||||
|
||||
```Python hl_lines="2 20"
|
||||
{!./src/response_directly/tutorial002.py!}
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
When you return a `Response` directly its data is not validated, converted (serialized), nor documented automatically.
|
||||
|
||||
But you can still <a href="tutorial/additional-responses/" target="_blank">document it</a>.
|
||||
|
||||
In the next sections you will see how to use/declare these custom `Response`s while still having automatic data conversion, documentation, etc.
|
||||
|
||||
You will also see how to use them to set response Headers and Cookies.
|
||||
40
docs/tutorial/security/http-basic-auth.md
Normal file
40
docs/tutorial/security/http-basic-auth.md
Normal file
@@ -0,0 +1,40 @@
|
||||
For the simplest cases, you can use HTTP Basic Auth.
|
||||
|
||||
In HTTP Basic Auth, the application expects a header that contains a username and a password.
|
||||
|
||||
If it doesn't receive it, it returns an HTTP 401 "Unauthorized" error.
|
||||
|
||||
And returns a header `WWW-Authenticate` with a value of `Basic`, and an optional `realm` parameter.
|
||||
|
||||
That tells the browser to show the integrated prompt for a username and password.
|
||||
|
||||
Then, when you type that username and password, the browser sends them in the header automatically.
|
||||
|
||||
## Simple HTTP Basic Auth
|
||||
|
||||
* Import `HTTPBAsic` and `HTTPBasicCredentials`.
|
||||
* Create a "`security` scheme" using `HTTPBAsic`.
|
||||
* Use that `security` with a dependency in your *path operation*.
|
||||
* It returns an object of type `HTTPBasicCredentials`:
|
||||
* It contains the `username` and `password` sent.
|
||||
|
||||
|
||||
```Python hl_lines="2 6 10"
|
||||
{!./src/security/tutorial006.py!}
|
||||
```
|
||||
|
||||
When you try to open the URL for the first time (or click the "Execute" button in the docs) the browser will ask you for your username and password:
|
||||
|
||||
<img src="/img/tutorial/security/image12.png">
|
||||
|
||||
## Check the username
|
||||
|
||||
Here's a more complete example.
|
||||
|
||||
Use a dependency to check if the username and password are correct.
|
||||
|
||||
If the credentials are incorrect, return an `HTTPException` with a status code 401 (the same returned when no credentials are provided) and add the header `WWW-Authenticate` to make the browser show the login prompt again:
|
||||
|
||||
```Python hl_lines="10 11 12 13 14 15 16 17 21"
|
||||
{!./src/security/tutorial007.py!}
|
||||
```
|
||||
@@ -20,7 +20,7 @@ It is quite an extensive specification and covers several complex use cases.
|
||||
|
||||
It includes ways to authenticate using a "third party".
|
||||
|
||||
That's what all the system with "login with Facebook, Google, Twitter, GitHub" use underneath.
|
||||
That's what all the systems with "login with Facebook, Google, Twitter, GitHub" use underneath.
|
||||
|
||||
### OAuth 1
|
||||
|
||||
|
||||
85
docs/tutorial/testing.md
Normal file
85
docs/tutorial/testing.md
Normal file
@@ -0,0 +1,85 @@
|
||||
Thanks to <a href="https://www.starlette.io/testclient/" target="_blank">Starlette's TestClient</a>, testing **FastAPI** applications is easy and enjoyable.
|
||||
|
||||
It is based on <a href="http://docs.python-requests.org" target="_blank">Requests</a>, so it's very familiar and intuitive.
|
||||
|
||||
With it, you can use <a href="https://docs.pytest.org/" target="_blank">pytest</a> directly with **FastAPI**.
|
||||
|
||||
## Using `TestClient`
|
||||
|
||||
Import `TestClient` from `starlette.testclient`.
|
||||
|
||||
Create a `TestClient` passing to it your **FastAPI**.
|
||||
|
||||
Create functions with a name that starts with `test_` (this is standard `pytest` conventions).
|
||||
|
||||
Use the `TestClient` object the same way as you do with `requests`.
|
||||
|
||||
Write simple `assert` statements with the standard Python expressions that you need to check (again, standard `pytest`).
|
||||
|
||||
```Python hl_lines="2 12 15 16 17 18"
|
||||
{!./src/app_testing/tutorial001.py!}
|
||||
```
|
||||
|
||||
!!! tip
|
||||
Notice that the testing functions are normal `def`, not `async def`.
|
||||
|
||||
And the calls to the client are also normal calls, not using `await`.
|
||||
|
||||
This allows you to use `pytest` directly without complications.
|
||||
|
||||
|
||||
## Separating tests
|
||||
|
||||
In a real application, you probably would have your tests in a different file.
|
||||
|
||||
And your **FastAPI** application might also be composed of several files/modules, etc.
|
||||
|
||||
### **FastAPI** app file
|
||||
|
||||
Let's say you have a file `main.py` with your **FastAPI** app:
|
||||
|
||||
```Python
|
||||
{!./src/app_testing/main.py!}
|
||||
```
|
||||
|
||||
### Testing file
|
||||
|
||||
Then you could have a file `test_main.py` with your tests, and import your `app` from the `main` module (`main.py`):
|
||||
|
||||
```Python
|
||||
{!./src/app_testing/test_main.py!}
|
||||
```
|
||||
|
||||
## Testing WebSockets
|
||||
|
||||
You can use the same `TestClient` to test WebSockets.
|
||||
|
||||
For this, you use the `TestClient` in a `with` statement, connecting to the WebSocket:
|
||||
|
||||
```Python hl_lines="27 28 29 30 31"
|
||||
{!./src/app_testing/tutorial002.py!}
|
||||
```
|
||||
|
||||
## Testing Events, `startup` and `shutdown`
|
||||
|
||||
When you need your event handlers (`startup` and `shutdown`) to run in your tests, you can use the `TestClient` with a `with` statement:
|
||||
|
||||
```Python hl_lines="9 10 11 12 20 21 22 23 24"
|
||||
{!./src/app_testing/tutorial003.py!}
|
||||
```
|
||||
|
||||
## Run it
|
||||
|
||||
After that, you just need to install `pytest`:
|
||||
|
||||
```bash
|
||||
pip install pytest
|
||||
```
|
||||
|
||||
It will detect the files and tests automatically, execute them, and report the results back to you.
|
||||
|
||||
Run the tests with:
|
||||
|
||||
```bash
|
||||
pytest
|
||||
```
|
||||
@@ -1,6 +1,6 @@
|
||||
"""FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
|
||||
|
||||
__version__ = "0.13.0"
|
||||
__version__ = "0.19.0"
|
||||
|
||||
from starlette.background import BackgroundTasks
|
||||
|
||||
|
||||
@@ -119,7 +119,7 @@ class FastAPI(Starlette):
|
||||
methods: List[str] = None,
|
||||
operation_id: str = None,
|
||||
include_in_schema: bool = True,
|
||||
content_type: Type[Response] = JSONResponse,
|
||||
response_class: Type[Response] = JSONResponse,
|
||||
name: str = None,
|
||||
) -> None:
|
||||
self.router.add_api_route(
|
||||
@@ -136,7 +136,7 @@ class FastAPI(Starlette):
|
||||
methods=methods,
|
||||
operation_id=operation_id,
|
||||
include_in_schema=include_in_schema,
|
||||
content_type=content_type,
|
||||
response_class=response_class,
|
||||
name=name,
|
||||
)
|
||||
|
||||
@@ -155,7 +155,7 @@ class FastAPI(Starlette):
|
||||
methods: List[str] = None,
|
||||
operation_id: str = None,
|
||||
include_in_schema: bool = True,
|
||||
content_type: Type[Response] = JSONResponse,
|
||||
response_class: Type[Response] = JSONResponse,
|
||||
name: str = None,
|
||||
) -> Callable:
|
||||
def decorator(func: Callable) -> Callable:
|
||||
@@ -173,7 +173,7 @@ class FastAPI(Starlette):
|
||||
methods=methods,
|
||||
operation_id=operation_id,
|
||||
include_in_schema=include_in_schema,
|
||||
content_type=content_type,
|
||||
response_class=response_class,
|
||||
name=name,
|
||||
)
|
||||
return func
|
||||
@@ -206,7 +206,7 @@ class FastAPI(Starlette):
|
||||
deprecated: bool = None,
|
||||
operation_id: str = None,
|
||||
include_in_schema: bool = True,
|
||||
content_type: Type[Response] = JSONResponse,
|
||||
response_class: Type[Response] = JSONResponse,
|
||||
name: str = None,
|
||||
) -> Callable:
|
||||
return self.router.get(
|
||||
@@ -221,7 +221,7 @@ class FastAPI(Starlette):
|
||||
deprecated=deprecated,
|
||||
operation_id=operation_id,
|
||||
include_in_schema=include_in_schema,
|
||||
content_type=content_type,
|
||||
response_class=response_class,
|
||||
name=name,
|
||||
)
|
||||
|
||||
@@ -239,7 +239,7 @@ class FastAPI(Starlette):
|
||||
deprecated: bool = None,
|
||||
operation_id: str = None,
|
||||
include_in_schema: bool = True,
|
||||
content_type: Type[Response] = JSONResponse,
|
||||
response_class: Type[Response] = JSONResponse,
|
||||
name: str = None,
|
||||
) -> Callable:
|
||||
return self.router.put(
|
||||
@@ -254,7 +254,7 @@ class FastAPI(Starlette):
|
||||
deprecated=deprecated,
|
||||
operation_id=operation_id,
|
||||
include_in_schema=include_in_schema,
|
||||
content_type=content_type,
|
||||
response_class=response_class,
|
||||
name=name,
|
||||
)
|
||||
|
||||
@@ -272,7 +272,7 @@ class FastAPI(Starlette):
|
||||
deprecated: bool = None,
|
||||
operation_id: str = None,
|
||||
include_in_schema: bool = True,
|
||||
content_type: Type[Response] = JSONResponse,
|
||||
response_class: Type[Response] = JSONResponse,
|
||||
name: str = None,
|
||||
) -> Callable:
|
||||
return self.router.post(
|
||||
@@ -287,7 +287,7 @@ class FastAPI(Starlette):
|
||||
deprecated=deprecated,
|
||||
operation_id=operation_id,
|
||||
include_in_schema=include_in_schema,
|
||||
content_type=content_type,
|
||||
response_class=response_class,
|
||||
name=name,
|
||||
)
|
||||
|
||||
@@ -305,7 +305,7 @@ class FastAPI(Starlette):
|
||||
deprecated: bool = None,
|
||||
operation_id: str = None,
|
||||
include_in_schema: bool = True,
|
||||
content_type: Type[Response] = JSONResponse,
|
||||
response_class: Type[Response] = JSONResponse,
|
||||
name: str = None,
|
||||
) -> Callable:
|
||||
return self.router.delete(
|
||||
@@ -320,7 +320,7 @@ class FastAPI(Starlette):
|
||||
deprecated=deprecated,
|
||||
operation_id=operation_id,
|
||||
include_in_schema=include_in_schema,
|
||||
content_type=content_type,
|
||||
response_class=response_class,
|
||||
name=name,
|
||||
)
|
||||
|
||||
@@ -338,7 +338,7 @@ class FastAPI(Starlette):
|
||||
deprecated: bool = None,
|
||||
operation_id: str = None,
|
||||
include_in_schema: bool = True,
|
||||
content_type: Type[Response] = JSONResponse,
|
||||
response_class: Type[Response] = JSONResponse,
|
||||
name: str = None,
|
||||
) -> Callable:
|
||||
return self.router.options(
|
||||
@@ -353,7 +353,7 @@ class FastAPI(Starlette):
|
||||
deprecated=deprecated,
|
||||
operation_id=operation_id,
|
||||
include_in_schema=include_in_schema,
|
||||
content_type=content_type,
|
||||
response_class=response_class,
|
||||
name=name,
|
||||
)
|
||||
|
||||
@@ -371,7 +371,7 @@ class FastAPI(Starlette):
|
||||
deprecated: bool = None,
|
||||
operation_id: str = None,
|
||||
include_in_schema: bool = True,
|
||||
content_type: Type[Response] = JSONResponse,
|
||||
response_class: Type[Response] = JSONResponse,
|
||||
name: str = None,
|
||||
) -> Callable:
|
||||
return self.router.head(
|
||||
@@ -386,7 +386,7 @@ class FastAPI(Starlette):
|
||||
deprecated=deprecated,
|
||||
operation_id=operation_id,
|
||||
include_in_schema=include_in_schema,
|
||||
content_type=content_type,
|
||||
response_class=response_class,
|
||||
name=name,
|
||||
)
|
||||
|
||||
@@ -404,7 +404,7 @@ class FastAPI(Starlette):
|
||||
deprecated: bool = None,
|
||||
operation_id: str = None,
|
||||
include_in_schema: bool = True,
|
||||
content_type: Type[Response] = JSONResponse,
|
||||
response_class: Type[Response] = JSONResponse,
|
||||
name: str = None,
|
||||
) -> Callable:
|
||||
return self.router.patch(
|
||||
@@ -419,7 +419,7 @@ class FastAPI(Starlette):
|
||||
deprecated=deprecated,
|
||||
operation_id=operation_id,
|
||||
include_in_schema=include_in_schema,
|
||||
content_type=content_type,
|
||||
response_class=response_class,
|
||||
name=name,
|
||||
)
|
||||
|
||||
@@ -437,7 +437,7 @@ class FastAPI(Starlette):
|
||||
deprecated: bool = None,
|
||||
operation_id: str = None,
|
||||
include_in_schema: bool = True,
|
||||
content_type: Type[Response] = JSONResponse,
|
||||
response_class: Type[Response] = JSONResponse,
|
||||
name: str = None,
|
||||
) -> Callable:
|
||||
return self.router.trace(
|
||||
@@ -452,6 +452,6 @@ class FastAPI(Starlette):
|
||||
deprecated=deprecated,
|
||||
operation_id=operation_id,
|
||||
include_in_schema=include_in_schema,
|
||||
content_type=content_type,
|
||||
response_class=response_class,
|
||||
name=name,
|
||||
)
|
||||
|
||||
@@ -22,8 +22,8 @@ from fastapi.dependencies.models import Dependant, SecurityRequirement
|
||||
from fastapi.security.base import SecurityBase
|
||||
from fastapi.security.oauth2 import OAuth2, SecurityScopes
|
||||
from fastapi.security.open_id_connect_url import OpenIdConnect
|
||||
from fastapi.utils import UnconstrainedConfig, get_path_param_names
|
||||
from pydantic import Schema, create_model
|
||||
from fastapi.utils import get_path_param_names
|
||||
from pydantic import BaseConfig, Schema, create_model
|
||||
from pydantic.error_wrappers import ErrorWrapper
|
||||
from pydantic.errors import MissingError
|
||||
from pydantic.fields import Field, Required, Shape
|
||||
@@ -31,8 +31,8 @@ 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
|
||||
from starlette.datastructures import FormData, Headers, QueryParams, UploadFile
|
||||
from starlette.requests import Request
|
||||
|
||||
param_supported_types = (
|
||||
str,
|
||||
@@ -47,6 +47,10 @@ param_supported_types = (
|
||||
Decimal,
|
||||
)
|
||||
|
||||
sequence_shapes = {Shape.LIST, Shape.SET, Shape.TUPLE}
|
||||
sequence_types = (list, set, tuple)
|
||||
sequence_shape_to_type = {Shape.LIST: list, Shape.SET: set, Shape.TUPLE: tuple}
|
||||
|
||||
|
||||
def get_sub_dependant(
|
||||
*, param: inspect.Parameter, path: str, security_scopes: List[str] = None
|
||||
@@ -199,8 +203,8 @@ def add_param_to_fields(
|
||||
default=None if required else default_value,
|
||||
alias=alias,
|
||||
required=required,
|
||||
model_config=UnconstrainedConfig,
|
||||
class_validators=[],
|
||||
model_config=BaseConfig,
|
||||
class_validators={},
|
||||
schema=schema,
|
||||
)
|
||||
if schema.in_ == params.ParamTypes.path:
|
||||
@@ -233,8 +237,8 @@ def add_param_to_body_fields(*, param: inspect.Parameter, dependant: Dependant)
|
||||
default=None if required else default_value,
|
||||
alias=schema.alias or param.name,
|
||||
required=required,
|
||||
model_config=UnconstrainedConfig,
|
||||
class_validators=[],
|
||||
model_config=BaseConfig,
|
||||
class_validators={},
|
||||
schema=schema,
|
||||
)
|
||||
dependant.body_params.append(field)
|
||||
@@ -318,7 +322,7 @@ def request_params_to_args(
|
||||
values = {}
|
||||
errors = []
|
||||
for field in required_params:
|
||||
if field.shape in {Shape.LIST, Shape.SET, Shape.TUPLE} and isinstance(
|
||||
if field.shape in sequence_shapes and isinstance(
|
||||
received_params, (QueryParams, Headers)
|
||||
):
|
||||
value = received_params.getlist(field.alias)
|
||||
@@ -332,7 +336,7 @@ def request_params_to_args(
|
||||
ErrorWrapper(
|
||||
MissingError(),
|
||||
loc=(schema.in_.value, field.alias),
|
||||
config=UnconstrainedConfig,
|
||||
config=BaseConfig,
|
||||
)
|
||||
)
|
||||
else:
|
||||
@@ -358,17 +362,24 @@ async def request_body_to_args(
|
||||
embed = getattr(field.schema, "embed", None)
|
||||
if len(required_params) == 1 and not embed:
|
||||
received_body = {field.alias: received_body}
|
||||
elif received_body is None:
|
||||
received_body = {}
|
||||
for field in required_params:
|
||||
value = received_body.get(field.alias)
|
||||
if value is None or (isinstance(field.schema, params.Form) and value == ""):
|
||||
if field.shape in sequence_shapes and isinstance(received_body, FormData):
|
||||
value = received_body.getlist(field.alias)
|
||||
else:
|
||||
value = received_body.get(field.alias)
|
||||
if (
|
||||
value is None
|
||||
or (isinstance(field.schema, params.Form) and value == "")
|
||||
or (
|
||||
isinstance(field.schema, params.Form)
|
||||
and field.shape in sequence_shapes
|
||||
and len(value) == 0
|
||||
)
|
||||
):
|
||||
if field.required:
|
||||
errors.append(
|
||||
ErrorWrapper(
|
||||
MissingError(),
|
||||
loc=("body", field.alias),
|
||||
config=UnconstrainedConfig,
|
||||
MissingError(), loc=("body", field.alias), config=BaseConfig
|
||||
)
|
||||
)
|
||||
else:
|
||||
@@ -380,6 +391,15 @@ async def request_body_to_args(
|
||||
and isinstance(value, UploadFile)
|
||||
):
|
||||
value = await value.read()
|
||||
elif (
|
||||
field.shape in sequence_shapes
|
||||
and isinstance(field.schema, params.File)
|
||||
and lenient_issubclass(field.type_, bytes)
|
||||
and isinstance(value, sequence_types)
|
||||
):
|
||||
awaitables = [sub_value.read() for sub_value in value]
|
||||
contents = await asyncio.gather(*awaitables)
|
||||
value = sequence_shape_to_type[field.shape](contents)
|
||||
v_, errors_ = field.validate(value, values, loc=("body", field.alias))
|
||||
if isinstance(errors_, ErrorWrapper):
|
||||
errors.append(errors_)
|
||||
@@ -391,10 +411,14 @@ async def request_body_to_args(
|
||||
|
||||
|
||||
def get_schema_compatible_field(*, field: Field) -> Field:
|
||||
out_field = field
|
||||
if lenient_issubclass(field.type_, UploadFile):
|
||||
return Field(
|
||||
use_type: type = bytes
|
||||
if field.shape in sequence_shapes:
|
||||
use_type = List[bytes]
|
||||
out_field = Field(
|
||||
name=field.name,
|
||||
type_=bytes,
|
||||
type_=use_type,
|
||||
class_validators=field.class_validators,
|
||||
model_config=field.model_config,
|
||||
default=field.default,
|
||||
@@ -402,10 +426,10 @@ def get_schema_compatible_field(*, field: Field) -> Field:
|
||||
alias=field.alias,
|
||||
schema=field.schema,
|
||||
)
|
||||
return field
|
||||
return out_field
|
||||
|
||||
|
||||
def get_body_field(*, dependant: Dependant, name: str) -> Field:
|
||||
def get_body_field(*, dependant: Dependant, name: str) -> Optional[Field]:
|
||||
flat_dependant = get_flat_dependant(dependant)
|
||||
if not flat_dependant.body_params:
|
||||
return None
|
||||
@@ -430,8 +454,8 @@ def get_body_field(*, dependant: Dependant, name: str) -> Field:
|
||||
type_=BodyModel,
|
||||
default=None,
|
||||
required=required,
|
||||
model_config=UnconstrainedConfig,
|
||||
class_validators=[],
|
||||
model_config=BaseConfig,
|
||||
class_validators={},
|
||||
alias="body",
|
||||
schema=BodySchema(None),
|
||||
)
|
||||
|
||||
@@ -10,7 +10,7 @@ def jsonable_encoder(
|
||||
obj: Any,
|
||||
include: Set[str] = None,
|
||||
exclude: Set[str] = set(),
|
||||
by_alias: bool = False,
|
||||
by_alias: bool = True,
|
||||
include_none: bool = True,
|
||||
custom_encoder: dict = {},
|
||||
sqlalchemy_safe: bool = True,
|
||||
|
||||
@@ -31,7 +31,8 @@ def get_swagger_ui_html(*, openapi_url: str, title: str) -> HTMLResponse:
|
||||
SwaggerUIBundle.presets.apis,
|
||||
SwaggerUIBundle.SwaggerUIStandalonePreset
|
||||
],
|
||||
layout: "BaseLayout"
|
||||
layout: "BaseLayout",
|
||||
deepLinking: true
|
||||
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -124,7 +124,7 @@ def generate_operation_id(*, route: routing.APIRoute, method: str) -> str:
|
||||
def generate_operation_summary(*, route: routing.APIRoute, method: str) -> str:
|
||||
if route.summary:
|
||||
return route.summary
|
||||
return route.name.replace("_", " ").title() + " " + method.title()
|
||||
return route.name.replace("_", " ").title()
|
||||
|
||||
|
||||
def get_openapi_operation_metadata(*, route: routing.APIRoute, method: str) -> Dict:
|
||||
@@ -197,7 +197,7 @@ def get_openapi_path(
|
||||
] = response
|
||||
status_code = str(route.status_code)
|
||||
response_schema = {"type": "string"}
|
||||
if lenient_issubclass(route.content_type, JSONResponse):
|
||||
if lenient_issubclass(route.response_class, JSONResponse):
|
||||
if route.response_field:
|
||||
response_schema, _ = field_schema(
|
||||
route.response_field,
|
||||
@@ -211,7 +211,7 @@ def get_openapi_path(
|
||||
] = route.response_description
|
||||
operation.setdefault("responses", {}).setdefault(
|
||||
status_code, {}
|
||||
).setdefault("content", {}).setdefault(route.content_type.media_type, {})[
|
||||
).setdefault("content", {}).setdefault(route.response_class.media_type, {})[
|
||||
"schema"
|
||||
] = response_schema
|
||||
if all_route_params or route.body_field:
|
||||
|
||||
@@ -7,8 +7,7 @@ from fastapi import params
|
||||
from fastapi.dependencies.models import Dependant
|
||||
from fastapi.dependencies.utils import get_body_field, get_dependant, solve_dependencies
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from fastapi.utils import UnconstrainedConfig
|
||||
from pydantic import BaseModel, Schema
|
||||
from pydantic import BaseConfig, BaseModel, Schema
|
||||
from pydantic.error_wrappers import ErrorWrapper, ValidationError
|
||||
from pydantic.fields import Field
|
||||
from pydantic.utils import lenient_issubclass
|
||||
@@ -41,7 +40,7 @@ def get_app(
|
||||
dependant: Dependant,
|
||||
body_field: Field = None,
|
||||
status_code: int = 200,
|
||||
content_type: Type[Response] = JSONResponse,
|
||||
response_class: Type[Response] = JSONResponse,
|
||||
response_field: Field = None,
|
||||
) -> Callable:
|
||||
assert dependant.call is not None, "dependant.call must me a function"
|
||||
@@ -53,18 +52,13 @@ def get_app(
|
||||
body = None
|
||||
if body_field:
|
||||
if is_body_form:
|
||||
raw_body = await request.form()
|
||||
form_fields = {}
|
||||
for field, value in raw_body.items():
|
||||
form_fields[field] = value
|
||||
if form_fields:
|
||||
body = form_fields
|
||||
body = await request.form()
|
||||
else:
|
||||
body_bytes = await request.body()
|
||||
if body_bytes:
|
||||
body = await request.json()
|
||||
except Exception as e:
|
||||
logging.error("Error getting request body", e)
|
||||
logging.error(f"Error getting request body: {e}")
|
||||
raise HTTPException(
|
||||
status_code=400, detail="There was an error parsing the body"
|
||||
)
|
||||
@@ -89,7 +83,7 @@ def get_app(
|
||||
response_data = serialize_response(
|
||||
field=response_field, response=raw_response
|
||||
)
|
||||
return content_type(
|
||||
return response_class(
|
||||
content=response_data,
|
||||
status_code=status_code,
|
||||
background=background_tasks,
|
||||
@@ -116,7 +110,7 @@ class APIRoute(routing.Route):
|
||||
methods: List[str] = None,
|
||||
operation_id: str = None,
|
||||
include_in_schema: bool = True,
|
||||
content_type: Type[Response] = JSONResponse,
|
||||
response_class: Type[Response] = JSONResponse,
|
||||
) -> None:
|
||||
assert path.startswith("/"), "Routed paths must always start with '/'"
|
||||
self.path = path
|
||||
@@ -125,16 +119,16 @@ class APIRoute(routing.Route):
|
||||
self.response_model = response_model
|
||||
if self.response_model:
|
||||
assert lenient_issubclass(
|
||||
content_type, JSONResponse
|
||||
response_class, JSONResponse
|
||||
), "To declare a type the response must be a JSON response"
|
||||
response_name = "Response_" + self.name
|
||||
self.response_field: Optional[Field] = Field(
|
||||
name=response_name,
|
||||
type_=self.response_model,
|
||||
class_validators=[],
|
||||
class_validators={},
|
||||
default=None,
|
||||
required=False,
|
||||
model_config=UnconstrainedConfig,
|
||||
model_config=BaseConfig,
|
||||
schema=Schema(None),
|
||||
)
|
||||
else:
|
||||
@@ -142,7 +136,7 @@ class APIRoute(routing.Route):
|
||||
self.status_code = status_code
|
||||
self.tags = tags or []
|
||||
self.summary = summary
|
||||
self.description = description or self.endpoint.__doc__
|
||||
self.description = description or inspect.cleandoc(self.endpoint.__doc__ or "")
|
||||
self.response_description = response_description
|
||||
self.responses = responses or {}
|
||||
response_fields = {}
|
||||
@@ -160,7 +154,7 @@ class APIRoute(routing.Route):
|
||||
class_validators=None,
|
||||
default=None,
|
||||
required=False,
|
||||
model_config=UnconstrainedConfig,
|
||||
model_config=BaseConfig,
|
||||
schema=Schema(None),
|
||||
)
|
||||
response_fields[additional_status_code] = response_field
|
||||
@@ -174,7 +168,7 @@ class APIRoute(routing.Route):
|
||||
self.methods = methods
|
||||
self.operation_id = operation_id
|
||||
self.include_in_schema = include_in_schema
|
||||
self.content_type = content_type
|
||||
self.response_class = response_class
|
||||
|
||||
self.path_regex, self.path_format, self.param_convertors = compile_path(path)
|
||||
assert inspect.isfunction(endpoint) or inspect.ismethod(
|
||||
@@ -187,7 +181,7 @@ class APIRoute(routing.Route):
|
||||
dependant=self.dependant,
|
||||
body_field=self.body_field,
|
||||
status_code=self.status_code,
|
||||
content_type=self.content_type,
|
||||
response_class=self.response_class,
|
||||
response_field=self.response_field,
|
||||
)
|
||||
)
|
||||
@@ -210,7 +204,7 @@ class APIRouter(routing.Router):
|
||||
methods: List[str] = None,
|
||||
operation_id: str = None,
|
||||
include_in_schema: bool = True,
|
||||
content_type: Type[Response] = JSONResponse,
|
||||
response_class: Type[Response] = JSONResponse,
|
||||
name: str = None,
|
||||
) -> None:
|
||||
route = APIRoute(
|
||||
@@ -227,7 +221,7 @@ class APIRouter(routing.Router):
|
||||
methods=methods,
|
||||
operation_id=operation_id,
|
||||
include_in_schema=include_in_schema,
|
||||
content_type=content_type,
|
||||
response_class=response_class,
|
||||
name=name,
|
||||
)
|
||||
self.routes.append(route)
|
||||
@@ -247,7 +241,7 @@ class APIRouter(routing.Router):
|
||||
methods: List[str] = None,
|
||||
operation_id: str = None,
|
||||
include_in_schema: bool = True,
|
||||
content_type: Type[Response] = JSONResponse,
|
||||
response_class: Type[Response] = JSONResponse,
|
||||
name: str = None,
|
||||
) -> Callable:
|
||||
def decorator(func: Callable) -> Callable:
|
||||
@@ -265,7 +259,7 @@ class APIRouter(routing.Router):
|
||||
methods=methods,
|
||||
operation_id=operation_id,
|
||||
include_in_schema=include_in_schema,
|
||||
content_type=content_type,
|
||||
response_class=response_class,
|
||||
name=name,
|
||||
)
|
||||
return func
|
||||
@@ -304,7 +298,7 @@ class APIRouter(routing.Router):
|
||||
methods=route.methods,
|
||||
operation_id=route.operation_id,
|
||||
include_in_schema=route.include_in_schema,
|
||||
content_type=route.content_type,
|
||||
response_class=route.response_class,
|
||||
name=route.name,
|
||||
)
|
||||
elif isinstance(route, routing.Route):
|
||||
@@ -334,7 +328,7 @@ class APIRouter(routing.Router):
|
||||
deprecated: bool = None,
|
||||
operation_id: str = None,
|
||||
include_in_schema: bool = True,
|
||||
content_type: Type[Response] = JSONResponse,
|
||||
response_class: Type[Response] = JSONResponse,
|
||||
name: str = None,
|
||||
) -> Callable:
|
||||
return self.api_route(
|
||||
@@ -350,7 +344,7 @@ class APIRouter(routing.Router):
|
||||
methods=["GET"],
|
||||
operation_id=operation_id,
|
||||
include_in_schema=include_in_schema,
|
||||
content_type=content_type,
|
||||
response_class=response_class,
|
||||
name=name,
|
||||
)
|
||||
|
||||
@@ -368,7 +362,7 @@ class APIRouter(routing.Router):
|
||||
deprecated: bool = None,
|
||||
operation_id: str = None,
|
||||
include_in_schema: bool = True,
|
||||
content_type: Type[Response] = JSONResponse,
|
||||
response_class: Type[Response] = JSONResponse,
|
||||
name: str = None,
|
||||
) -> Callable:
|
||||
return self.api_route(
|
||||
@@ -384,7 +378,7 @@ class APIRouter(routing.Router):
|
||||
methods=["PUT"],
|
||||
operation_id=operation_id,
|
||||
include_in_schema=include_in_schema,
|
||||
content_type=content_type,
|
||||
response_class=response_class,
|
||||
name=name,
|
||||
)
|
||||
|
||||
@@ -402,7 +396,7 @@ class APIRouter(routing.Router):
|
||||
deprecated: bool = None,
|
||||
operation_id: str = None,
|
||||
include_in_schema: bool = True,
|
||||
content_type: Type[Response] = JSONResponse,
|
||||
response_class: Type[Response] = JSONResponse,
|
||||
name: str = None,
|
||||
) -> Callable:
|
||||
return self.api_route(
|
||||
@@ -418,7 +412,7 @@ class APIRouter(routing.Router):
|
||||
methods=["POST"],
|
||||
operation_id=operation_id,
|
||||
include_in_schema=include_in_schema,
|
||||
content_type=content_type,
|
||||
response_class=response_class,
|
||||
name=name,
|
||||
)
|
||||
|
||||
@@ -436,7 +430,7 @@ class APIRouter(routing.Router):
|
||||
deprecated: bool = None,
|
||||
operation_id: str = None,
|
||||
include_in_schema: bool = True,
|
||||
content_type: Type[Response] = JSONResponse,
|
||||
response_class: Type[Response] = JSONResponse,
|
||||
name: str = None,
|
||||
) -> Callable:
|
||||
return self.api_route(
|
||||
@@ -452,7 +446,7 @@ class APIRouter(routing.Router):
|
||||
methods=["DELETE"],
|
||||
operation_id=operation_id,
|
||||
include_in_schema=include_in_schema,
|
||||
content_type=content_type,
|
||||
response_class=response_class,
|
||||
name=name,
|
||||
)
|
||||
|
||||
@@ -470,7 +464,7 @@ class APIRouter(routing.Router):
|
||||
deprecated: bool = None,
|
||||
operation_id: str = None,
|
||||
include_in_schema: bool = True,
|
||||
content_type: Type[Response] = JSONResponse,
|
||||
response_class: Type[Response] = JSONResponse,
|
||||
name: str = None,
|
||||
) -> Callable:
|
||||
return self.api_route(
|
||||
@@ -486,7 +480,7 @@ class APIRouter(routing.Router):
|
||||
methods=["OPTIONS"],
|
||||
operation_id=operation_id,
|
||||
include_in_schema=include_in_schema,
|
||||
content_type=content_type,
|
||||
response_class=response_class,
|
||||
name=name,
|
||||
)
|
||||
|
||||
@@ -504,7 +498,7 @@ class APIRouter(routing.Router):
|
||||
deprecated: bool = None,
|
||||
operation_id: str = None,
|
||||
include_in_schema: bool = True,
|
||||
content_type: Type[Response] = JSONResponse,
|
||||
response_class: Type[Response] = JSONResponse,
|
||||
name: str = None,
|
||||
) -> Callable:
|
||||
return self.api_route(
|
||||
@@ -520,7 +514,7 @@ class APIRouter(routing.Router):
|
||||
methods=["HEAD"],
|
||||
operation_id=operation_id,
|
||||
include_in_schema=include_in_schema,
|
||||
content_type=content_type,
|
||||
response_class=response_class,
|
||||
name=name,
|
||||
)
|
||||
|
||||
@@ -538,7 +532,7 @@ class APIRouter(routing.Router):
|
||||
deprecated: bool = None,
|
||||
operation_id: str = None,
|
||||
include_in_schema: bool = True,
|
||||
content_type: Type[Response] = JSONResponse,
|
||||
response_class: Type[Response] = JSONResponse,
|
||||
name: str = None,
|
||||
) -> Callable:
|
||||
return self.api_route(
|
||||
@@ -554,7 +548,7 @@ class APIRouter(routing.Router):
|
||||
methods=["PATCH"],
|
||||
operation_id=operation_id,
|
||||
include_in_schema=include_in_schema,
|
||||
content_type=content_type,
|
||||
response_class=response_class,
|
||||
name=name,
|
||||
)
|
||||
|
||||
@@ -572,7 +566,7 @@ class APIRouter(routing.Router):
|
||||
deprecated: bool = None,
|
||||
operation_id: str = None,
|
||||
include_in_schema: bool = True,
|
||||
content_type: Type[Response] = JSONResponse,
|
||||
response_class: Type[Response] = JSONResponse,
|
||||
name: str = None,
|
||||
) -> Callable:
|
||||
return self.api_route(
|
||||
@@ -588,6 +582,6 @@ class APIRouter(routing.Router):
|
||||
methods=["TRACE"],
|
||||
operation_id=operation_id,
|
||||
include_in_schema=include_in_schema,
|
||||
content_type=content_type,
|
||||
response_class=response_class,
|
||||
name=name,
|
||||
)
|
||||
|
||||
@@ -2,6 +2,7 @@ import binascii
|
||||
from base64 import b64decode
|
||||
from typing import Optional
|
||||
|
||||
from fastapi.exceptions import HTTPException
|
||||
from fastapi.openapi.models import (
|
||||
HTTPBase as HTTPBaseModel,
|
||||
HTTPBearer as HTTPBearerModel,
|
||||
@@ -9,9 +10,8 @@ from fastapi.openapi.models import (
|
||||
from fastapi.security.base import SecurityBase
|
||||
from fastapi.security.utils import get_authorization_scheme_param
|
||||
from pydantic import BaseModel
|
||||
from starlette.exceptions import HTTPException
|
||||
from starlette.requests import Request
|
||||
from starlette.status import HTTP_403_FORBIDDEN
|
||||
from starlette.status import HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN
|
||||
|
||||
|
||||
class HTTPBasicCredentials(BaseModel):
|
||||
@@ -59,15 +59,21 @@ class HTTPBasic(HTTPBase):
|
||||
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
|
||||
# unauthorized_headers = {"WWW-Authenticate": "Basic"}
|
||||
if self.realm:
|
||||
unauthorized_headers = {"WWW-Authenticate": f'Basic realm="{self.realm}"'}
|
||||
else:
|
||||
unauthorized_headers = {"WWW-Authenticate": "Basic"}
|
||||
invalid_user_credentials_exc = HTTPException(
|
||||
status_code=HTTP_403_FORBIDDEN, detail="Invalid authentication credentials"
|
||||
status_code=HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid authentication credentials",
|
||||
headers=unauthorized_headers,
|
||||
)
|
||||
if not authorization or scheme.lower() != "basic":
|
||||
if self.auto_error:
|
||||
raise HTTPException(
|
||||
status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
|
||||
status_code=HTTP_401_UNAUTHORIZED,
|
||||
detail="Not authenticated",
|
||||
headers=unauthorized_headers,
|
||||
)
|
||||
else:
|
||||
return None
|
||||
@@ -87,7 +93,7 @@ class HTTPBearer(HTTPBase):
|
||||
*,
|
||||
bearerFormat: str = None,
|
||||
scheme_name: str = None,
|
||||
auto_error: bool = True
|
||||
auto_error: bool = True,
|
||||
):
|
||||
self.model = HTTPBearerModel(bearerFormat=bearerFormat)
|
||||
self.scheme_name = scheme_name or self.__class__.__name__
|
||||
|
||||
@@ -3,17 +3,12 @@ from typing import Any, Dict, List, Sequence, Set, Type
|
||||
|
||||
from fastapi import routing
|
||||
from fastapi.openapi.constants import REF_PREFIX
|
||||
from pydantic import BaseConfig, BaseModel
|
||||
from pydantic import BaseModel
|
||||
from pydantic.fields import Field
|
||||
from pydantic.schema import get_flat_models_from_fields, model_process_schema
|
||||
from starlette.routing import BaseRoute
|
||||
|
||||
|
||||
class UnconstrainedConfig(BaseConfig):
|
||||
min_anystr_length = None
|
||||
max_anystr_length = None
|
||||
|
||||
|
||||
def get_flat_models_from_routes(
|
||||
routes: Sequence[Type[BaseRoute]]
|
||||
) -> Set[Type[BaseModel]]:
|
||||
|
||||
11
mkdocs.yml
11
mkdocs.yml
@@ -43,8 +43,11 @@ nav:
|
||||
- Handling Errors: 'tutorial/handling-errors.md'
|
||||
- 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'
|
||||
- Additional Status Codes: 'tutorial/additional-status-codes.md'
|
||||
- JSON compatible encoder: 'tutorial/encoder.md'
|
||||
- Return a Response directly: 'tutorial/response-directly.md'
|
||||
- Custom Response Class: 'tutorial/custom-response.md'
|
||||
- Additional Responses in OpenAPI: 'tutorial/additional-responses.md'
|
||||
- Dependencies:
|
||||
- First Steps: 'tutorial/dependencies/first-steps.md'
|
||||
- Classes as Dependencies: 'tutorial/dependencies/classes-as-dependencies.md'
|
||||
@@ -57,6 +60,9 @@ nav:
|
||||
- Simple OAuth2 with Password and Bearer: 'tutorial/security/simple-oauth2.md'
|
||||
- OAuth2 with Password (and hashing), Bearer with JWT tokens: 'tutorial/security/oauth2-jwt.md'
|
||||
- OAuth2 scopes: 'tutorial/security/oauth2-scopes.md'
|
||||
- HTTP Basic Auth: 'tutorial/security/http-basic-auth.md'
|
||||
- Middleware: 'tutorial/middleware.md'
|
||||
- CORS (Cross-Origin Resource Sharing): 'tutorial/cors.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'
|
||||
@@ -68,6 +74,7 @@ nav:
|
||||
- GraphQL: 'tutorial/graphql.md'
|
||||
- WebSockets: 'tutorial/websockets.md'
|
||||
- 'Events: startup - shutdown': 'tutorial/events.md'
|
||||
- Testing: 'tutorial/testing.md'
|
||||
- Debugging: 'tutorial/debugging.md'
|
||||
- Extending OpenAPI: 'tutorial/extending-openapi.md'
|
||||
- Concurrency and async / await: 'async.md'
|
||||
|
||||
@@ -20,7 +20,7 @@ classifiers = [
|
||||
]
|
||||
requires = [
|
||||
"starlette ==0.11.1",
|
||||
"pydantic >=0.17,<=0.21.0"
|
||||
"pydantic >=0.17,<=0.23.0"
|
||||
]
|
||||
description-file = "README.md"
|
||||
requires-python = ">=3.6"
|
||||
|
||||
7
scripts/deploy.sh
Executable file
7
scripts/deploy.sh
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
bash scripts/publish.sh
|
||||
|
||||
bash scripts/trigger-docker.sh
|
||||
5
scripts/publish.sh
Executable file
5
scripts/publish.sh
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
flit publish
|
||||
@@ -41,7 +41,7 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Foo Post",
|
||||
"summary": "Foo",
|
||||
"operationId": "foo_foo_post",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
|
||||
@@ -30,7 +30,7 @@ openapi_schema = {
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
}
|
||||
},
|
||||
"summary": "Read Item Get",
|
||||
"summary": "Read Item",
|
||||
"operationId": "read_item_items__get",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ openapi_schema = {
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
},
|
||||
"summary": "A Get",
|
||||
"summary": "A",
|
||||
"operationId": "a_a_get",
|
||||
}
|
||||
},
|
||||
@@ -48,7 +48,7 @@ openapi_schema = {
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
},
|
||||
"summary": "B Get",
|
||||
"summary": "B",
|
||||
"operationId": "b_b_get",
|
||||
}
|
||||
},
|
||||
@@ -61,7 +61,7 @@ openapi_schema = {
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
},
|
||||
"summary": "C Get",
|
||||
"summary": "C",
|
||||
"operationId": "c_c_get",
|
||||
}
|
||||
},
|
||||
|
||||
@@ -17,7 +17,7 @@ openapi_schema = {
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
}
|
||||
},
|
||||
"summary": "Non Operation Get",
|
||||
"summary": "Non Operation",
|
||||
"operationId": "non_operation_api_route_get",
|
||||
}
|
||||
},
|
||||
@@ -29,7 +29,7 @@ openapi_schema = {
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
}
|
||||
},
|
||||
"summary": "Non Decorated Route Get",
|
||||
"summary": "Non Decorated Route",
|
||||
"operationId": "non_decorated_route_non_decorated_route_get",
|
||||
}
|
||||
},
|
||||
@@ -41,7 +41,7 @@ openapi_schema = {
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
}
|
||||
},
|
||||
"summary": "Get Text Get",
|
||||
"summary": "Get Text",
|
||||
"operationId": "get_text_text_get",
|
||||
}
|
||||
},
|
||||
@@ -63,7 +63,7 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Get Id Get",
|
||||
"summary": "Get Id",
|
||||
"operationId": "get_id_path__item_id__get",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -93,7 +93,7 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Get Str Id Get",
|
||||
"summary": "Get Str Id",
|
||||
"operationId": "get_str_id_path_str__item_id__get",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -123,7 +123,7 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Get Int Id Get",
|
||||
"summary": "Get Int Id",
|
||||
"operationId": "get_int_id_path_int__item_id__get",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -153,7 +153,7 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Get Float Id Get",
|
||||
"summary": "Get Float Id",
|
||||
"operationId": "get_float_id_path_float__item_id__get",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -183,7 +183,7 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Get Bool Id Get",
|
||||
"summary": "Get Bool Id",
|
||||
"operationId": "get_bool_id_path_bool__item_id__get",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -213,7 +213,7 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Get Path Param Id Get",
|
||||
"summary": "Get Path Param Id",
|
||||
"operationId": "get_path_param_id_path_param__item_id__get",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -243,7 +243,7 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Get Path Param Required Id Get",
|
||||
"summary": "Get Path Param Required Id",
|
||||
"operationId": "get_path_param_required_id_path_param-required__item_id__get",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -273,7 +273,7 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Get Path Param Min Length Get",
|
||||
"summary": "Get Path Param Min Length",
|
||||
"operationId": "get_path_param_min_length_path_param-minlength__item_id__get",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -307,7 +307,7 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Get Path Param Max Length Get",
|
||||
"summary": "Get Path Param Max Length",
|
||||
"operationId": "get_path_param_max_length_path_param-maxlength__item_id__get",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -341,7 +341,7 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Get Path Param Min Max Length Get",
|
||||
"summary": "Get Path Param Min Max Length",
|
||||
"operationId": "get_path_param_min_max_length_path_param-min_maxlength__item_id__get",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -376,7 +376,7 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Get Path Param Gt Get",
|
||||
"summary": "Get Path Param Gt",
|
||||
"operationId": "get_path_param_gt_path_param-gt__item_id__get",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -410,7 +410,7 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Get Path Param Gt0 Get",
|
||||
"summary": "Get Path Param Gt0",
|
||||
"operationId": "get_path_param_gt0_path_param-gt0__item_id__get",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -444,7 +444,7 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Get Path Param Ge Get",
|
||||
"summary": "Get Path Param Ge",
|
||||
"operationId": "get_path_param_ge_path_param-ge__item_id__get",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -478,7 +478,7 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Get Path Param Lt Get",
|
||||
"summary": "Get Path Param Lt",
|
||||
"operationId": "get_path_param_lt_path_param-lt__item_id__get",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -512,7 +512,7 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Get Path Param Lt0 Get",
|
||||
"summary": "Get Path Param Lt0",
|
||||
"operationId": "get_path_param_lt0_path_param-lt0__item_id__get",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -546,7 +546,7 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Get Path Param Le Get",
|
||||
"summary": "Get Path Param Le",
|
||||
"operationId": "get_path_param_le_path_param-le__item_id__get",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -580,7 +580,7 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Get Path Param Lt Gt Get",
|
||||
"summary": "Get Path Param Lt Gt",
|
||||
"operationId": "get_path_param_lt_gt_path_param-lt-gt__item_id__get",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -615,7 +615,7 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Get Path Param Le Ge Get",
|
||||
"summary": "Get Path Param Le Ge",
|
||||
"operationId": "get_path_param_le_ge_path_param-le-ge__item_id__get",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -650,7 +650,7 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Get Path Param Lt Int Get",
|
||||
"summary": "Get Path Param Lt Int",
|
||||
"operationId": "get_path_param_lt_int_path_param-lt-int__item_id__get",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -684,7 +684,7 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Get Path Param Gt Int Get",
|
||||
"summary": "Get Path Param Gt Int",
|
||||
"operationId": "get_path_param_gt_int_path_param-gt-int__item_id__get",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -718,7 +718,7 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Get Path Param Le Int Get",
|
||||
"summary": "Get Path Param Le Int",
|
||||
"operationId": "get_path_param_le_int_path_param-le-int__item_id__get",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -752,7 +752,7 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Get Path Param Ge Int Get",
|
||||
"summary": "Get Path Param Ge Int",
|
||||
"operationId": "get_path_param_ge_int_path_param-ge-int__item_id__get",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -786,7 +786,7 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Get Path Param Lt Gt Int Get",
|
||||
"summary": "Get Path Param Lt Gt Int",
|
||||
"operationId": "get_path_param_lt_gt_int_path_param-lt-gt-int__item_id__get",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -821,7 +821,7 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Get Path Param Le Ge Int Get",
|
||||
"summary": "Get Path Param Le Ge Int",
|
||||
"operationId": "get_path_param_le_ge_int_path_param-le-ge-int__item_id__get",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -856,7 +856,7 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Get Query Get",
|
||||
"summary": "Get Query",
|
||||
"operationId": "get_query_query_get",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -886,7 +886,7 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Get Query Optional Get",
|
||||
"summary": "Get Query Optional",
|
||||
"operationId": "get_query_optional_query_optional_get",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -916,7 +916,7 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Get Query Type Get",
|
||||
"summary": "Get Query Type",
|
||||
"operationId": "get_query_type_query_int_get",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -946,7 +946,7 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Get Query Type Optional Get",
|
||||
"summary": "Get Query Type Optional",
|
||||
"operationId": "get_query_type_optional_query_int_optional_get",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -976,7 +976,7 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Get Query Type Optional Get",
|
||||
"summary": "Get Query Type Optional",
|
||||
"operationId": "get_query_type_optional_query_int_default_get",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -1006,7 +1006,7 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Get Query Param Get",
|
||||
"summary": "Get Query Param",
|
||||
"operationId": "get_query_param_query_param_get",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -1036,7 +1036,7 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Get Query Param Required Get",
|
||||
"summary": "Get Query Param Required",
|
||||
"operationId": "get_query_param_required_query_param-required_get",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -1066,7 +1066,7 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Get Query Param Required Type Get",
|
||||
"summary": "Get Query Param Required Type",
|
||||
"operationId": "get_query_param_required_type_query_param-required_int_get",
|
||||
"parameters": [
|
||||
{
|
||||
|
||||
@@ -72,7 +72,7 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Get Items Get",
|
||||
"summary": "Get Items",
|
||||
"operationId": "get_items_items__item_id__get",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -100,7 +100,7 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Delete Item Delete",
|
||||
"summary": "Delete Item",
|
||||
"operationId": "delete_item_items__item_id__delete",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -136,7 +136,7 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Options Item Options",
|
||||
"summary": "Options Item",
|
||||
"operationId": "options_item_items__item_id__options",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -164,7 +164,7 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Head Item Head",
|
||||
"summary": "Head Item",
|
||||
"operationId": "head_item_items__item_id__head",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -192,7 +192,7 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Patch Item Patch",
|
||||
"summary": "Patch Item",
|
||||
"operationId": "patch_item_items__item_id__patch",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -228,7 +228,7 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Trace Item Trace",
|
||||
"summary": "Trace Item",
|
||||
"operationId": "trace_item_items__item_id__trace",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -258,7 +258,7 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Get Not Decorated Get",
|
||||
"summary": "Get Not Decorated",
|
||||
"operationId": "get_not_decorated_items-not-decorated__item_id__get",
|
||||
"parameters": [
|
||||
{
|
||||
|
||||
@@ -3,7 +3,7 @@ from enum import Enum
|
||||
|
||||
import pytest
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from pydantic import BaseModel
|
||||
from pydantic import BaseModel, Schema, ValidationError
|
||||
|
||||
|
||||
class Person:
|
||||
@@ -59,6 +59,10 @@ class ModelWithConfig(BaseModel):
|
||||
use_enum_values = True
|
||||
|
||||
|
||||
class ModelWithAlias(BaseModel):
|
||||
foo: str = Schema(..., alias="Foo")
|
||||
|
||||
|
||||
def test_encode_class():
|
||||
person = Person(name="Foo")
|
||||
pet = Pet(owner=person, name="Firulais")
|
||||
@@ -85,3 +89,13 @@ def test_encode_custom_json_encoders_model():
|
||||
def test_encode_model_with_config():
|
||||
model = ModelWithConfig(role=RoleEnum.admin)
|
||||
assert jsonable_encoder(model) == {"role": "admin"}
|
||||
|
||||
|
||||
def test_encode_model_with_alias_raises():
|
||||
with pytest.raises(ValidationError):
|
||||
model = ModelWithAlias(foo="Bar")
|
||||
|
||||
|
||||
def test_encode_model_with_alias():
|
||||
model = ModelWithAlias(Foo="Bar")
|
||||
assert jsonable_encoder(model) == {"Foo": "Bar"}
|
||||
|
||||
@@ -42,7 +42,7 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Save Item No Body Post",
|
||||
"summary": "Save Item No Body",
|
||||
"operationId": "save_item_no_body_items__post",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
|
||||
@@ -36,7 +36,7 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Read Items Get",
|
||||
"summary": "Read Items",
|
||||
"operationId": "read_items_items__get",
|
||||
"parameters": [
|
||||
{
|
||||
|
||||
@@ -34,7 +34,7 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Save Item No Body Put",
|
||||
"summary": "Save Item No Body",
|
||||
"operationId": "save_item_no_body_items__item_id__put",
|
||||
"parameters": [
|
||||
{
|
||||
|
||||
@@ -36,7 +36,7 @@ openapi_schema = {
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
}
|
||||
},
|
||||
"summary": "Read Current User Get",
|
||||
"summary": "Read Current User",
|
||||
"operationId": "read_current_user_users_me_get",
|
||||
"security": [{"APIKeyCookie": []}],
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ openapi_schema = {
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
}
|
||||
},
|
||||
"summary": "Read Current User Get",
|
||||
"summary": "Read Current User",
|
||||
"operationId": "read_current_user_users_me_get",
|
||||
"security": [{"APIKeyCookie": []}],
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ openapi_schema = {
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
}
|
||||
},
|
||||
"summary": "Read Current User Get",
|
||||
"summary": "Read Current User",
|
||||
"operationId": "read_current_user_users_me_get",
|
||||
"security": [{"APIKeyHeader": []}],
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ openapi_schema = {
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
}
|
||||
},
|
||||
"summary": "Read Current User Get",
|
||||
"summary": "Read Current User",
|
||||
"operationId": "read_current_user_users_me_get",
|
||||
"security": [{"APIKeyHeader": []}],
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ openapi_schema = {
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
}
|
||||
},
|
||||
"summary": "Read Current User Get",
|
||||
"summary": "Read Current User",
|
||||
"operationId": "read_current_user_users_me_get",
|
||||
"security": [{"APIKeyQuery": []}],
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ openapi_schema = {
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
}
|
||||
},
|
||||
"summary": "Read Current User Get",
|
||||
"summary": "Read Current User",
|
||||
"operationId": "read_current_user_users_me_get",
|
||||
"security": [{"APIKeyQuery": []}],
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ openapi_schema = {
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
}
|
||||
},
|
||||
"summary": "Read Current User Get",
|
||||
"summary": "Read Current User",
|
||||
"operationId": "read_current_user_users_me_get",
|
||||
"security": [{"HTTPBase": []}],
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ openapi_schema = {
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
}
|
||||
},
|
||||
"summary": "Read Current User Get",
|
||||
"summary": "Read Current User",
|
||||
"operationId": "read_current_user_users_me_get",
|
||||
"security": [{"HTTPBase": []}],
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ openapi_schema = {
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
}
|
||||
},
|
||||
"summary": "Read Current User Get",
|
||||
"summary": "Read Current User",
|
||||
"operationId": "read_current_user_users_me_get",
|
||||
"security": [{"HTTPBasic": []}],
|
||||
}
|
||||
@@ -67,7 +67,8 @@ def test_security_http_basic_invalid_credentials():
|
||||
response = client.get(
|
||||
"/users/me", headers={"Authorization": "Basic notabase64token"}
|
||||
)
|
||||
assert response.status_code == 403
|
||||
assert response.status_code == 401
|
||||
assert response.headers["WWW-Authenticate"] == "Basic"
|
||||
assert response.json() == {"detail": "Invalid authentication credentials"}
|
||||
|
||||
|
||||
@@ -75,5 +76,6 @@ def test_security_http_basic_non_basic_credentials():
|
||||
payload = b64encode(b"johnsecret").decode("ascii")
|
||||
auth_header = f"Basic {payload}"
|
||||
response = client.get("/users/me", headers={"Authorization": auth_header})
|
||||
assert response.status_code == 403
|
||||
assert response.status_code == 401
|
||||
assert response.headers["WWW-Authenticate"] == "Basic"
|
||||
assert response.json() == {"detail": "Invalid authentication credentials"}
|
||||
|
||||
@@ -7,7 +7,7 @@ from starlette.testclient import TestClient
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
security = HTTPBasic()
|
||||
security = HTTPBasic(realm="simple")
|
||||
|
||||
|
||||
@app.get("/users/me")
|
||||
@@ -29,7 +29,7 @@ openapi_schema = {
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
}
|
||||
},
|
||||
"summary": "Read Current User Get",
|
||||
"summary": "Read Current User",
|
||||
"operationId": "read_current_user_users_me_get",
|
||||
"security": [{"HTTPBasic": []}],
|
||||
}
|
||||
@@ -56,15 +56,17 @@ def test_security_http_basic():
|
||||
|
||||
def test_security_http_basic_no_credentials():
|
||||
response = client.get("/users/me")
|
||||
assert response.status_code == 403
|
||||
assert response.json() == {"detail": "Not authenticated"}
|
||||
assert response.status_code == 401
|
||||
assert response.headers["WWW-Authenticate"] == 'Basic realm="simple"'
|
||||
|
||||
|
||||
def test_security_http_basic_invalid_credentials():
|
||||
response = client.get(
|
||||
"/users/me", headers={"Authorization": "Basic notabase64token"}
|
||||
)
|
||||
assert response.status_code == 403
|
||||
assert response.status_code == 401
|
||||
assert response.headers["WWW-Authenticate"] == 'Basic realm="simple"'
|
||||
assert response.json() == {"detail": "Invalid authentication credentials"}
|
||||
|
||||
|
||||
@@ -72,5 +74,6 @@ def test_security_http_basic_non_basic_credentials():
|
||||
payload = b64encode(b"johnsecret").decode("ascii")
|
||||
auth_header = f"Basic {payload}"
|
||||
response = client.get("/users/me", headers={"Authorization": auth_header})
|
||||
assert response.status_code == 403
|
||||
assert response.status_code == 401
|
||||
assert response.headers["WWW-Authenticate"] == 'Basic realm="simple"'
|
||||
assert response.json() == {"detail": "Invalid authentication credentials"}
|
||||
@@ -26,7 +26,7 @@ openapi_schema = {
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
}
|
||||
},
|
||||
"summary": "Read Current User Get",
|
||||
"summary": "Read Current User",
|
||||
"operationId": "read_current_user_users_me_get",
|
||||
"security": [{"HTTPBearer": []}],
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ openapi_schema = {
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
}
|
||||
},
|
||||
"summary": "Read Current User Get",
|
||||
"summary": "Read Current User",
|
||||
"operationId": "read_current_user_users_me_get",
|
||||
"security": [{"HTTPBearer": []}],
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ openapi_schema = {
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
}
|
||||
},
|
||||
"summary": "Read Current User Get",
|
||||
"summary": "Read Current User",
|
||||
"operationId": "read_current_user_users_me_get",
|
||||
"security": [{"HTTPDigest": []}],
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ openapi_schema = {
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
}
|
||||
},
|
||||
"summary": "Read Current User Get",
|
||||
"summary": "Read Current User",
|
||||
"operationId": "read_current_user_users_me_get",
|
||||
"security": [{"HTTPDigest": []}],
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Read Current User Post",
|
||||
"summary": "Read Current User",
|
||||
"operationId": "read_current_user_login_post",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
@@ -82,7 +82,7 @@ openapi_schema = {
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
}
|
||||
},
|
||||
"summary": "Read Current User Get",
|
||||
"summary": "Read Current User",
|
||||
"operationId": "read_current_user_users_me_get",
|
||||
"security": [{"OAuth2": []}],
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Read Current User Post",
|
||||
"summary": "Read Current User",
|
||||
"operationId": "read_current_user_login_post",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
@@ -89,7 +89,7 @@ openapi_schema = {
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
}
|
||||
},
|
||||
"summary": "Read Current User Get",
|
||||
"summary": "Read Current User",
|
||||
"operationId": "read_current_user_users_me_get",
|
||||
"security": [{"OAuth2": []}],
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ openapi_schema = {
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
}
|
||||
},
|
||||
"summary": "Read Items Get",
|
||||
"summary": "Read Items",
|
||||
"operationId": "read_items_items__get",
|
||||
"security": [{"OAuth2PasswordBearer": []}],
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ openapi_schema = {
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
}
|
||||
},
|
||||
"summary": "Read Current User Get",
|
||||
"summary": "Read Current User",
|
||||
"operationId": "read_current_user_users_me_get",
|
||||
"security": [{"OpenIdConnect": []}],
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ openapi_schema = {
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
}
|
||||
},
|
||||
"summary": "Read Current User Get",
|
||||
"summary": "Read Current User",
|
||||
"operationId": "read_current_user_users_me_get",
|
||||
"security": [{"OpenIdConnect": []}],
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Create Item Get",
|
||||
"summary": "Create Item",
|
||||
"operationId": "create_item_items__item_id__get",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -79,7 +79,7 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Create Item Get",
|
||||
"summary": "Create Item",
|
||||
"operationId": "create_item_starlette-items__item_id__get",
|
||||
"parameters": [
|
||||
{
|
||||
|
||||
@@ -38,7 +38,7 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Read Item Get",
|
||||
"summary": "Read Item",
|
||||
"operationId": "read_item_items__item_id__get",
|
||||
"parameters": [
|
||||
{
|
||||
|
||||
@@ -34,7 +34,7 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Read Item Get",
|
||||
"summary": "Read Item",
|
||||
"operationId": "read_item_items__item_id__get",
|
||||
"parameters": [
|
||||
{
|
||||
|
||||
@@ -39,7 +39,7 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Read Item Get",
|
||||
"summary": "Read Item",
|
||||
"operationId": "read_item_items__item_id__get",
|
||||
"parameters": [
|
||||
{
|
||||
|
||||
@@ -37,7 +37,7 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Read Item Get",
|
||||
"summary": "Read Item",
|
||||
"operationId": "read_item_items__item_id__get",
|
||||
"parameters": [
|
||||
{
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
from starlette.testclient import TestClient
|
||||
|
||||
from additional_status_codes.tutorial001 import app
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
def test_update():
|
||||
response = client.put("/items/foo", json={"name": "Wrestlers"})
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"name": "Wrestlers", "size": None}
|
||||
|
||||
|
||||
def test_create():
|
||||
response = client.put("/items/red", json={"name": "Chillies"})
|
||||
assert response.status_code == 201
|
||||
assert response.json() == {"name": "Chillies", "size": None}
|
||||
@@ -20,7 +20,7 @@ openapi_schema = {
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
}
|
||||
},
|
||||
"summary": "Read Items Get",
|
||||
"summary": "Read Items",
|
||||
"operationId": "read_items_items__get",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ openapi_schema = {
|
||||
},
|
||||
}
|
||||
},
|
||||
"summary": "Read Notes Get",
|
||||
"summary": "Read Notes",
|
||||
"operationId": "read_notes_notes__get",
|
||||
},
|
||||
"post": {
|
||||
@@ -46,7 +46,7 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Create Note Post",
|
||||
"summary": "Create Note",
|
||||
"operationId": "create_note_notes__post",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
|
||||
@@ -18,7 +18,7 @@ openapi_schema = {
|
||||
}
|
||||
},
|
||||
"tags": ["users"],
|
||||
"summary": "Read Users Get",
|
||||
"summary": "Read Users",
|
||||
"operationId": "read_users_users__get",
|
||||
}
|
||||
},
|
||||
@@ -31,7 +31,7 @@ openapi_schema = {
|
||||
}
|
||||
},
|
||||
"tags": ["users"],
|
||||
"summary": "Read User Me Get",
|
||||
"summary": "Read User Me",
|
||||
"operationId": "read_user_me_users_me_get",
|
||||
}
|
||||
},
|
||||
@@ -54,7 +54,7 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
"tags": ["users"],
|
||||
"summary": "Read User Get",
|
||||
"summary": "Read User",
|
||||
"operationId": "read_user_users__username__get",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -76,7 +76,7 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
"tags": ["items"],
|
||||
"summary": "Read Items Get",
|
||||
"summary": "Read Items",
|
||||
"operationId": "read_items_items__get",
|
||||
}
|
||||
},
|
||||
@@ -100,7 +100,7 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
"tags": ["items"],
|
||||
"summary": "Read Item Get",
|
||||
"summary": "Read Item",
|
||||
"operationId": "read_item_items__item_id__get",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -131,7 +131,7 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
"tags": ["custom", "items"],
|
||||
"summary": "Update Item Put",
|
||||
"summary": "Update Item",
|
||||
"operationId": "update_item_items__item_id__put",
|
||||
"parameters": [
|
||||
{
|
||||
|
||||
@@ -27,7 +27,7 @@ openapi_schema = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Create Item Post",
|
||||
"summary": "Create Item",
|
||||
"operationId": "create_item_items__post",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user