Compare commits

...

34 Commits

Author SHA1 Message Date
Sebastián Ramírez
26e3dffb37 🚀 Deploy when tagged using Python 3.6 2019-04-20 22:16:07 +04:00
Sebastián Ramírez
aa7b4bd101 🔖 0.17.0 2019-04-20 22:12:55 +04:00
Sebastián Ramírez
ffc4c716c0 🚀 Make Flit publish from CI (#170) 2019-04-20 22:09:35 +04:00
Sebastián Ramírez
ef7b6e8eaf 📝 Update Release Notes 2019-04-20 21:15:03 +04:00
Sebastián Ramírez
596243f4a5 Add docs about CORS (#169) 2019-04-20 21:13:01 +04:00
Sebastián Ramírez
766bf1c5aa 📝 Update release notes 2019-04-20 20:31:44 +04:00
Sebastián Ramírez
9e748dbca4 By default, encode by alias (#168) 2019-04-20 20:29:54 +04:00
Sebastián Ramírez
cefe6cf92c 🔖 Release version 0.16.0 2019-04-16 23:28:13 +04:00
Sebastián Ramírez
be3953499f 📝 Update release notes 2019-04-16 23:27:25 +04:00
Sebastián Ramírez
546d233dec ♻️ Update Pydantic usage, types, values, minor structure changes (#164) 2019-04-16 23:26:09 +04:00
Sebastián Ramírez
61dd36a945 Upgrade docstring Markdown parsing (#163)
*  Upgrade docstring Markdown parsing

* 📝 Update release notes
2019-04-16 22:49:18 +04:00
Sebastián Ramírez
27f9d55c3e 📝 Update release notes 2019-04-16 22:43:59 +04:00
euri10
906cc60f65 ⬆️ Upgrade Pydantic to 0.23 (#160)
* Add websocket to APIRouter

* Upgrade pydantic to v0.23.0

* Forgot pyproject.toml

* ⬆️ Upgrade some Pipfile.lock dependencies
2019-04-16 22:42:00 +04:00
Sebastián Ramírez
69afaf256f 📝 Update release notes 2019-04-16 22:21:32 +04:00
Daniel Michaels
4ab349a2a8 ✏️ fixed small typo /tutorial/extra-models.md (#159) 2019-04-16 22:20:03 +04:00
Sebastián Ramírez
9c258107b4 📝 Update release notes 2019-04-16 22:18:42 +04:00
hayata-yamamoto
29a4f90bcd 📝 fix URL examples in Tutorial: Query Parameters (#157)
* modify tutorial

* modify item_id
2019-04-16 22:16:16 +04:00
Sebastián Ramírez
361fd00777 📝 Add note about Swagger UI and multi-part uploads 2019-04-14 22:24:31 +04:00
Sebastián Ramírez
4c3cf31730 🔖 Release 0.15.0, multi-file uploads 2019-04-14 22:14:20 +04:00
Sebastián Ramírez
aad6b123f7 Add support for multi-file uploads (#158) 2019-04-14 22:12:14 +04:00
Sebastián Ramírez
e40e87c662 📝 Use same link in benchmarks as in index 2019-04-12 22:56:09 +04:00
Sebastián Ramírez
84de980977 Add docs about responses with additional status codes (#156)
*  Add docs about responses with additional status codes

* 📝 Update docs, link to documenting additional responses
2019-04-12 22:43:21 +04:00
Sebastián Ramírez
08484603ee 🔖 Release 0.14.0 2019-04-12 21:56:22 +04:00
Sebastián Ramírez
ab6dd60997 📝 Add note on installing and running pytest 2019-04-12 21:45:19 +04:00
Sebastián Ramírez
c9ef7bd6dc 📝 Update release notes 2019-04-12 21:27:24 +04:00
Sebastián Ramírez
88ece95a30 🎨 Improve automatic naming of path operations in API docs (#155)
* 🎨 Improve operation summary naming

* 📝 Update names in README

* 🚚 Improve names of security tutorial
2019-04-12 21:25:26 +04:00
Sebastián Ramírez
366c5db0bb 📝 Update release notes 2019-04-12 20:18:48 +04:00
Sebastián Ramírez
46e3811f8d Add testing docs and tests (#151)
* ✏️ Fix typo in security intro

*  Add testing docs and tests

* 🐛 Debug Travis coverage

* 🐛 Debug Travis coverage, report XML

* 💚 Make Travis/Flit use same code install

*  Revert Travis/Codecov debugging changes
2019-04-12 20:15:05 +04:00
Sebastián Ramírez
613e211d20 📝 Update release notes 2019-04-12 11:51:26 +04:00
William Hayes
500f2b2ad4 Add deeplinking to Swagger UI (#148)
Update the Swagger UI docs to deep link to path operations to share them more easily.
2019-04-12 11:49:32 +04:00
Sebastián Ramírez
5cf7718657 📝 Update release notes 2019-04-12 11:40:56 +04:00
Sebastián Ramírez
727b656c8d ⬆️ Update development dependencies, Pipfile.lock (#150) 2019-04-12 11:39:06 +04:00
Sebastián Ramírez
cc7102e9b8 📝 Add opinions in main page 2019-04-10 22:27:28 +04:00
Sebastián Ramírez
5123915fe4 📝 Include Hug and Falcon in Alternatives/Inspiration 2019-04-10 22:09:50 +04:00
122 changed files with 1126 additions and 287 deletions

View File

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

View File

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

149
Pipfile.lock generated
View File

@@ -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:da819f7e00dc7d8c2f0585ec53aa49bae63b366f800506097db2e87972a4d44f"
],
"index": "pypi",
"version": "==0.2.0"
"version": "==0.2.1"
},
"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:06d4515a8f8965624d6db922093eb11e77fb8f9a9ebedd1c5d6df5a0fcd0a12c",
"sha256:6c0b1461695877ececd6921a6a330e4392790275c5d6e88fc8ea8261445468b1"
],
"version": "==0.5.0"
"version": "==0.6.0rc1"
},
"dnspython": {
"hashes": [
@@ -317,11 +321,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 +336,10 @@
},
"jinja2": {
"hashes": [
"sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd",
"sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4"
"sha256:065c4f02ebe7f7cf559e49ee5a95fb800a9e4528727aec6f24402a5374c65013",
"sha256:14dd6caf1527abb21f08f86c784eac40853ba93edb79552aa1e4b8aef1b61c7b"
],
"version": "==2.10"
"version": "==2.10.1"
},
"jsonschema": {
"hashes": [
@@ -383,10 +387,10 @@
},
"markdown": {
"hashes": [
"sha256:c00429bd503a47ec88d5e30a751e147dcb4c6889663cd3e2ba0afe858e009baa",
"sha256:d02e0f9b04c500cde6637c11ad7c72671f359b87b9fe924b2383649d8841db7c"
"sha256:fc4a6f69a656b8d858d7503bda633f4dd63c2d70cf80abdc6eafa64c4ae8c250",
"sha256:fe463ff51e679377e3624984c829022e2cfb3be5518726b06f608a07a3aad680"
],
"version": "==3.0.1"
"version": "==3.1"
},
"markdown-include": {
"hashes": [
@@ -452,27 +456,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:03261a04ace27250cf14f1301969e2cc36ad0343dd437e60007ce42f06ddbaff",
"sha256:6a7923e90dd8f8b8e762327e3a4dd814f0bc5581a627010f4e2ec72d906ada0f",
"sha256:6a7c2b16ff7dee1cd4a913641d6a8da0cd386be812524f41427ea25f8fe337a6",
"sha256:7480db0bc2bb473547c8d519ea549de9f9654170e6f5b34310094ebe5ee1c9dc",
"sha256:863774c896f2cdc62a0e2252e9ba7aaeb7da04c0296f47c82b125dce3437c580",
"sha256:9a990cf039891a83ee90f130256cc06d09c0793242ea38d0fe33fdc449507123",
"sha256:b03573d0cd8c051aa9ef7f47d564cf44bbc5e91e89a7a078b3ca904b3da8855a",
"sha256:b10b16d9aa7a01266f14260344fb25849ef0d508c512a916043f77987489aeff",
"sha256:b1eab82221c3cc94bf22152e701b3efc9d64f60fac4cab20969a0427e5a78261",
"sha256:e663d4424531dc99fb85c947df8a4a107442f53f20a4e0bcefaa1d21c87e1563",
"sha256:ffac30f3fa2c9e10118cbb0faa0b7da7edb6e3c24a4048a15446a1f3409884e3"
],
"index": "pypi",
"version": "==0.670"
"version": "==0.700"
},
"mypy-extensions": {
"hashes": [
@@ -497,10 +510,10 @@
},
"notebook": {
"hashes": [
"sha256:18a98858c0331fb65a60f2ebb6439f8c0c4defd14ca363731b6cabc7f61624b4",
"sha256:cc027a62be0f7756e0ef3d2d98458c4d7f4b3566449fb1a05891207f5bd9a1bf"
"sha256:573e0ae650c5d76b18b6e564ba6d21bf321d00847de1d215b418acb64f056eb8",
"sha256:f64fa6624d2323fbef6210a621817d6505a45d0d4a9367f1843b20a38a4666ee"
],
"version": "==5.7.6"
"version": "==5.7.8"
},
"pandocfilters": {
"hashes": [
@@ -510,18 +523,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 +615,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 +726,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": [
@@ -797,10 +810,10 @@
},
"uvicorn": {
"hashes": [
"sha256:d700b65169820fc260f39402b7f966c178691daaa40cb376cad99d7cd737f772"
"sha256:d96fb442d9ce9c1dba67360035161d392970b8e6b0ed797d2cefed24abfd78bc"
],
"index": "pypi",
"version": "==0.7.0b1"
"version": "==0.7.0b2"
},
"uvloop": {
"hashes": [

View File

@@ -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>
---
"*Im over the moon excited about **FastAPI**. Its 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}
```

View File

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

View File

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

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 85 KiB

View File

@@ -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>
---
"*Im over the moon excited about **FastAPI**. Its 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}
```

View File

@@ -1,5 +1,43 @@
## Next release
## 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`:

View 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)

View File

View File

@@ -0,0 +1,8 @@
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def read_main():
return {"msg": "Hello World"}

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

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

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

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

View 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=["*"],
)

View File

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

View File

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

View File

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

View File

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

View File

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

View 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)

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
"""FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
__version__ = "0.13.0"
__version__ = "0.17.0"
from starlette.background import BackgroundTasks

View File

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

View File

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

View File

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

View File

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

View File

@@ -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
@@ -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"
)
@@ -131,10 +125,10 @@ class APIRoute(routing.Route):
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

View File

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

View File

@@ -43,6 +43,7 @@ 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'
- Additional Status Codes: 'tutorial/additional-status-codes.md'
- Custom Response: 'tutorial/custom-response.md'
- Additional Responses: 'tutorial/additional-responses.md'
- Dependencies:
@@ -57,6 +58,7 @@ 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'
- 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 +70,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'

View File

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

@@ -0,0 +1,5 @@
#!/usr/bin/env bash
set -e
flit publish

View File

@@ -41,7 +41,7 @@ openapi_schema = {
},
},
},
"summary": "Foo Post",
"summary": "Foo",
"operationId": "foo_foo_post",
"requestBody": {
"content": {

View File

@@ -30,7 +30,7 @@ openapi_schema = {
"content": {"application/json": {"schema": {}}},
}
},
"summary": "Read Item Get",
"summary": "Read Item",
"operationId": "read_item_items__get",
}
}

View File

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

View File

@@ -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": [
{

View File

@@ -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": [
{

View File

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

View File

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

View File

@@ -36,7 +36,7 @@ openapi_schema = {
},
},
},
"summary": "Read Items Get",
"summary": "Read Items",
"operationId": "read_items_items__get",
"parameters": [
{

View File

@@ -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": [
{

View File

@@ -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": []}],
}

View File

@@ -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": []}],
}

View File

@@ -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": []}],
}

View File

@@ -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": []}],
}

View File

@@ -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": []}],
}

View File

@@ -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": []}],
}

View File

@@ -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": []}],
}

View File

@@ -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": []}],
}

View File

@@ -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": []}],
}

View File

@@ -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": []}],
}

View File

@@ -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": []}],
}

View File

@@ -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": []}],
}

View File

@@ -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": []}],
}

View File

@@ -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": []}],
}

View File

@@ -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": []}],
}

View File

@@ -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": []}],
}

View File

@@ -30,7 +30,7 @@ openapi_schema = {
"content": {"application/json": {"schema": {}}},
}
},
"summary": "Read Items Get",
"summary": "Read Items",
"operationId": "read_items_items__get",
"security": [{"OAuth2PasswordBearer": []}],
}

View File

@@ -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": []}],
}

View File

@@ -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": []}],
}

View File

@@ -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": [
{

View File

@@ -38,7 +38,7 @@ openapi_schema = {
},
},
},
"summary": "Read Item Get",
"summary": "Read Item",
"operationId": "read_item_items__item_id__get",
"parameters": [
{

View File

@@ -34,7 +34,7 @@ openapi_schema = {
},
},
},
"summary": "Read Item Get",
"summary": "Read Item",
"operationId": "read_item_items__item_id__get",
"parameters": [
{

View File

@@ -39,7 +39,7 @@ openapi_schema = {
},
},
},
"summary": "Read Item Get",
"summary": "Read Item",
"operationId": "read_item_items__item_id__get",
"parameters": [
{

View File

@@ -37,7 +37,7 @@ openapi_schema = {
},
},
},
"summary": "Read Item Get",
"summary": "Read Item",
"operationId": "read_item_items__item_id__get",
"parameters": [
{

View File

View File

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

View File

@@ -20,7 +20,7 @@ openapi_schema = {
"content": {"application/json": {"schema": {}}},
}
},
"summary": "Read Items Get",
"summary": "Read Items",
"operationId": "read_items_items__get",
}
}

View File

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

View File

@@ -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": [
{

View File

@@ -27,7 +27,7 @@ openapi_schema = {
},
},
},
"summary": "Create Item Post",
"summary": "Create Item",
"operationId": "create_item_items__post",
"requestBody": {
"content": {

View File

@@ -27,7 +27,7 @@ openapi_schema = {
},
},
},
"summary": "Update Item Put",
"summary": "Update Item",
"operationId": "update_item_items__item_id__put",
"parameters": [
{

View File

@@ -28,7 +28,7 @@ openapi_schema = {
},
},
},
"summary": "Update Item Put",
"summary": "Update Item",
"operationId": "update_item_items__item_id__put",
"parameters": [
{

View File

@@ -27,7 +27,7 @@ openapi_schema = {
},
},
},
"summary": "Read Items Get",
"summary": "Read Items",
"operationId": "read_items_items__get",
"parameters": [
{

View File

@@ -16,7 +16,7 @@ openapi_schema = {
"content": {"application/json": {"schema": {}}},
}
},
"summary": "Read Items Get",
"summary": "Read Items",
"operationId": "read_items_items__get",
}
}

View File

@@ -16,7 +16,7 @@ openapi_schema = {
"content": {"text/html": {"schema": {"type": "string"}}},
}
},
"summary": "Read Items Get",
"summary": "Read Items",
"operationId": "read_items_items__get",
}
}

View File

@@ -27,7 +27,7 @@ openapi_schema = {
},
},
},
"summary": "Read Items Get",
"summary": "Read Items",
"operationId": "read_items_items__get",
"parameters": [
{
@@ -69,7 +69,7 @@ openapi_schema = {
},
},
},
"summary": "Read Users Get",
"summary": "Read Users",
"operationId": "read_users_users__get",
"parameters": [
{

View File

@@ -27,7 +27,7 @@ openapi_schema = {
},
},
},
"summary": "Read Items Get",
"summary": "Read Items",
"operationId": "read_items_items__get",
"parameters": [
{

View File

@@ -24,8 +24,8 @@ openapi_schema = {
},
},
},
"summary": "Read Item Get",
"operationId": "read_item_items__item_id__get",
"summary": "Read Items",
"operationId": "read_items_items__item_id__get",
"parameters": [
{
"required": True,

View File

@@ -14,7 +14,7 @@ openapi_schema = {
"content": {"application/json": {"schema": {}}},
}
},
"summary": "Read Items Get",
"summary": "Read Items",
"operationId": "read_items_items__get",
}
}

View File

@@ -21,7 +21,7 @@ openapi_schema = {
"content": {"application/json": {"schema": {}}},
}
},
"summary": "Read Items Get",
"summary": "Read Items",
"operationId": "read_items_items__get",
}
}

View File

@@ -27,7 +27,7 @@ openapi_schema = {
},
},
},
"summary": "Read Items Put",
"summary": "Read Items",
"operationId": "read_items_items__item_id__put",
"parameters": [
{

View File

@@ -36,7 +36,7 @@ openapi_schema = {
},
},
},
"summary": "Read Item Get",
"summary": "Read Item",
"operationId": "read_item_items__item_id__get",
"parameters": [
{

View File

@@ -24,7 +24,7 @@ openapi_schema = {
},
}
},
"summary": "Read Items Get",
"summary": "Read Items",
"operationId": "read_items_items__get",
}
}

View File

@@ -17,7 +17,7 @@ openapi_schema = {
"content": {"application/json": {"schema": {}}},
}
},
"summary": "Root Get",
"summary": "Root",
"operationId": "root__get",
}
}

View File

@@ -26,8 +26,8 @@ openapi_schema = {
},
},
},
"summary": "Create Item Get",
"operationId": "create_item_items__item_id__get",
"summary": "Read Item",
"operationId": "read_item_items__item_id__get",
"parameters": [
{
"required": True,

View File

@@ -26,8 +26,8 @@ openapi_schema = {
},
},
},
"summary": "Create Item Header Get",
"operationId": "create_item_header_items-header__item_id__get",
"summary": "Read Item Header",
"operationId": "read_item_header_items-header__item_id__get",
"parameters": [
{
"required": True,

View File

@@ -28,7 +28,7 @@ openapi_schema = {
},
},
},
"summary": "Read Items Get",
"summary": "Read Items",
"operationId": "read_items_items__get",
"parameters": [
{

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