Compare commits

...

23 Commits

Author SHA1 Message Date
Sebastián Ramírez
783816a7e3 📝 Update Release Notes 2019-02-12 23:07:54 +04:00
Sebastián Ramírez
7863490c8c 🔖 Release after SQLAlchemy fix: 0.3.0 2019-02-12 23:06:05 +04:00
Sebastián Ramírez
955e9fcb31 Update fix SQLAlchemy support with ORM (#30)
 SQLAlchemy ORM support

Improved jsonable_encoder with SQLAlchemy support, tests running with SQLite, improved and updated SQL docs

*  Add SQLAlchemy to development dependencies (not required for using FastAPI)

*  Add sqlalchemy to testing dependencies (not required to use FastAPI)
2019-02-12 23:02:21 +04:00
Sebastián Ramírez
9484f939ed 🔖 Bump version, after fix, release 2019-02-12 21:46:35 +04:00
Sebastián Ramírez
9745a5d1ae 🐛 Fix jsonable_encoder for models with Config (#29)
but without json_encoders
2019-02-12 21:43:34 +04:00
Sebastián Ramírez
92c825be6a 🔖 Release 0.2.0 2019-02-08 16:09:48 +04:00
euri10
32438c85f6 Using pydantic custom encoders (#21)
Add support for Pydantic custom JSON encoders.
2019-02-08 16:06:19 +04:00
Sebastián Ramírez
02e53fde90 📝 Update release notes 2019-02-08 15:43:00 +04:00
Ken Kinder
902cdaf010 Fix typos (#24)
Fix typos in security section.
2019-02-08 15:41:13 +04:00
Sebastián Ramírez
04d77bb1c4 ✏️ Fix typos in index and alternatives 2019-02-08 15:39:26 +04:00
Sebastián Ramírez
6d9fc08a7e 🚀 Bump version and add Release Notes 2019-02-01 14:23:20 +04:00
euri10
5c9c088a2a Upgrade Starlette version (#17)
Upgrade Starlette version
2019-02-01 14:14:23 +04:00
Sebastián Ramírez
014c7df142 📝 Add Requests to inspiration 2019-01-24 22:31:33 +04:00
Sebastián Ramírez
9259dc228a 📈 Add Analytics to understand docs usage and improvements 2019-01-24 21:58:27 +04:00
Sebastián Ramírez
de431d948d Merge pull request #11 from tiangolo/fix-10
Pin versions of dependencies and bump version
2019-01-23 16:12:09 +01:00
Sebastián Ramírez
3d2c0993c1 📌 Pin versions of dependencies and bump version 2019-01-23 18:57:48 +04:00
Sebastián Ramírez
37bc3614fd 📝 Fix docs clarification about dict unwrapping
in extra-models and simple-oauth2 #7
2019-01-14 23:01:34 +04:00
Sebastián Ramírez
188da34529 📝 Clarify docs, alternatives, about APISpec OAI versions 2019-01-14 21:26:29 +04:00
Sebastián Ramírez
d692c28f52 📝 Add docs for bigger applications and APIRouter
and update tests to match docs
2019-01-14 19:23:38 +04:00
Sebastián Ramírez
8568862a19 📝 Add docs for response status codes 2019-01-14 17:30:55 +04:00
Sebastián Ramírez
dfa067b061 📝 Add screenshot to body-schema tutorial 2019-01-10 20:52:06 +04:00
Sebastián Ramírez
0d1b97fb94 📝 Add docs for deployment, with Docker, HTTPS, etc 2019-01-05 20:24:33 +04:00
Sebastián Ramírez
df1e754380 🔧 Update development environment dependencies 2019-01-05 18:49:50 +04:00
36 changed files with 1259 additions and 341 deletions

10
Pipfile
View File

@@ -5,10 +5,7 @@ verify_ssl = true
[dev-packages]
mypy = "*"
jedi = "*"
black = "*"
prospector = "*"
rope = "*"
jupyter = "*"
better-exceptions = "*"
pytest = "*"
@@ -22,10 +19,13 @@ markdown-include = "*"
autoflake = "*"
email-validator = "*"
ujson = "*"
flake8 = "*"
python-multipart = "*"
sqlalchemy = "*"
[packages]
starlette = "*"
pydantic = "*"
starlette = "==0.10.1"
pydantic = "==0.18.2"
[requires]
python_version = "3.6"

335
Pipfile.lock generated
View File

@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "a0f966a95cb84845ca4aad02c44fc0e7c5e2047fc44dcf19a95a4abaa02d0197"
"sha256": "37b34bb892b6b4dc0f7c941434d0e08199aa7a7ca83efb6294b89ace44168bba"
},
"pipfile-spec": 6,
"requires": {
@@ -26,18 +26,18 @@
},
"pydantic": {
"hashes": [
"sha256:51f879ca4b1d114c9f892737a0d65233251fb00fcd2b6da2be0d277b8ba7d28d",
"sha256:c90c9e5ae2a6a3f59efdcb1505ddfb18be6dc5648b536bf33782269460954cc2"
"sha256:9f023811b6cefd203c5fd8fd15a4152f04e79e531b8f676ab1244dfe06ce8024",
"sha256:edbb08b561feda505374c0f25e4b54466a0a0c702ed6b2efaabdc3890d1a82e7"
],
"index": "pypi",
"version": "==0.16.1"
"version": "==0.18.2"
},
"starlette": {
"hashes": [
"sha256:01f04283b49a8cb0c8921baa90dbafe47e953f0a265f6ebb38176038e4bd9bf8"
"sha256:7cc05c33d00db3b2ddfd7516a737544ed0a34c9dd0ced94076f29b581ce4f532"
],
"index": "pypi",
"version": "==0.9.9"
"version": "==0.10.1"
}
},
"develop": {
@@ -48,19 +48,12 @@
],
"version": "==1.4.3"
},
"astroid": {
"hashes": [
"sha256:292fa429e69d60e4161e7612cb7cc8fa3609e2e309f80c224d93a76d5e7b58be",
"sha256:c7013d119ec95eb626f7a2011f0b63d0c9a095df9ad06d8507b37084eada1a8d"
],
"version": "==2.0.4"
},
"atomicwrites": {
"hashes": [
"sha256:0312ad34fcad8fac3704d441f7b317e50af620823353ec657a53e981f92920c0",
"sha256:ec9ae8adaae229e4f8446952d204a3e4b5fdd2d099f9be3aaf556120135fb3ee"
"sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4",
"sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"
],
"version": "==1.2.1"
"version": "==1.3.0"
},
"attrs": {
"hashes": [
@@ -85,10 +78,11 @@
},
"better-exceptions": {
"hashes": [
"sha256:0a73efef96b48f867ea980227ac3b00d36a92754e6d316ad2ee472f136014580"
"sha256:bf79c87659bc849989d726bf0e4a2100edefe7eded112d201f54fe08467fdf63",
"sha256:c196cad849de615abb9f6eb67ca1b83f33b938818f0e2fe8fa157b22aeb7b992"
],
"index": "pypi",
"version": "==0.2.1"
"version": "==0.2.2"
},
"black": {
"hashes": [
@@ -100,10 +94,10 @@
},
"bleach": {
"hashes": [
"sha256:48d39675b80a75f6d1c3bdbffec791cf0bbbab665cf01e20da701c77de278718",
"sha256:73d26f018af5d5adcdabf5c1c974add4361a9c76af215fe32fdec8a6fc5fb9b9"
"sha256:213336e49e102af26d9cde77dd2d0397afabc5a6bf2fed985dc35b5d1e285a16",
"sha256:3fdf7f77adcf649c9911387df51254b813185e32b2c6619f690b593a617e19fa"
],
"version": "==3.0.2"
"version": "==3.1.0"
},
"certifi": {
"hashes": [
@@ -162,10 +156,10 @@
},
"decorator": {
"hashes": [
"sha256:2c51dff8ef3c447388fe5e4453d24a2bf128d3a4c32af3fabef1f01c6851ab82",
"sha256:c39efa13fbdeb4506c476c9b3babf6a718da943dab7811c206005a4a956c080c"
"sha256:33cd704aea07b4c28b3eb2c97d288a06918275dac0ecebdaf1bc8a48d98adb9e",
"sha256:cabb249f4710888a2fc0e13e9a16c343d932033718ff62e1e9bc93a9d3a9122b"
],
"version": "==4.3.0"
"version": "==4.3.2"
},
"defusedxml": {
"hashes": [
@@ -189,12 +183,6 @@
],
"version": "==0.14"
},
"dodgy": {
"hashes": [
"sha256:65e13cf878d7aff129f1461c13cb5fd1bb6dfe66bb5327e09379c3877763280c"
],
"version": "==0.1.9"
},
"email-validator": {
"hashes": [
"sha256:ddc4b5b59fa699bb10127adcf7ad4de78fde4ec539a072b104b8bb16da666ae5"
@@ -204,18 +192,26 @@
},
"entrypoints": {
"hashes": [
"sha256:10ad569bb245e7e2ba425285b9fa3e8178a0dc92fc53b1e1c553805e15a8825b",
"sha256:d2d587dde06f99545fb13a383d2cd336a8ff1f359c5839ce3a64c917d10c029f"
"sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19",
"sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"
],
"version": "==0.2.3"
"version": "==0.3"
},
"flake8": {
"hashes": [
"sha256:c3ba1e130c813191db95c431a18cb4d20a468e98af7a77e2181b68574481ad36",
"sha256:fd9ddf503110bf3d8b1d270e8c673aab29ccb3dd6abf29bae1f54e5116ab4a91"
],
"index": "pypi",
"version": "==3.7.5"
},
"flit": {
"hashes": [
"sha256:6aefa6ff89a993af7a7af40d3df3d0387d6663df99797981ec41b1431ec6d1e1",
"sha256:9969db9708305b64fd8acf20043fcff144f910222397a221fd29871f02ed4a6f"
"sha256:1d93f7a833ed8a6e120ddc40db5c4763bc39bccc75c05081ec8285ece718aefb",
"sha256:6f6f0fb83c51ffa3a150fa41b5ac118df9ea4a87c2c06dff4ebf9adbe7b52b36"
],
"index": "pypi",
"version": "==1.2.1"
"version": "==1.3"
},
"idna": {
"hashes": [
@@ -267,7 +263,6 @@
"sha256:571702b5bd167911fe9036e5039ba67f820d6502832285cde8c881ab2b2149fd",
"sha256:c8481b5e59d34a5c7c42e98f6625e633f6ef59353abea6437472c7ec2093f191"
],
"index": "pypi",
"version": "==0.13.2"
},
"jinja2": {
@@ -279,10 +274,10 @@
},
"jsonschema": {
"hashes": [
"sha256:3ae8afd6f4ca6417f14bf43ef61341311598f14234cdb4174fe43d42b236a3c8",
"sha256:dfd8426040892c8d0ef6da574085f282569f189cb24b70091a66c21c12d6705e"
"sha256:683fe7ed58763ea0be572de5aad47cd3cc1297640916f9a8ccd222b287da7d2f",
"sha256:b42d7a292addb57370e6260bcbadb77e00a899fe6ec998c453f45893c41c658b"
],
"version": "==3.0.0a3"
"version": "==3.0.0b3"
},
"jupyter": {
"hashes": [
@@ -314,40 +309,6 @@
],
"version": "==4.4.0"
},
"lazy-object-proxy": {
"hashes": [
"sha256:0ce34342b419bd8f018e6666bfef729aec3edf62345a53b537a4dcc115746a33",
"sha256:1b668120716eb7ee21d8a38815e5eb3bb8211117d9a90b0f8e21722c0758cc39",
"sha256:209615b0fe4624d79e50220ce3310ca1a9445fd8e6d3572a896e7f9146bbf019",
"sha256:27bf62cb2b1a2068d443ff7097ee33393f8483b570b475db8ebf7e1cba64f088",
"sha256:27ea6fd1c02dcc78172a82fc37fcc0992a94e4cecf53cb6d73f11749825bd98b",
"sha256:2c1b21b44ac9beb0fc848d3993924147ba45c4ebc24be19825e57aabbe74a99e",
"sha256:2df72ab12046a3496a92476020a1a0abf78b2a7db9ff4dc2036b8dd980203ae6",
"sha256:320ffd3de9699d3892048baee45ebfbbf9388a7d65d832d7e580243ade426d2b",
"sha256:50e3b9a464d5d08cc5227413db0d1c4707b6172e4d4d915c1c70e4de0bbff1f5",
"sha256:5276db7ff62bb7b52f77f1f51ed58850e315154249aceb42e7f4c611f0f847ff",
"sha256:61a6cf00dcb1a7f0c773ed4acc509cb636af2d6337a08f362413c76b2b47a8dd",
"sha256:6ae6c4cb59f199d8827c5a07546b2ab7e85d262acaccaacd49b62f53f7c456f7",
"sha256:7661d401d60d8bf15bb5da39e4dd72f5d764c5aff5a86ef52a042506e3e970ff",
"sha256:7bd527f36a605c914efca5d3d014170b2cb184723e423d26b1fb2fd9108e264d",
"sha256:7cb54db3535c8686ea12e9535eb087d32421184eacc6939ef15ef50f83a5e7e2",
"sha256:7f3a2d740291f7f2c111d86a1c4851b70fb000a6c8883a59660d95ad57b9df35",
"sha256:81304b7d8e9c824d058087dcb89144842c8e0dea6d281c031f59f0acf66963d4",
"sha256:933947e8b4fbe617a51528b09851685138b49d511af0b6c0da2539115d6d4514",
"sha256:94223d7f060301b3a8c09c9b3bc3294b56b2188e7d8179c762a1cda72c979252",
"sha256:ab3ca49afcb47058393b0122428358d2fbe0408cf99f1b58b295cfeb4ed39109",
"sha256:bd6292f565ca46dee4e737ebcc20742e3b5be2b01556dafe169f6c65d088875f",
"sha256:cb924aa3e4a3fb644d0c463cad5bc2572649a6a3f68a7f8e4fbe44aaa6d77e4c",
"sha256:d0fc7a286feac9077ec52a927fc9fe8fe2fabab95426722be4c953c9a8bede92",
"sha256:ddc34786490a6e4ec0a855d401034cbd1242ef186c20d79d2166d6a4bd449577",
"sha256:e34b155e36fa9da7e1b7c738ed7767fc9491a62ec6af70fe9da4a057759edc2d",
"sha256:e5b9e8f6bda48460b7b143c3821b21b452cb3a835e6bbd5dd33aa0c8d3f5137d",
"sha256:e81ebf6c5ee9684be8f2c87563880f93eedd56dd2b6146d8a725b50b7e5adb0f",
"sha256:eb91be369f945f10d3a49f5f9be8b3d0b93a4c2be8f8a5b83b0571b8123e0a7a",
"sha256:f460d1ceb0e4a5dcb2a652db0904224f367c9b3c1470d5a7683c0480e582468b"
],
"version": "==1.3.1"
},
"livereload": {
"hashes": [
"sha256:29cadfabcedd12eed792e0131991235b9d4764d4474bed75cf525f57109ec0a2",
@@ -426,27 +387,26 @@
},
"mkdocs-material": {
"hashes": [
"sha256:037712dd7e2128a9b596943bcd92ebc9ad28800906dcee447e2fc008dd9dbbff",
"sha256:52522c8553a6d6da8fca2afe43297e8f88acdcf8ccf752a118148f1328f761e2"
"sha256:4b4af83c704d2bab41be3a5228e800a5e1157003368fbf548d95073ce19e0f61",
"sha256:86c0042c803586985bf79c99962ebd4644c3f0ff095d5df541f09fa48f5b62cc"
],
"index": "pypi",
"version": "==3.1.0"
"version": "==3.3.0"
},
"more-itertools": {
"hashes": [
"sha256:c187a73da93e7a8acc0001572aebc7e3c69daf7bf6881a2cea10650bd4420092",
"sha256:c476b5d3a34e12d40130bc2f935028b5f636df8f372dc2c1c01dc19681b2039e",
"sha256:fcbfeaea0be121980e15bc97b3817b5202ca73d0eae185b4550cbfce2a3ebb3d"
"sha256:0125e8f60e9e031347105eb1682cef932f5e97d7b9a1a28d9bf00c22a5daef40",
"sha256:590044e3942351a1bdb1de960b739ff4ce277960f2425ad4509446dbace8d9d1"
],
"version": "==4.3.0"
"version": "==6.0.0"
},
"mypy": {
"hashes": [
"sha256:12d965c9c4e8a625673aec493162cf390e66de12ef176b1f4821ac00d55f3ab3",
"sha256:38d5b5f835a81817dcc0af8d155bce4e9aefa03794fe32ed154d6612e83feafa"
"sha256:308c274eb8482fbf16006f549137ddc0d69e5a589465e37b99c4564414363ca7",
"sha256:e80fd6af34614a0e898a57f14296d0dacb584648f0339c2e000ddbf0f4cc2f8d"
],
"index": "pypi",
"version": "==0.650"
"version": "==0.670"
},
"mypy-extensions": {
"hashes": [
@@ -457,10 +417,10 @@
},
"nbconvert": {
"hashes": [
"sha256:08d21cf4203fabafd0d09bbd63f06131b411db8ebeede34b0fd4be4548351779",
"sha256:a8a2749f972592aa9250db975304af6b7337f32337e523a2c995cc9e12c07807"
"sha256:302554a2e219bc0fc84f3edd3e79953f3767b46ab67626fdec16e38ba3f7efe4",
"sha256:5de8fb2284422272a1d45abc77c07b888127550a6d602ce619592a2b08a474ff"
],
"version": "==5.4.0"
"version": "==5.4.1"
},
"nbformat": {
"hashes": [
@@ -484,17 +444,10 @@
},
"parso": {
"hashes": [
"sha256:35704a43a3c113cce4de228ddb39aab374b8004f4f2407d070b6a2ca784ce8a2",
"sha256:895c63e93b94ac1e1690f5fdd40b65f07c8171e3e53cbd7793b5b96c0e0a7f24"
"sha256:6ecf7244be8e7283ec9009c72d074830e7e0e611c974f813d76db0390a4e0dd6",
"sha256:8162be7570ffb34ec0b8d215d7f3b6c5fab24f51eb3886d6dee362de96b6db94"
],
"version": "==0.3.1"
},
"pep8-naming": {
"hashes": [
"sha256:1b419fa45b68b61cd8c5daf4e0c96d28915ad14d3d5f35fcc1e7e95324a33a2e",
"sha256:4eedfd4c4b05e48796f74f5d8628c068ff788b9c2b08471ad408007fc6450e5a"
],
"version": "==0.4.1"
"version": "==0.3.3"
},
"pexpect": {
"hashes": [
@@ -513,10 +466,10 @@
},
"pluggy": {
"hashes": [
"sha256:447ba94990e8014ee25ec853339faf7b0fc8050cdc3289d4d71f7f410fb90095",
"sha256:bde19360a8ec4dfd8a20dcb811780a30998101f078fc7ded6162f0076f50508f"
"sha256:8ddc32f03971bfdf900a81961a48ccf2fb677cf7715108f85295c67405798616",
"sha256:980710797ff6a041e9a73a5787804f848996ecaa6f8a1b1e08224a5894f2074a"
],
"version": "==0.8.0"
"version": "==0.8.1"
},
"prometheus-client": {
"hashes": [
@@ -526,18 +479,11 @@
},
"prompt-toolkit": {
"hashes": [
"sha256:c1d6aff5252ab2ef391c2fe498ed8c088066f66bc64a8d5c095bbf795d9fec34",
"sha256:d4c47f79b635a0e70b84fdb97ebd9a274203706b1ee5ed44c10da62755cf3ec9",
"sha256:fd17048d8335c1e6d5ee403c3569953ba3eb8555d710bfc548faf0712666ea39"
"sha256:88002cc618cacfda8760c4539e76c3b3f148ecdb7035a3d422c7ecdc90c2a3ba",
"sha256:c6655a12e9b08edb8cf5aeab4815fd1e1bdea4ad73d3bbf269cf2e0c4eb75d5e",
"sha256:df5835fb8f417aa55e5cafadbaeb0cf630a1e824aad16989f9f0493e679ec010"
],
"version": "==2.0.7"
},
"prospector": {
"hashes": [
"sha256:877d8d361a5c0e04c8587718c22c5d671afcf814945c96b3e592836d772943fd"
],
"index": "pypi",
"version": "==1.1.6.2"
"version": "==2.0.8"
},
"ptyprocess": {
"hashes": [
@@ -556,25 +502,17 @@
},
"pycodestyle": {
"hashes": [
"sha256:cbc619d09254895b0d12c2c691e237b2e91e9b2ecf5e84c26b35400f93dcfb83",
"sha256:cbfca99bd594a10f674d0cd97a3d802a1fdef635d4361e1a2658de47ed261e3a"
"sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56",
"sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c"
],
"version": "==2.4.0"
},
"pydocstyle": {
"hashes": [
"sha256:2258f9b0df68b97bf3a6c29003edc5238ff8879f1efb6f1999988d934e432bd8",
"sha256:5741c85e408f9e0ddf873611085e819b809fca90b619f5fd7f34bd4959da3dd4",
"sha256:ed79d4ec5e92655eccc21eb0c6cf512e69512b4a97d215ace46d17e4990f2039"
],
"version": "==3.0.0"
"version": "==2.5.0"
},
"pyflakes": {
"hashes": [
"sha256:08bd6a50edf8cffa9fa09a463063c425ecaaf10d1eb0335a7e8b1401aef89e6f",
"sha256:8d616a382f243dbf19b54743f280b80198be0bca3a5396f1d2e1fca6223e8805"
"sha256:5e8c00e30c464c99e0b501dc160b13a14af7f27d4dffb529c556e30a159e231d",
"sha256:f277f9ca3e55de669fba45b7393a1449009cff5a37d1af10ebb76c52765269cd"
],
"version": "==1.6.0"
"version": "==2.1.0"
},
"pygments": {
"hashes": [
@@ -583,38 +521,6 @@
],
"version": "==2.3.1"
},
"pylint": {
"hashes": [
"sha256:1d6d3622c94b4887115fe5204982eee66fdd8a951cf98635ee5caee6ec98c3ec",
"sha256:31142f764d2a7cd41df5196f9933b12b7ee55e73ef12204b648ad7e556c119fb"
],
"version": "==2.1.1"
},
"pylint-celery": {
"hashes": [
"sha256:41e32094e7408d15c044178ea828dd524beedbdbe6f83f712c5e35bde1de4beb"
],
"version": "==0.3"
},
"pylint-django": {
"hashes": [
"sha256:5dc5f85caef2c5f9e61622b9cbd89d94edd3dcf546939b2974d18de4fa90d676",
"sha256:bf313f10b68ed915a34f0f475cc9ff8c7f574a95302beb48b79c5993f7efd84c"
],
"version": "==2.0.2"
},
"pylint-flask": {
"hashes": [
"sha256:8fcdbb7cbf13d8c2ac1f2230b2aa1c1b83bb3ca2bd8b76f95561cb8757a305ec"
],
"version": "==0.5"
},
"pylint-plugin-utils": {
"hashes": [
"sha256:8ad25a82bcce390d1d6b7c006c123e0cb18051839c9df7b8bdb7823c53fe676e"
],
"version": "==0.4"
},
"pymdown-extensions": {
"hashes": [
"sha256:25b0a7967fa697b5035e23340a48594e3e93acb10b06d74574218ace3347d1df",
@@ -624,32 +530,39 @@
},
"pyrsistent": {
"hashes": [
"sha256:59880cc33ac293515892b2969aa8f4ed2cec592cbd0be4c4e20f2410468bbc62"
"sha256:07f7ae71291af8b0dbad8c2ab630d8223e4a8c4e10fc37badda158c02e753acf"
],
"version": "==0.14.8"
"version": "==0.14.10"
},
"pytest": {
"hashes": [
"sha256:f689bf2fc18c4585403348dd56f47d87780bf217c53ed9ae7a3e2d7faa45f8e9",
"sha256:f812ea39a0153566be53d88f8de94839db1e8a05352ed8a49525d7d7f37861e9"
"sha256:65aeaa77ae87c7fc95de56285282546cfa9c886dc8e5dc78313db1c25e21bc07",
"sha256:6ac6d467d9f053e95aaacd79f831dbecfe730f419c6c7022cb316b365cd9199d"
],
"index": "pypi",
"version": "==4.0.2"
"version": "==4.2.0"
},
"pytest-cov": {
"hashes": [
"sha256:513c425e931a0344944f84ea47f3956be0e416d95acbd897a44970c8d926d5d7",
"sha256:e360f048b7dae3f2f2a9a4d067b2dd6b6a015d384d1577c994a43f3f7cbad762"
"sha256:0ab664b25c6aa9716cbf203b17ddb301932383046082c081b9848a0edf5add33",
"sha256:230ef817450ab0699c6cc3c9c8f7a829c34674456f2ed8df1fe1d39780f7c87f"
],
"index": "pypi",
"version": "==2.6.0"
"version": "==2.6.1"
},
"python-dateutil": {
"hashes": [
"sha256:063df5763652e21de43de7d9e00ccf239f953a832941e37be541614732cdfc93",
"sha256:88f9287c0174266bb0d8cedd395cfba9c58e87e5ad86b2ce58859bc11be3cf02"
"sha256:7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb",
"sha256:c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e"
],
"version": "==2.7.5"
"version": "==2.8.0"
},
"python-multipart": {
"hashes": [
"sha256:f7bb5f611fc600d15fa47b3974c8aa16e93724513b49b5f95c81e6624c83fa43"
],
"index": "pypi",
"version": "==0.0.5"
},
"pytoml": {
"hashes": [
@@ -712,19 +625,6 @@
"index": "pypi",
"version": "==2.21.0"
},
"requirements-detector": {
"hashes": [
"sha256:9fbc4b24e8b7c3663aff32e3eba34596848c6b91bd425079b386973bd8d08931"
],
"version": "==0.6"
},
"rope": {
"hashes": [
"sha256:a108c445e1cd897fe19272ab7877d172e7faf3d4148c80e7d20faba42ea8f7b2"
],
"index": "pypi",
"version": "==0.11.0"
},
"send2trash": {
"hashes": [
"sha256:60001cc07d707fe247c94f74ca6ac0d3255aabcb930529690897ca2a39db28b2",
@@ -732,12 +632,6 @@
],
"version": "==1.5.0"
},
"setoptconf": {
"hashes": [
"sha256:5b0b5d8e0077713f5d5152d4f63be6f048d9a1bb66be15d089a11c898c3cf49c"
],
"version": "==0.2.0"
},
"six": {
"hashes": [
"sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c",
@@ -745,12 +639,12 @@
],
"version": "==1.12.0"
},
"snowballstemmer": {
"sqlalchemy": {
"hashes": [
"sha256:919f26a68b2c17a7634da993d91339e288964f93c274f1343e3bbbe2096e1128",
"sha256:9f3bcd3c401c3e862ec0ebe6d2c069ebc012ce142cce209c098ccb5b09136e89"
"sha256:7dede29f121071da9873e7b8c98091874617858e790dc364ffaab4b09d81216c"
],
"version": "==1.2.1"
"index": "pypi",
"version": "==1.3.0b3"
},
"terminado": {
"hashes": [
@@ -775,15 +669,9 @@
},
"tornado": {
"hashes": [
"sha256:0662d28b1ca9f67108c7e3b77afabfb9c7e87bde174fbda78186ecedc2499a9d",
"sha256:4e5158d97583502a7e2739951553cbd88a72076f152b4b11b64b9a10c4c49409",
"sha256:732e836008c708de2e89a31cb2fa6c0e5a70cb60492bee6f1ea1047500feaf7f",
"sha256:8154ec22c450df4e06b35f131adc4f2f3a12ec85981a203301d310abf580500f",
"sha256:8e9d728c4579682e837c92fdd98036bd5cdefa1da2aaf6acf26947e6dd0c01c5",
"sha256:d4b3e5329f572f055b587efc57d29bd051589fb5a43ec8898c77a47ec2fa2bbb",
"sha256:e5f2585afccbff22390cddac29849df463b252b711aa2ce7c5f3f342a5b3b444"
"sha256:d3b719a0cb7094e2b1ca94b31f4b601639fa7ad01a548a1a2ccdd6cbdfd56671"
],
"version": "==5.1.1"
"version": "==6.0b1"
},
"traitlets": {
"hashes": [
@@ -794,30 +682,27 @@
},
"typed-ast": {
"hashes": [
"sha256:0555eca1671ebe09eb5f2176723826f6f44cca5060502fea259de9b0e893ab53",
"sha256:0ca96128ea66163aea13911c9b4b661cb345eb729a20be15c034271360fc7474",
"sha256:16ccd06d614cf81b96de42a37679af12526ea25a208bce3da2d9226f44563868",
"sha256:1e21ae7b49a3f744958ffad1737dfbdb43e1137503ccc59f4e32c4ac33b0bd1c",
"sha256:37670c6fd857b5eb68aa5d193e14098354783b5138de482afa401cc2644f5a7f",
"sha256:46d84c8e3806619ece595aaf4f37743083f9454c9ea68a517f1daa05126daf1d",
"sha256:5b972bbb3819ece283a67358103cc6671da3646397b06e7acea558444daf54b2",
"sha256:6306ffa64922a7b58ee2e8d6f207813460ca5a90213b4a400c2e730375049246",
"sha256:6cb25dc95078931ecbd6cbcc4178d1b8ae8f2b513ae9c3bd0b7f81c2191db4c6",
"sha256:7e19d439fee23620dea6468d85bfe529b873dace39b7e5b0c82c7099681f8a22",
"sha256:7f5cd83af6b3ca9757e1127d852f497d11c7b09b4716c355acfbebf783d028da",
"sha256:81e885a713e06faeef37223a5b1167615db87f947ecc73f815b9d1bbd6b585be",
"sha256:94af325c9fe354019a29f9016277c547ad5d8a2d98a02806f27a7436b2da6735",
"sha256:b1e5445c6075f509d5764b84ce641a1535748801253b97f3b7ea9d948a22853a",
"sha256:cb061a959fec9a514d243831c514b51ccb940b58a5ce572a4e209810f2507dcf",
"sha256:cc8d0b703d573cbabe0d51c9d68ab68df42a81409e4ed6af45a04a95484b96a5",
"sha256:da0afa955865920edb146926455ec49da20965389982f91e926389666f5cf86a",
"sha256:dc76738331d61818ce0b90647aedde17bbba3d3f9e969d83c1d9087b4f978862",
"sha256:e7ec9a1445d27dbd0446568035f7106fa899a36f55e52ade28020f7b3845180d",
"sha256:f741ba03feb480061ab91a465d1a3ed2d40b52822ada5b4017770dfcb88f839f",
"sha256:fe800a58547dd424cd286b7270b967b5b3316b993d86453ede184a17b5a6b17d"
"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"
],
"markers": "python_version < '3.7' and implementation_name == 'cpython'",
"version": "==1.1.1"
"version": "==1.3.1"
},
"ujson": {
"hashes": [
@@ -853,12 +738,6 @@
"sha256:fa618be8435447a017fd1bf2c7ae922d0428056cfc7449f7a8641edf76b48265"
],
"version": "==3.4.2"
},
"wrapt": {
"hashes": [
"sha256:d4d560d479f2c21e1b5443bbd15fe7ec4b37fe7e53d335d3b9b0a7b1226fe3c6"
],
"version": "==1.10.11"
}
}
}

View File

@@ -24,7 +24,7 @@
---
FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3.6+.
FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3.6+ based on standard Python type hints.
The key features are:
@@ -166,7 +166,7 @@ You will see the alternative automatic documentation (provided by <a href="https
## Example upgrade
Now modify the file `main.py` to recive a body from a `PUT` request.
Now modify the file `main.py` to receive a body from a `PUT` request.
Declare the body using standard Python types, thanks to Pydantic.
@@ -257,7 +257,7 @@ item: Item
* Validation of data:
* Automatic and clear errors when the data is invalid.
* Validation even for deeply nested JSON objects.
* <abbr title="also known as: serialization, parsing, marshalling">Conversion</abbr> of input data: coming from the network, to Python data and types. Reading from:
* <abbr title="also known as: serialization, parsing, marshalling">Conversion</abbr> of input data: coming from the network to Python data and types. Reading from:
* JSON.
* Path parameters.
* Query parameters.
@@ -292,7 +292,7 @@ Coming back to the previous code example, **FastAPI** will:
* All this would also work for deeply nested JSON objects.
* Convert from and to JSON automatically.
* Document everything with OpenAPI, that can be used by:
* Interactive documentation sytems.
* Interactive documentation systems.
* Automatic client code generation systems, for many languages.
* Provide 2 interactive documentation web interfaces directly.
@@ -329,7 +329,7 @@ For a more complete example including more features, see the <a href="https://fa
**Spoiler alert**: the tutorial - user guide includes:
* Declaration of **parameters** from other different places as: **headers**, **cookies**, **form fields** and **files**.
* How to set **validation constrains** as `maximum_length` or `regex`.
* How to set **validation constraints** as `maximum_length` or `regex`.
* A very powerful and easy to use **<abbr title="also known as components, resources, providers, services, injectables">Dependency Injection</abbr>** system.
* Security and authentication, including support for **OAuth2** with **JWT tokens** and **HTTP Basic** auth.
* More advanced (but equally easy) techniques for declaring **deeply nested JSON models** (thanks to Pydantic).

View File

@@ -6,7 +6,7 @@ What inspired **FastAPI**, how it compares to other alternatives and what it lea
There have been many tools created before that have helped inspire its creation.
I have been avoiding the creation of a new framework for several years. First I tried to solve all the features covered by **FastAPI** using many different frameworks, plug-ins and tools.
I have been avoiding the creation of a new framework for several years. First I tried to solve all the features covered by **FastAPI** using many different frameworks, plug-ins, and tools.
But at some point, there was no other option than creating something that provided all these features, taking the best ideas from previous tools, and combining them in the best way possible, using language features that weren't even available before (Python 3.6+ type hints).
@@ -54,6 +54,47 @@ Given the simplicity of Flask, it seemed like a good match for building APIs. Th
Have a simple and easy to use routing system.
### <a href="http://docs.python-requests.org" target="_blank">Requests</a>
**FastAPI** is not actually an alternative to **Requests**. Their scope is very different.
It would actually be common to use Requests *inside* of a FastAPI application.
But still, FastAPI got quite some inspiration from Requests.
**Requests** is a library to *interact* with APIs (as a client), while **FastAPI** is a library to *build* APIs (as a server).
They are, more or less, at opposite ends, complementing each other.
Requests has a very simple and intuitive design, it's very easy to use, with sensible defaults. But at the same time, it's very powerful and customizable.
That's why, as said in the official website:
> Requests is one of the most downloaded Python packages of all time
The way you use it is very simple. For example, to do a `GET` request, you would write:
```Python
response = requests.get("http://example.com/some/url")
```
The FastAPI counterpart API path operation could look like:
```Python hl_lines="1"
@app.get("/some/url")
def read_url():
return {"message": "Hello World"}
```
See the similarities in `requests.get(...)` and `@app.get(...)`.
!!! check "Inspired **FastAPI** to"
* Have a simple and intuitive API.
* Use HTTP method names (operations) directly, in a straightforward and intuitive way.
* Have sensible defaults, but powerful customizations.
### <a href="https://swagger.io/" target="_blank">Swagger</a> / <a href="https://github.com/OAI/OpenAPI-Specification/" target="_blank">OpenAPI</a>
The main feature I wanted from Django REST Framework was the automatic API documentation.
@@ -80,7 +121,7 @@ That's why when talking about version 2.0 it's common to say "Swagger", and for
There are several Flask REST frameworks, but after investing the time and work into investigating them, I found that many are discontinued or abandoned, with several standing issues that made them unfit.
## <a href="https://marshmallow.readthedocs.io/en/3.0/" target="_blank">Marshmallow</a>
### <a href="https://marshmallow.readthedocs.io/en/3.0/" target="_blank">Marshmallow</a>
One of the main features needed by API systems is data "<abbr title="also called marshalling, convertion">serialization</abbr>" which is taking data from the code (Python) and converting it into something that can be sent through the network. For example, converting an object containing data from a database into a JSON object. Converting `datetime` objects into strings, etc.
@@ -121,7 +162,7 @@ It is a plug-in for many frameworks (and there's a plug-in for Starlette too).
The way it works is that you write the definition of the schema using YAML format inside the docstring of each function handling a route.
And it generates Swagger 2.0 schemas (OpenAPI 2.0).
And it generates OpenAPI schemas.
That's how it works in Flask, Starlette, Responder, etc.
@@ -140,7 +181,7 @@ The editor can't help much with that. And if we modify parameters or Marshmallow
It's a Flask plug-in, that ties together Webargs, Marshmallow and APISpec.
It uses the information from Webargs and Marshmallow to automatically generate Swagger 2.0 schemas, using APISpec.
It uses the information from Webargs and Marshmallow to automatically generate OpenAPI schemas, using APISpec.
It's a great tool, very under-rated. It should be way more popular than many Flask plug-ins out there. It might be due to its documentation being too concise and abstract.

View File

@@ -1 +1,219 @@
Coming soon...
It is recommended to use <a href="https://www.docker.com/" target="_blank">**Docker**</a> for security, replicability, development simplicity, etc.
In this section you'll see instructions and links to guides to know how to:
* Make your **FastAPI** application a Docker image/container with maximum performance. In about **5 min**.
* (Optionally) understand what you, as a developer, need to know about HTTPS.
* Set up a Docker Swarm mode cluster with automatic HTTPS, even on a simple $5 USD/month server. In about **20 min**.
* Generate and deploy a full **FastAPI** application, using your Docker Swarm cluster, with HTTPS, etc. In about **10 min**.
---
You can also easily use **FastAPI** in a standard server directly too (without Docker).
## Docker
If you are using Docker, you can use the official Docker image:
### <a href="https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker" target="_blank">tiangolo/uvicorn-gunicorn-fastapi</a>
This image has an "auto-tuning" mechanism included, so that you can just add your code and get very high performance automatically. And without making sacrifices.
But you can still change and update all the configurations with environment variables or configuration files.
!!! tip
To see all the configurations and options, go to the Docker image page: <a href="https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker" target="_blank">tiangolo/uvicorn-gunicorn-fastapi</a>.
### Build your Image
* Go to your project directory.
* Create a `Dockerfile` with:
```Dockerfile
FROM tiangolo/uvicorn-gunicorn-fastapi:python3.7
COPY ./app /app
```
* Create an `app` directory and enter in it.
* Create a `main.py` file with:
```Python
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"Hello": "World"}
@app.get("/items/{item_id}")
def read_item(item_id: int, q: str = None):
return {"item_id": item_id, "q": q}
```
* You should now have a directory structure like:
```
.
├── app
│ └── main.py
└── Dockerfile
```
* Go to the project directory (in where your `Dockerfile` is, containing your `app` directory).
* Build your FastAPI image:
```bash
docker build -t myimage .
```
* Run a container based on your image:
```bash
docker run -d --name mycontainer -p 80:80 myimage
```
Now you have an optimized FastAPI server in a Docker container. Auto-tuned for your current server (and number of CPU cores).
### Check it
You should be able to check it in your Docker container's URL, for example: <a href="http://192.168.99.100/items/5?q=somequery" target="_blank">http://192.168.99.100/items/5?q=somequery</a> or <a href="http://127.0.0.1/items/5?q=somequery" target="_blank">http://127.0.0.1/items/5?q=somequery</a> (or equivalent, using your Docker host).
You will see something like:
```JSON
{"item_id": 5, "q": "somequery"}
```
### Interactive API docs
Now you can go to <a href="http://192.168.99.100/docs" target="_blank">http://192.168.99.100/docs</a> or <a href="http://127.0.0.1/docs" target="_blank">http://127.0.0.1/docs</a> (or equivalent, using your Docker host).
You will see the automatic interactive API documentation (provided by <a href="https://github.com/swagger-api/swagger-ui" target="_blank">Swagger UI</a>):
![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png)
### Alternative API docs
And you can also go to <a href="http://192.168.99.100/redoc" target="_blank">http://192.168.99.100/redoc</a> or <a href="http://127.0.0.1/redoc" target="_blank">http://127.0.0.1/redoc</a> (or equivalent, using your Docker host).
You will see the alternative automatic documentation (provided by <a href="https://github.com/Rebilly/ReDoc" target="_blank">ReDoc</a>):
![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png)
## HTTPS
### About HTTPS
It is easy to assume that HTTPS is something that is just "enabled" or not.
But it is way more complex than that.
!!! tip
If you are in a hurry or don't care, continue with the next section for step by step instructions to set everything up.
To learn the basics of HTTPS, from a consumer perspective, check <a href="https://howhttps.works/" target="_blank">https://howhttps.works/</a>.
Now, from a developer's perspective, here are several things to have in mind while thinking about HTTPS:
* For HTTPS, the server needs to have "certificates" generated by a third party.
* Those certificates are actually acquired from the third-party, not "generated".
* Certificates have a lifetime.
* They expire.
* And then they need to be renewed, acquired again from the third party.
* The encryption of the connection happens at the TCP level.
* That's one layer below HTTP.
* So, the certificate and encryption handling is done before HTTP.
* TCP doesn't know about "domains". Only about IP addresses.
* The information about the specific domain requested goes in the HTTP data.
* The HTTPS certificates "certificate" a certain domain, but the protocol and encryption happen at the TCP level, before knowing which domain is being dealt with.
* By default, that would mean that you can only have one HTTPS certificate per IP address.
* No matter how big is your server and how small each application you have there might be. But...
* There's an extension to the TLS protocol (the one handling the encryption at the TCP level, before HTTP) called <a href="https://en.wikipedia.org/wiki/Server_Name_Indication" target="_blank"><abbr title="Server Name Indication">SNI</abbr></a>.
* This SNI extension allows one single server (with a single IP address) to have several HTTPS certificates and server multiple HTTPS domains/applications.
* For this to work, a single component (program) running in the server, listening in the public IP address, must have all the HTTPS certificates in the server.
* After having a secure connection, the communication protocol is the same HTTP.
* It goes encrypted, but the encrypted contents are the same HTTP protocol.
It is a common practice to have one program/HTTP server runing in the server (the machine, host, etc) and managing all the HTTPS parts, sending the decrypted HTTP requests to the actual HTTP application running in the same server (the **FastAPI** application, in this case), take the HTTP response from the application, encrypt it using the appropriate certificate and sending it back to the client using HTTPS. This server is ofter called a <a href="https://en.wikipedia.org/wiki/TLS_termination_proxy" target="_blank">TLS Termination Proxy</a>.
### Let's Encrypt
Up to some years ago, these HTTPS certificates were sold by trusted third-parties.
The process to acquire one of these certificates used to be cumbersome, require quite some paperwork and the certificates were quite expensive.
But then <a href="https://letsencrypt.org/" target="_blank">Let's Encrypt</a> was created.
It is a project from the Linux Foundation. It provides HTTPS certificates for free. In an automated way. These certificates use all the standard cryptographic security, and are short lived (about 3 months), so, the security is actually increased, by reducing their lifespan.
The domain's are securely verified and the certificates are generated automatically. This also allows automatizing the renewal of these certificates.
The idea is to automatize the acquisition and renewal of these certificates, so that you can have secure HTTPS, free, forever.
### Traefik
<a href="https://traefik.io/" target="_blank">Traefik</a> is a high performance reverse proxy / load balancer. It can do the "TLS Termination Proxy" job (apart from other features).
It has integration with Let's Encrypt. So, it can handle all the HTTPS parts, including certificate acquisition and renewal.
It also has integrations with Docker. So, you can declare your domains in each application configurations and have it read those configurations, generate the HTTPS certificates and serve HTTPS to your application, all automatically. Without requiring any change in its configuration.
---
With this information and tools, continue with the next section to combine everything.
## Docker Swarm mode cluster with Traefik and HTTPS
You can have a Docker Swarm mode cluster set up in minutes (about 20 min) with a main Traefik handling HTTPS (including certificate acquisition and renewal).
By using Docker Swarm mode, you can start with a "cluster" of a single machine (it can even be a $5 USD / month server) and then you can grow as much as you need adding more servers.
To set up a Docker Swarm Mode cluster with Traefik and HTTPS handling, follow this guide:
### <a href="https://medium.com/@tiangolo/docker-swarm-mode-and-traefik-for-a-https-cluster-20328dba6232" target="_blank">Docker Swarm Mode and Traefik for an HTTPS cluster</a>.
### Deploy a FastAPI application
The easiest way to set everything up, would be using the <a href="/project-generation/" target="_blank">FastAPI project generator</a>.
It is designed to be integrated with this Docker Swarm cluster with Traefik and HTTPS described above.
You can generate a project in about 2 min.
The generated project has instructions to deploy it, doing it takes other 2 min.
## Alternatively, deploy **FastAPI** without Docker
You can deploy **FastAPI** directly without Docker too.
You just need to install <a href="https://www.uvicorn.org/" target="_blank">Uvicorn</a> (or any other ASGI server).
And run your application the same way you have done in the tutorials, but without the `--debug` option, e.g.:
```bash
uvicorn main:app --host 0.0.0.0 --port 80
```
You might want to set up some tooling to make sure it is restarted automatically if it stops.
You might also want to install <a href="https://gunicorn.org/" target="_blank">Gunicorn</a> and <a href="https://www.uvicorn.org/#running-with-gunicorn" target="_blank">use it as a manager for Uvicorn</a>.
Making sure to fine-tune the number of workers, etc.
But if you are doing all that, you might just use the Docker image that does it automatically.

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

View File

@@ -24,7 +24,7 @@
---
FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3.6+.
FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3.6+ based on standard Python type hints.
The key features are:
@@ -166,7 +166,7 @@ You will see the alternative automatic documentation (provided by <a href="https
## Example upgrade
Now modify the file `main.py` to recive a body from a `PUT` request.
Now modify the file `main.py` to receive a body from a `PUT` request.
Declare the body using standard Python types, thanks to Pydantic.
@@ -257,7 +257,7 @@ item: Item
* Validation of data:
* Automatic and clear errors when the data is invalid.
* Validation even for deeply nested JSON objects.
* <abbr title="also known as: serialization, parsing, marshalling">Conversion</abbr> of input data: coming from the network, to Python data and types. Reading from:
* <abbr title="also known as: serialization, parsing, marshalling">Conversion</abbr> of input data: coming from the network to Python data and types. Reading from:
* JSON.
* Path parameters.
* Query parameters.
@@ -292,7 +292,7 @@ Coming back to the previous code example, **FastAPI** will:
* All this would also work for deeply nested JSON objects.
* Convert from and to JSON automatically.
* Document everything with OpenAPI, that can be used by:
* Interactive documentation sytems.
* Interactive documentation systems.
* Automatic client code generation systems, for many languages.
* Provide 2 interactive documentation web interfaces directly.
@@ -329,7 +329,7 @@ For a more complete example including more features, see the <a href="https://fa
**Spoiler alert**: the tutorial - user guide includes:
* Declaration of **parameters** from other different places as: **headers**, **cookies**, **form fields** and **files**.
* How to set **validation constrains** as `maximum_length` or `regex`.
* How to set **validation constraints** as `maximum_length` or `regex`.
* A very powerful and easy to use **<abbr title="also known as components, resources, providers, services, injectables">Dependency Injection</abbr>** system.
* Security and authentication, including support for **OAuth2** with **JWT tokens** and **HTTP Basic** auth.
* More advanced (but equally easy) techniques for declaring **deeply nested JSON models** (thanks to Pydantic).

17
docs/release-notes.md Normal file
View File

@@ -0,0 +1,17 @@
## 0.3.0
* Fix/add SQLAlchemy support, including ORM, and update <a href="https://fastapi.tiangolo.com/tutorial/sql-databases/" target="_blank">docs for SQLAlchemy</a>: <a href="https://github.com/tiangolo/fastapi/pull/30" target="_blank">#30</a>
## 0.2.1
* Fix `jsonable_encoder` for Pydantic models with `Config` but without `json_encoders`: <a href="https://github.com/tiangolo/fastapi/pull/29" target="_blank">#29</a>
## 0.2.0
* Fix typos in Security section: <a href="https://github.com/tiangolo/fastapi/pull/24" target="_blank">#24</a> by <a href="https://github.com/kkinder" target="_blank">@kkinder</a>
* Add support for Pydantic custom JSON encoders: <a href="https://github.com/tiangolo/fastapi/pull/21" target="_blank">#21</a> by <a href="https://github.com/euri10" target="_blank">@euri10</a>
## 0.1.19
* Upgrade Starlette version to the current latest `0.10.1`: <a href="https://github.com/tiangolo/fastapi/pull/17" target="_blank">#17</a> by <a href="https://github.com/euri10" target="_blank">@euri10</a>

View File

@@ -1,7 +1,7 @@
from fastapi import FastAPI
from .routers.tutorial001 import router as users_router
from .routers.tutorial002 import router as items_router
from .routers.items import router as items_router
from .routers.users import router as users_router
app = FastAPI()

View File

@@ -3,11 +3,11 @@ from fastapi import APIRouter
router = APIRouter()
@router.get("/")
@router.get("/", tags=["items"])
async def read_items():
return [{"name": "Item Foo"}, {"name": "item Bar"}]
@router.get("/{item_id}")
@router.get("/{item_id}", tags=["items"])
async def read_item(item_id: str):
return {"name": "Fake Specific Item", "item_id": item_id}

View File

@@ -3,16 +3,16 @@ from fastapi import APIRouter
router = APIRouter()
@router.get("/users/")
@router.get("/users/", tags=["users"])
async def read_users():
return [{"username": "Foo"}, {"username": "Bar"}]
@router.get("/users/me")
@router.get("/users/me", tags=["users"])
async def read_user_me():
return {"username": "fakecurrentuser"}
@router.get("/users/{username}")
@router.get("/users/{username}", tags=["users"])
async def read_user(username: str):
return {"username": username}

View File

@@ -0,0 +1,8 @@
from fastapi import FastAPI
app = FastAPI()
@app.post("/items/", status_code=201)
async def create_item(name: str):
return {"name": name}

View File

@@ -0,0 +1,9 @@
from fastapi import FastAPI
from starlette.status import HTTP_201_CREATED
app = FastAPI()
@app.post("/items/", status_code=HTTP_201_CREATED)
async def create_item(name: str):
return {"name": name}

View File

@@ -1,13 +1,15 @@
from fastapi import FastAPI
from sqlalchemy import Boolean, Column, Integer, String, create_engine
from sqlalchemy.ext.declarative import declarative_base, declared_attr
from sqlalchemy.orm import scoped_session, sessionmaker
# SQLAlchemy specific code, as with any other app
SQLALCHEMY_DATABASE_URI = "postgresql://user:password@postgresserver/db"
SQLALCHEMY_DATABASE_URI = "sqlite:///./test.db"
# SQLALCHEMY_DATABASE_URI = "postgresql://user:password@postgresserver/db"
engine = create_engine(SQLALCHEMY_DATABASE_URI, convert_unicode=True)
engine = create_engine(
SQLALCHEMY_DATABASE_URI, connect_args={"check_same_thread": False}
)
db_session = scoped_session(
sessionmaker(autocommit=False, autoflush=False, bind=engine)
)
@@ -30,15 +32,25 @@ class User(Base):
is_active = Column(Boolean(), default=True)
def get_user(username, db_session):
return db_session.query(User).filter(User.id == username).first()
Base.metadata.create_all(bind=engine)
first_user = db_session.query(User).first()
if not first_user:
u = User(email="johndoe@example.com", hashed_password="notreallyhashed")
db_session.add(u)
db_session.commit()
# Utility
def get_user(db_session, user_id: int):
return db_session.query(User).filter(User.id == user_id).first()
# FastAPI specific code
app = FastAPI()
@app.get("/users/{username}")
def read_user(username: str):
user = get_user(username, db_session)
@app.get("/users/{user_id}")
def read_user(user_id: int):
user = get_user(db_session, user_id=user_id)
return user

View File

@@ -1,13 +1,284 @@
Coming soon...
If you are building an application or a web API, it's rarely the case that you can put everything on a single file.
```Python
{!./src/bigger_applications/app/routers/tutorial001.py!}
**FastAPI** provides a convenience tool to structure your application while keeping all the flexibility.
## An example file structure
Let's say you have a file structure like this:
```
.
├── app
│   ├── __init__.py
│   ├── main.py
│   └── routers
│   ├── __init__.py
│   ├── items.py
│   └── users.py
```
```Python
{!./src/bigger_applications/app/routers/tutorial002.py!}
!!! tip
There are two `__init__.py` files: one in each directory or subdirectory.
This is what allows importing code from one file into another.
For example, in `app/main.py` you could have a line like:
```
from app.routers import items
```
* The `app` directory contains everything.
* This `app` directory has an empty file `app/__init__.py`.
* So, the `app` directory is a "Python package" (a collection of "Python modules").
* The `app` directory also has a `app/main.py` file.
* As it is inside a Python package directory (because there's a file `__init__.py`), it is a "module" of that package: `app.main`.
* There's a subdirectory `app/routers/`.
* The subdirectory `app/routers` also has an empty file `__init__.py`.
* So, it is a "Python subpackage".
* The file `app/routers/items.py` is beside the `app/routers/__init__.py`.
* So, it's a submodule: `app.routers.items`.
* The file `app/routers/users.py` is beside the `app/routers/__init__.py`.
* So, it's a submodule: `app.routers.users`.
## `APIRouter`
Let's say the file dedicated to handling just users is the submodule at `/app/routers/users.py`.
You want to have the *path operations* related to your users separated from the rest of the code, to keep it organized.
But it's still part of the same **FastAPI** application/web API (it's part of the same "Python Package").
You can create the *path operations* for that module using `APIRouter`.
### Import `APIRouter`
You import it and create an "instance" the same way you would with the class `FastAPI`:
```Python hl_lines="1 3"
{!./src/bigger_applications/app/routers/users.py!}
```
```Python
{!./src/bigger_applications/app/tutorial003.py!}
### Path operations with `APIRouter`
And then you use it to declare your *path operations*.
Use it the same way you would use the `FastAPI` class:
```Python hl_lines="6 11 16"
{!./src/bigger_applications/app/routers/users.py!}
```
You can think of `APIRouter` as a "mini `FastAPI`" class.
All the same options are supported.
All the same parameters, responses, dependencies, tags, etc.
!!! tip
In this example, the variable is called `router`, but you can name it however you want.
We are going to include this `APIrouter` in the main `FastAPI` app, but first, let's add another `APIRouter`.
## Another module with `APIRouter`
Let's say you also have the endpoints dedicated to handling "Items" from your application in the module at `app/routers/items.py`.
You have path operations for:
* `/items/`
* `/items/{item_id}`
It's all the same structure as with `app/routers/users.py`.
But let's say that this time we are more lazy.
And we don't want to have to explicitly type `/items/` in every path operation, we can do it later:
```Python hl_lines="6 11 16"
{!./src/bigger_applications/app/routers/items.py!}
```
## The main `FastAPI`
Now, let's see the module at `app/main.py`.
Here's where you import and use the class `FastAPI`.
This will be the main file in your application that ties everything together.
### Import `FastAPI`
You import and create a `FastAPI` class as normally:
```Python hl_lines="1 6"
{!./src/bigger_applications/app/main.py!}
```
### Import the `APIRouter`
But this time we are not adding path operations directly with the `FastAPI` `app`.
We import the `APIRouter`s from the other files:
```Python hl_lines="3 4"
{!./src/bigger_applications/app/main.py!}
```
As the file `app/routers/items.py` is part of the same Python package, we can import it using "dot notation".
### How the importing works
The section:
```Python
from .routers.items import router
```
Means:
* Starting in the same package that this module (the file `app/main.py`) lives in (the directory `app/`)...
* look for the subpackage `routers` (the directory at `app/routers/`)...
* and from it, the submodule `items` (the file at `app/routers/items.py`)...
* and from that submodule, import the variable `router`.
The variable `router` is the same one we created in the file `app/routers/items.py`. It's an `APIRouter`.
We could also import it like:
```Python
from app.routers.items import router
```
!!! info
The first version is a "relative import".
The second version is an "absolute import".
To learn more about Python Packages and Modules, read <a href="https://docs.python.org/3/tutorial/modules.html" target="_blank">the official Python documentation about Modules</a>.
### Avoid name collisions
We are importing a variable named `router` from the submodule `items`.
But we also have another variable named `router` in the submodule `users`.
If we import one after the other, like:
```Python
from .routers.items import router
from .routers.users import router
```
The `router` from `users` will overwrite the one form `items` and we won't be able to use them at the same time.
So, to be able to use both of them in the same file, we rename them while importing them using `as`:
```Python hl_lines="3 4"
{!./src/bigger_applications/app/main.py!}
```
### Include an `APIRouter`
Now, let's include the router from the submodule `users`, now in the variable `users_router`:
```Python hl_lines="8"
{!./src/bigger_applications/app/main.py!}
```
With `app.include_router()` we can add an `APIRouter` to the main `FastAPI` application.
It will include all the routes from that router as part of it.
!!! note "Technical Details"
It will actually internally create a path operation for each path operation that was declared in the `APIRouter`.
So, behind the scenes, it will actually work as if everything was the same single app.
!!! check
You don't have to worry about performance when including routers.
This will take microseconds and will only happen at startup.
So it won't affect performance.
### Include an `APIRouter` with a prefix
Now, let's include the router form the `items` submodule, now in the variable `items_router`.
But, remember that we were lazy and didn't add `/items/` to all the path operations?
We can add a prefix to all the path operations using the parameter `prefix` of `app.include_router()`.
As the path of each path operation has to start with `/`, like in:
```Python hl_lines="1"
@router.get("/{item_id}", tags=["items"])
async def read_item(item_id: str):
...
```
...the prefix must not include a final `/`.
So, the prefix in this case would be `/items`:
```Python hl_lines="9"
{!./src/bigger_applications/app/main.py!}
```
The end result is that the item paths are now:
* `/items/`
* `/items/{item_id}`
...as we intended.
!!! check
The `prefix` parameter is (as in many other cases) just a feature from **FastAPI** to help you avoid code duplication.
!!! tip
You could also add path operations directly, for example with: `@app.get(...)`.
Apart from `app.include_router()`, in the same **FastAPI** app.
It would still work the same.
!!! info "Very Technical Details"
**Note**: this is a very technical detail that you probably can **just skip**.
---
The `APIRouter`s are not "mounted", they are not isolated from the rest of the application.
This is because we want to include their path operations in the OpenAPI schema and the user interfaces.
As we cannot just isolate them and "mount" them independently of the rest, the path operations are "cloned" (re-created), not included directly.
## Check the automatic API docs
Now, run `uvicorn`, using the module `app.main` and the variable `app`:
```bash
uvicorn app.main:app --debug
```
And open the docs at <a href="http://127.0.0.1:8000/docs" target="_blank">http://127.0.0.1:8000/docs</a>.
You will see the automatic API docs, including the paths from all the submodules:
<img src="/img/tutorial/bigger-applications/image01.png">

View File

@@ -48,6 +48,10 @@ For example, you can use that functionality to pass a <a href="http://json-schem
{!./src/body_schema/tutorial002.py!}
```
And it would look in the `/docs` like this:
<img src="/img/tutorial/body-schema/image01.png">
## Recap
You can use Pydantic's `Schema` to declare extra validations and metadata for model attributes.

View File

@@ -19,11 +19,67 @@ Here's a general idea of how the models could look like with their password fiel
{!./src/extra_models/tutorial001.py!}
```
#### About `**user_dict`
### About `**user_in.dict()`
`UserInDB(**user_dict)` means:
Pass the keys and values of the `user_dict` directly as key-value arguments, equivalent to:
#### Pydantic's `.dict()`
`user_in` is a Pydantic model of class `UserIn`.
Pydantic models have a `.dict()` method that returns a `dict` with the model's data.
So, if we create a Pydantic object `user_in` like:
```Python
user_in = UserIn(username="john", password="secret", email="john.doe@example.com")
```
and then we call:
```Python
user_dict = user_in.dict()
```
we now have a `dict` with the data in the variable `user_dict` (it's a `dict` instead of a Pydantic model object).
And if we call:
```Python
print(user_dict)
```
we would get a Python `dict` with:
```Python
{
'username': 'john',
'password': 'secret',
'email': 'john.doe@example.com',
'full_name': None,
}
```
#### Unwrapping a `dict`
If we take a `dict` like `user_dict` and pass it to a function (or class) with `**user_dict`, Python will "unwrap" it. It will pass the keys and values of the `user_dict` directly as key-value arguments.
So, continuing with the `user_dict` from above, writing:
```Python
UserInDB(**user_dict)
```
Would result in something equivalent to:
```Python
UserInDB(
username="john",
password="secret",
email="john.doe@example.com",
full_name=None,
)
```
Or more exactly, using `user_dict` directly, with whatever contents it might have in the future:
```Python
UserInDB(
@@ -34,7 +90,28 @@ UserInDB(
)
```
And then adding the extra `hashed_password=hashed_password`, like in:
#### A Pydantic model from the contents of another
As in the example above we got `user_dict` from `user_in.dict()`, this code:
```Python
user_dict = user_in.dict()
UserInDB(**user_dict)
```
would be equivalent to:
```Python
UserInDB(**user_in.dict())
```
...because `user_in.dict()` is a `dict`, and then we make Python "unwrap" it by passing it to `UserInDB` prepended with `**`.
So, we get a Pydantic model from the data in another Pydantic model.
#### Unrapping a `dict` and extra keywords
And then adding the extra keyword argument `hashed_password=hashed_password`, like in:
```Python
UserInDB(**user_in.dict(), hashed_password=hashed_password)
@@ -65,7 +142,7 @@ And these models are all sharing a lot of the data and duplicating attribute nam
We could do better.
We can declare a `Userbase` model that serves as a base for our other models. And then we can make subclasses of that model that inherit its attributes (type declarations, validation, etc).
We can declare a `UserBase` model that serves as a base for our other models. And then we can make subclasses of that model that inherit its attributes (type declarations, validation, etc).
All the data conversion, validation, documentation, etc. will still work as normally.
@@ -77,4 +154,6 @@ That way, we can declare just the differences between the models (with plaintext
## Recap
Use multiple Pydantic models and inherit freely for each case. You don't need to have a single data model per entity if that entity must be able to have different "states". As the case with the user "entity" with a state including `password`, `password_hash` and no password.
Use multiple Pydantic models and inherit freely for each case.
You don't need to have a single data model per entity if that entity must be able to have different "states". As the case with the user "entity" with a state including `password`, `password_hash` and no password.

View File

@@ -0,0 +1,71 @@
The same way you can specify a response model, you can also declare the HTTP status code used for the response with the parameter `status_code` in any of the path operations:
* `@app.get()`
* `@app.post()`
* `@app.put()`
* `@app.delete()`
* etc.
```Python hl_lines="6"
{!./src/response_status_code/tutorial001.py!}
```
!!! note
Notice that `status_code` is a parameter of the "decorator" method (`get`, `post`, etc). Not of your path operation function, like all the parameters and body.
The `status_code` parameter receives a number with the HTTP status code.
It will:
* Return that status code in the response.
* Document it as such in the OpenAPI schema (and so, in the user interfaces):
<img src="/img/tutorial/response-status-code/image01.png">
## About HTTP status codes
!!! note
If you already know what HTTP status codes are, skip to the next section.
In HTTP, you send a numeric status code of 3 digits as part of the response.
These status codes have a name associated to recognize them, but the important part is the number.
In short:
* `100` and above are for "Information". You rarely use them directly.
* **`200`** and above are for "Successful" responses. These are the ones you would use the most.
* `200` is the default status code, which means everything was "OK".
* Another example would be `201`, "Created". It is commonly used after creating a new record in the database.
* `300` and above are for "Redirection".
* **`400`** and above are for "Client error" responses. These are the second type you would probably use the most.
* An example is `404`, for a "Not Found" response.
* For generic errors from the client, you can just use `400`.
* `500` and above are for server errors. You almost never use them directly. When something goes wrong at some part in your application code, or server, it will automatically return one of these status codes.
!!! tip
To know more about each status code and which code is for what, check the <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status" target="_blank"><abbr title="Mozilla Developer Network">MDN</abbr> documentation about HTTP status codes</a>.
## Shortcut to remember the names
Let's see the previous example again:
```Python hl_lines="6"
{!./src/response_status_code/tutorial001.py!}
```
`201` is the status code for "Created".
But you don't have to memorize what each of these codes mean.
You can use the convenience variables from `starlette.status`.
```Python hl_lines="2 7"
{!./src/response_status_code/tutorial002.py!}
```
They are just a convenience, they hold the same number, but that way you can use the editor's autocomplete to find them:
<img src="/img/tutorial/response-status-code/image02.png">

View File

@@ -1,4 +1,4 @@
There are many ways to handle security, authentication and autorization.
There are many ways to handle security, authentication and authorization.
And it normally is a complex and "difficult" topic.
@@ -14,9 +14,9 @@ If you don't care about any of these terms and you just need to add security wit
## OAuth2
OAuth2 is a specification that defines several ways to handle authentication and autorization.
OAuth2 is a specification that defines several ways to handle authentication and authorization.
It is quite an extensive especification and covers several complex use cases.
It is quite an extensive specification and covers several complex use cases.
It includes ways to authenticate using a "third party".

View File

@@ -128,6 +128,9 @@ UserInDB(
)
```
!!! info
For a more complete explanation of `**user_dict` check back in <a href="/tutorial/extra-models/#about-user_indict" target="_blank">the documentation for **Extra Models**</a>.
## Return the token
The response of the `token` endpoint must be a JSON object.

View File

@@ -12,7 +12,9 @@ You can easily adapt it to any database supported by SQLAlchemy, like:
* Oracle
* Microsoft SQL Server, etc.
In this example, we'll use **PostgreSQL**.
In this example, we'll use **SQLite**, because it uses a single file and Python has integrated support. So, you can copy this example and run it as is.
Later, for your production application, you might want to use a database server like **PostgreSQL**.
!!! note
Notice that most of the code is the standard `SQLAlchemy` code you would use with any framework.
@@ -23,30 +25,58 @@ In this example, we'll use **PostgreSQL**.
For now, don't pay attention to the rest, only the imports:
```Python hl_lines="3 4 5"
```Python hl_lines="2 3 4"
{!./src/sql_databases/tutorial001.py!}
```
## Define the database
Define the database that SQLAlchemy should connect to:
Define the database that SQLAlchemy should "connect" to:
```Python hl_lines="8"
```Python hl_lines="7"
{!./src/sql_databases/tutorial001.py!}
```
In this example, we are "connecting" to a SQLite database (opening a file with the SQLite database).
The file will be located at the same directory in the file `test.db`. That's why the last part is `./test.db`.
If you were using a **PostgreSQL** database instead, you would just have to uncomment the line:
```Python
SQLALCHEMY_DATABASE_URI = "postgresql://user:password@postgresserver/db"
```
...and adapt it with your database data and credentials (equivalently for MySQL, MariaDB or any other).
!!! tip
This is the main line that you would have to modify if you wanted to use a different database than **PostgreSQL**.
This is the main line that you would have to modify if you wanted to use a different database.
## Create the SQLAlchemy `engine`
```Python hl_lines="10"
```Python hl_lines="10 11 12"
{!./src/sql_databases/tutorial001.py!}
```
### Note
The argument:
```Python
connect_args={"check_same_thread": False}
```
...is needed only for `SQLite`. It's not needed for other databases.
!!! info "Technical Details"
That argument `check_same_thread` is there mainly to be able to run the tests that cover this example.
## Create a `scoped_session`
```Python hl_lines="11 12 13"
```Python hl_lines="13 14 15"
{!./src/sql_databases/tutorial001.py!}
```
@@ -55,9 +85,9 @@ Define the database that SQLAlchemy should connect to:
This `scoped_session` is a feature of SQLAlchemy.
The resulting object, the `db_session` can then be used anywhere a a normal SQLAlchemy session.
The resulting object, the `db_session` can then be used anywhere as a normal SQLAlchemy session.
It can be used as a global because it is implemented to work independently on each "<abbr title="A sequence of code being executed by the program, while at the same time, or at intervals, there can be others being executed too.">thread</abbr>", so the actions you perform with it in one path operation function won't affect the actions performed (possibly concurrently) by other path operation functions.
It can be used as a "global" variable because it is implemented to work independently on each "<abbr title="A sequence of code being executed by the program, while at the same time, or at intervals, there can be others being executed too.">thread</abbr>", so the actions you perform with it in one path operation function won't affect the actions performed (possibly concurrently) by other path operation functions.
## Create a `CustomBase` model
@@ -65,17 +95,17 @@ This is more of a trick to facilitate your life than something required.
But by creating this `CustomBase` class and inheriting from it, your models will have automatic `__tablename__` attributes (that are required by SQLAlchemy).
That way you don't have to declare them explicitly.
That way you don't have to declare them explicitly in every model.
So, your models will behave very similarly to, for example, Flask-SQLAlchemy.
```Python hl_lines="16 17 18 19 20"
```Python hl_lines="18 19 20 21 22"
{!./src/sql_databases/tutorial001.py!}
```
## Create the SQLAlchemy `Base` model
```Python hl_lines="23"
```Python hl_lines="25"
{!./src/sql_databases/tutorial001.py!}
```
@@ -85,15 +115,36 @@ Now this is finally code specific to your app.
Here's a user model that will be a table in the database:
```Python hl_lines="26 27 28 29 30"
```Python hl_lines="28 29 30 31 32"
{!./src/sql_databases/tutorial001.py!}
```
## Initialize your application
In a very simplistic way, initialize your database (create the tables, etc) and make sure you have a first user:
```Python hl_lines="35 37 38 39 40 41"
{!./src/sql_databases/tutorial001.py!}
```
### Note
Normally you would probably initialize your database (create tables, etc) with <a href="https://alembic.sqlalchemy.org/en/latest/" target="_blank">Alembic</a>.
And you would also use Alembic for migrations (that's its main job). For whenever you change the structure of your database, add a new column, a new table, etc.
The same way, you would probably make sure there's a first user in an external script that runs before your application, or as part of the application startup.
In this example we are doing those two operations in a very simple way, directly in the code, to focus on the main points.
Also, as all the functionality is self-contained in the same code, you can copy it and run it directly, and it will work as is.
## Get a user
By creating a function that is only dedicated to getting your user from a `username` (or any other parameter) independent of your path operation function, you can more easily re-use it in multiple parts and also add <abbr title="Automated test, written in code, that checks if another piece of code is working correctly.">unit tests</abbr> for it:
By creating a function that is only dedicated to getting your user from a `user_id` (or any other parameter) independent of your path operation function, you can more easily re-use it in multiple parts and also add <abbr title="Automated tests, written in code, that check if another piece of code is working correctly.">unit tests</abbr> for it:
```Python hl_lines="33 34"
```Python hl_lines="45 46"
{!./src/sql_databases/tutorial001.py!}
```
@@ -103,7 +154,7 @@ Now, finally, here's the standard **FastAPI** code.
Create your app and path operation function:
```Python hl_lines="38 41 42 43 44"
```Python hl_lines="50 53 54 55 56"
{!./src/sql_databases/tutorial001.py!}
```
@@ -113,25 +164,25 @@ We can just call `get_user` directly from inside of the path operation function
## Create the path operation function
Here we are using SQLAlchemy code inside of the path operation function, and it in turn will go and communicate with an external database.
Here we are using SQLAlchemy code inside of the path operation function, and in turn it will go and communicate with an external database.
That could potentially require some "waiting".
But as SQLAlchemy doesn't have compatibility for using `await`, as would be with something like:
But as SQLAlchemy doesn't have compatibility for using `await` directly, as would be with something like:
```Python
user = await get_user(username, db_session)
user = await get_user(db_session, user_id=user_id)
```
...and instead we are using:
```Python
user = get_user(username, db_session)
user = get_user(db_session, user_id=user_id)
```
Then we should declare the path operation without `async def`, just with a normal `def`:
```Python hl_lines="42"
```Python hl_lines="54"
{!./src/sql_databases/tutorial001.py!}
```
@@ -140,3 +191,47 @@ Then we should declare the path operation without `async def`, just with a norma
Because we are using SQLAlchemy directly and we don't require any kind of plug-in for it to work with **FastAPI**, we could integrate database <abbr title="Automatically updating the database to have any new column we define in our models.">migrations</abbr> with <a href="https://alembic.sqlalchemy.org" target="_blank">Alembic</a> directly.
You would probably want to declare your database and models in a different file or set of files, this would allow Alembic to import it and use it without even needing to have **FastAPI** installed for the migrations.
## Check it
You can copy this code and use it as is.
!!! info
In fact, the code shown here is part of the tests. As most of the code in these docs.
You can copy it, let's say, to a file `main.py`.
Then you can run it with Uvicorn:
```bash
uvicorn main:app --debug
```
And then, you can open your browser at <a href="http://127.0.0.1:8000/docs" target="_blank">http://127.0.0.1:8000/docs</a>.
And you will be able to interact with your **FastAPI** application, reading data from a real database:
<img src="/img/tutorial/sql-databases/image01.png">
## Response schema and security
This section has the minimum code to show how it works and how you can integrate SQLAlchemy with FastAPI.
But it is recommended that you also create a response model with Pydantic, as described in the section about <a href="/tutorial/extra-models/" target="_blank">Extra Models</a>.
That way you will document the schema of the responses of your API, and you will be able to limit/filter the returned data.
Limiting the returned data is important for security, as for example, you shouldn't be returning the `hashed_password` to the clients.
That's something that you can improve in this example application, here's the current response data:
```JSON
{
"is_active": true,
"hashed_password": "notreallyhashed",
"email": "johndoe@example.com",
"id": 1
}
```

View File

@@ -1,6 +1,6 @@
"""FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
__version__ = "0.1.17"
__version__ = "0.3.0"
from .applications import FastAPI
from .routing import APIRouter

View File

@@ -12,38 +12,70 @@ def jsonable_encoder(
exclude: Set[str] = set(),
by_alias: bool = False,
include_none: bool = True,
custom_encoder: dict = {},
sqlalchemy_safe: bool = True,
) -> Any:
if isinstance(obj, BaseModel):
encoder = getattr(obj.Config, "json_encoders", custom_encoder)
return jsonable_encoder(
obj.dict(include=include, exclude=exclude, by_alias=by_alias),
include_none=include_none,
custom_encoder=encoder,
sqlalchemy_safe=sqlalchemy_safe,
)
if isinstance(obj, Enum):
return obj.value
if isinstance(obj, (str, int, float, type(None))):
return obj
if isinstance(obj, dict):
return {
jsonable_encoder(
key, by_alias=by_alias, include_none=include_none
): jsonable_encoder(value, by_alias=by_alias, include_none=include_none)
for key, value in obj.items()
if value is not None or include_none
}
encoded_dict = {}
for key, value in obj.items():
if (
(
not sqlalchemy_safe
or (not isinstance(key, str))
or (not key.startswith("_sa"))
)
and (value is not None or include_none)
and ((include and key in include) or key not in exclude)
):
encoded_key = jsonable_encoder(
key,
by_alias=by_alias,
include_none=include_none,
custom_encoder=custom_encoder,
sqlalchemy_safe=sqlalchemy_safe,
)
encoded_value = jsonable_encoder(
value,
by_alias=by_alias,
include_none=include_none,
custom_encoder=custom_encoder,
sqlalchemy_safe=sqlalchemy_safe,
)
encoded_dict[encoded_key] = encoded_value
return encoded_dict
if isinstance(obj, (list, set, frozenset, GeneratorType, tuple)):
return [
jsonable_encoder(
item,
include=include,
exclude=exclude,
by_alias=by_alias,
include_none=include_none,
encoded_list = []
for item in obj:
encoded_list.append(
jsonable_encoder(
item,
include=include,
exclude=exclude,
by_alias=by_alias,
include_none=include_none,
custom_encoder=custom_encoder,
sqlalchemy_safe=sqlalchemy_safe,
)
)
for item in obj
]
return encoded_list
errors = []
try:
encoder = ENCODERS_BY_TYPE[type(obj)]
if custom_encoder and type(obj) in custom_encoder:
encoder = custom_encoder[type(obj)]
else:
encoder = ENCODERS_BY_TYPE[type(obj)]
return encoder(obj)
except KeyError as e:
errors.append(e)
@@ -56,4 +88,10 @@ def jsonable_encoder(
except Exception as e:
errors.append(e)
raise ValueError(errors)
return jsonable_encoder(data, by_alias=by_alias, include_none=include_none)
return jsonable_encoder(
data,
by_alias=by_alias,
include_none=include_none,
custom_encoder=custom_encoder,
sqlalchemy_safe=sqlalchemy_safe,
)

View File

@@ -18,7 +18,7 @@ from starlette.exceptions import HTTPException
from starlette.formparsers import UploadFile
from starlette.requests import Request
from starlette.responses import JSONResponse, Response
from starlette.routing import get_name, request_response
from starlette.routing import compile_path, get_name, request_response
from starlette.status import HTTP_422_UNPROCESSABLE_ENTITY
@@ -149,9 +149,7 @@ class APIRoute(routing.Route):
self.include_in_schema = include_in_schema
self.content_type = content_type
self.path_regex, self.path_format, self.param_convertors = self.compile_path(
path
)
self.path_regex, self.path_format, self.param_convertors = compile_path(path)
assert inspect.isfunction(endpoint) or inspect.ismethod(
endpoint
), f"An endpoint must be a function or method"

View File

@@ -11,7 +11,10 @@ theme:
repo_name: tiangolo/fastapi
repo_url: https://github.com/tiangolo/fastapi
edit_uri: ""
edit_uri: ''
google_analytics:
- 'UA-133183413-1'
- 'auto'
nav:
- FastAPI: 'index.md'
@@ -33,6 +36,7 @@ nav:
- Header Parameters: 'tutorial/header-params.md'
- Response Model: 'tutorial/response-model.md'
- Extra Models: 'tutorial/extra-models.md'
- Response Status Code: 'tutorial/response-status-code.md'
- Form Data: 'tutorial/request-forms.md'
- Request Files: 'tutorial/request-files.md'
- Request Forms and Files: 'tutorial/request-forms-and-files.md'
@@ -60,6 +64,7 @@ nav:
- Project Generation - Template: 'project-generation.md'
- Alternatives, Inspiration and Comparisons: 'alternatives.md'
- Benchmarks: 'benchmarks.md'
- Release Notes: release-notes.md
markdown_extensions:
- markdown.extensions.codehilite:

View File

@@ -19,8 +19,8 @@ classifiers = [
"Topic :: Internet :: WWW/HTTP :: HTTP Servers",
]
requires = [
"starlette >=0.9.7",
"pydantic >=0.17"
"starlette >=0.9.11,<=0.10.1",
"pydantic >=0.17,<=0.18.2"
]
description-file = "README.md"
requires-python = ">=3.6"
@@ -36,7 +36,8 @@ test = [
"black",
"isort",
"requests",
"email_validator"
"email_validator",
"sqlalchemy"
]
doc = [
"mkdocs",

5
scripts/test.sh Normal file → Executable file
View File

@@ -6,6 +6,11 @@ set -x
export VERSION_SCRIPT="import sys; print('%s.%s' % sys.version_info[0:2])"
export PYTHON_VERSION=`python -c "$VERSION_SCRIPT"`
# Remove temporary DB
if [ -f ./test.db ]; then
rm ./test.db
fi
export PYTHONPATH=./docs/src
pytest --cov=fastapi --cov=tests --cov=docs/src --cov-report=term-missing ${@}
mypy fastapi --disallow-untyped-defs

View File

@@ -0,0 +1,34 @@
from datetime import datetime, timezone
from fastapi import FastAPI
from pydantic import BaseModel
from starlette.testclient import TestClient
class ModelWithDatetimeField(BaseModel):
dt_field: datetime
class Config:
json_encoders = {
datetime: lambda dt: dt.replace(
microsecond=0, tzinfo=timezone.utc
).isoformat()
}
app = FastAPI()
model = ModelWithDatetimeField(dt_field=datetime(2019, 1, 1, 8))
@app.get("/model", response_model=ModelWithDatetimeField)
def get_model():
return model
client = TestClient(app)
def test_dt():
with client:
response = client.get("/model")
assert response.json() == {"dt_field": "2019-01-01T08:00:00+00:00"}

View File

@@ -1,5 +1,9 @@
from datetime import datetime, timezone
from enum import Enum
import pytest
from fastapi.encoders import jsonable_encoder
from pydantic import BaseModel
class Person:
@@ -32,6 +36,29 @@ class Unserializable:
raise NotImplementedError()
class ModelWithCustomEncoder(BaseModel):
dt_field: datetime
class Config:
json_encoders = {
datetime: lambda dt: dt.replace(
microsecond=0, tzinfo=timezone.utc
).isoformat()
}
class RoleEnum(Enum):
admin = "admin"
normal = "normal"
class ModelWithConfig(BaseModel):
role: RoleEnum = None
class Config:
use_enum_values = True
def test_encode_class():
person = Person(name="Foo")
pet = Pet(owner=person, name="Firulais")
@@ -48,3 +75,13 @@ def test_encode_unsupported():
unserializable = Unserializable()
with pytest.raises(ValueError):
jsonable_encoder(unserializable)
def test_encode_custom_json_encoders_model():
model = ModelWithCustomEncoder(dt_field=datetime(2019, 1, 1, 8))
assert jsonable_encoder(model) == {"dt_field": "2019-01-01T08:00:00+00:00"}
def test_encode_model_with_config():
model = ModelWithConfig(role=RoleEnum.admin)
assert jsonable_encoder(model) == {"role": "admin"}

View File

@@ -1,7 +1,7 @@
import pytest
from starlette.testclient import TestClient
from bigger_applications.app.tutorial003 import app
from bigger_applications.app.main import app
client = TestClient(app)
@@ -17,10 +17,24 @@ openapi_schema = {
"content": {"application/json": {"schema": {}}},
}
},
"tags": ["users"],
"summary": "Read Users Get",
"operationId": "read_users_users__get",
}
},
"/users/me": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
}
},
"tags": ["users"],
"summary": "Read User Me Get",
"operationId": "read_user_me_users_me_get",
}
},
"/users/{username}": {
"get": {
"responses": {
@@ -39,6 +53,7 @@ openapi_schema = {
},
},
},
"tags": ["users"],
"summary": "Read User Get",
"operationId": "read_user_users__username__get",
"parameters": [
@@ -51,18 +66,6 @@ openapi_schema = {
],
}
},
"/users/me": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
}
},
"summary": "Read User Me Get",
"operationId": "read_user_me_users_me_get",
}
},
"/items/": {
"get": {
"responses": {
@@ -71,6 +74,7 @@ openapi_schema = {
"content": {"application/json": {"schema": {}}},
}
},
"tags": ["items"],
"summary": "Read Items Get",
"operationId": "read_items_items__get",
}
@@ -93,6 +97,7 @@ openapi_schema = {
},
},
},
"tags": ["items"],
"summary": "Read Item Get",
"operationId": "read_item_items__item_id__get",
"parameters": [

View File

View File

@@ -0,0 +1,88 @@
from starlette.testclient import TestClient
from sql_databases.tutorial001 import app
client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"paths": {
"/users/{user_id}": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Read User Get",
"operationId": "read_user_users__user_id__get",
"parameters": [
{
"required": True,
"schema": {"title": "User_Id", "type": "integer"},
"name": "user_id",
"in": "path",
}
],
}
}
},
"components": {
"schemas": {
"ValidationError": {
"title": "ValidationError",
"required": ["loc", "msg", "type"],
"type": "object",
"properties": {
"loc": {
"title": "Location",
"type": "array",
"items": {"type": "string"},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
},
},
"HTTPValidationError": {
"title": "HTTPValidationError",
"type": "object",
"properties": {
"detail": {
"title": "Detail",
"type": "array",
"items": {"$ref": "#/components/schemas/ValidationError"},
}
},
},
}
},
}
def test_openapi_schema():
response = client.get("/openapi.json")
assert response.status_code == 200
assert response.json() == openapi_schema
def test_first_user():
response = client.get("/users/1")
assert response.status_code == 200
assert response.json() == {
"is_active": True,
"hashed_password": "notreallyhashed",
"email": "johndoe@example.com",
"id": 1,
}