Compare commits

...

48 Commits

Author SHA1 Message Date
Sebastián Ramírez
033bc2a6c9 🔖 Release 0.35.0 2019-08-07 14:12:15 -05:00
Sebastián Ramírez
28d3b9f783 📝 Update release notes 2019-08-07 14:09:50 -05:00
Pablo Marti
0c55553328 ✏️ Fix typo in assert statement (#419) 2019-08-07 14:03:11 -05:00
Bronsen
b66056aa34 📝 Fix plural-s without apostrophe in docs (#411) 2019-08-07 14:01:31 -05:00
Sebastián Ramírez
4f10b8b98d 📝 Update release notes 2019-08-07 13:57:41 -05:00
Koudai Aono
06eb421934 Fix request body parsing with Union (#400) 2019-08-07 13:55:33 -05:00
Sebastián Ramírez
bf229ad5d8 🔖 Release 0.34.0 upgrading Starlette 2019-08-06 07:22:06 -05:00
Sebastián Ramírez
d0319001be 📝 Update Release Notes 2019-08-06 07:13:24 -05:00
David De Sousa
c4682af13d ⬆️ Upgrade Starlette max range to 0.12.7 (#367) 2019-08-06 07:10:29 -05:00
Sebastián Ramírez
6ca3ce80e4 📝 Update release notes 2019-07-12 19:15:21 -05:00
Sebastián Ramírez
25e85c8522 Add test from @dmontagu in #333 for duplicate models (#385) 2019-07-12 19:13:28 -05:00
Sebastián Ramírez
6bf3ab3b7a 🔖 Release 0.33.0, including Pydantic 0.30.0 2019-07-12 19:01:27 -05:00
Sebastián Ramírez
f5ea5eef2a 📝 Update release notes 2019-07-12 18:58:09 -05:00
James Kaplan
46a986cacf ⬆️ Upgrade Pydantic to 0.30 (#384)
* bump pydantic to 0.30

* 📌 Pin Pydantic to 0.30 as 0.31 hasn't been released
2019-07-12 18:56:25 -05:00
Sebastián Ramírez
e620aeb46d 🔖 Release 0.32.0, as PR ##347 might be a breaking change
in some specific cases
2019-07-12 18:32:30 -05:00
Sebastián Ramírez
d1e2e46b80 🔖 Release 0.31.1 2019-07-12 18:30:54 -05:00
Sebastián Ramírez
b1c4a8acd5 📝 Update release notes 2019-07-12 18:29:49 -05:00
Martino Mensio
362e2cdc79 📝 Fix small typo in docs for features (#380) 2019-07-12 18:28:07 -05:00
Sebastián Ramírez
93e6a08acd 📝 Update release notes 2019-07-12 18:25:04 -05:00
Ben Williams
3ec4342282 📝 Change limit default parameter to 10 in Query docs (#366)
Rest of docs reference 10 as the default.
2019-07-12 18:22:21 -05:00
Sebastián Ramírez
dc483478eb 📝 Update release notes 2019-07-12 18:20:02 -05:00
Chris Withers
bdd251a05b 📝 Tweak wording on OAuth2 scopes (#371) 2019-07-12 18:17:34 -05:00
Sebastián Ramírez
195559ccba 📝 Update release notes 2019-06-28 21:29:29 +02:00
Sebastián Ramírez
9a71672a95 📝 Update enum examples to use str, and improve Swagger UI in examples (#351) 2019-06-28 21:27:27 +02:00
Sebastián Ramírez
7e48be1561 📝 Update release notes 2019-06-28 20:57:14 +02:00
Sebastián Ramírez
508f9ce954 🐛 Fix regression, Swagger UI with deep linking (#350) 2019-06-28 20:56:48 +02:00
Sebastián Ramírez
afbdf2546f 📝 Update release notes 2019-06-28 20:16:53 +02:00
Sebastián Ramírez
62df417807 Add test for templates in include_router path (#349) 2019-06-28 20:15:17 +02:00
Sebastián Ramírez
09d2747a70 📝 Update release notes 2019-06-28 20:00:24 +02:00
Sebastián Ramírez
d3ea6f7514 📝 Add note to docs about including same router multiple times (#348) 2019-06-28 19:54:49 +02:00
Sebastián Ramírez
02187636ea 📝 Update release notes 2019-06-28 19:40:31 +02:00
Sebastián Ramírez
687065509b 🏗️ Fix same function names in different modules with composite bodies (#347)
* 🏗️ Implement unique IDs for dynamic models

like those used for composite bodies and responses. IDs based on path (not only on function name, as it can be duplicated in a different module).

*  Add tests for same function name and composite body

*  Update OpenAPI in tests with new dynamic model ID generation
2019-06-28 19:35:16 +02:00
Sebastián Ramírez
b30cca8e9e 🔖 Release 0.31.0, upgrading Pydantic to 0.29 2019-06-28 17:01:04 +02:00
Sebastián Ramírez
3906777065 📝 Update release notes 2019-06-28 12:36:52 +02:00
Sebastián Ramírez
d60a10fa59 ⬆️ Upgrade support for Pydantic to 0.29 (#344) 2019-06-28 12:36:08 +02:00
Sebastián Ramírez
54368e7b22 🔖 Release 0.30.1 2019-06-28 09:39:29 +02:00
Sebastián Ramírez
acc556e416 📝 Update release notes 2019-06-27 22:44:54 +02:00
Sebastián Ramírez
700585f99d 📝 Add section about external links to docs (#341) 2019-06-27 22:44:15 +02:00
Sebastián Ramírez
4c2993f353 📝 Update release notes 2019-06-27 21:53:59 +02:00
Sebastián Ramírez
ea9277aab4 🔥 Remove Pipfile.lock from the repository (each contributor can keep his/her locally)
* 🔥 Remove Pipfile.lock

Being a library, it should work independent of locking of dependency tree.

The Pipfile (and Pipfile.lock) is only used locally for development of FastAPI itself, it doesn't affect final users (that is controlled with pyproject.toml).

The Pipfile.lock adds unnecessary noise to PRs that update/upgrade development packages, and the locking is not the same in all environments (e.g. Linux, Mac, and Windows).

Each FastAPI contributor (developing FastAPI itself) can have his/her own Pipfile.lock, but it doesn't have to be in git.

* 🙈 Add Pipfile.lock to .gitignore
2019-06-27 21:51:38 +02:00
Sebastián Ramírez
8d86fca027 📝 Update release notes 2019-06-27 21:33:07 +02:00
Sebastián Ramírez
fc0716a7dd 📝 Update Docs: Help FastAPI (#339) 2019-06-27 21:32:27 +02:00
Sebastián Ramírez
1e593dc4d4 📝 Update release notes 2019-06-27 20:53:54 +02:00
Sebastián Ramírez
dcc1e1bcf8 ♻️ Refine internal type declarations and logic around them (#338) 2019-06-27 20:51:17 +02:00
Sebastián Ramírez
06eb775c63 📝 Update release notes 2019-06-27 13:27:53 +02:00
Camila Gutierrez
ab77c069d4 📝 Update, simplify, and clarify the SQL tutorial (#331) 2019-06-27 13:25:16 +02:00
Sebastián Ramírez
46fffc0e94 📝 Update release notes 2019-06-27 13:22:56 +02:00
cyril
1c2cdb97e9 📝 Add online SQLite browsers to docs (#330) 2019-06-27 13:12:38 +02:00
56 changed files with 833 additions and 1160 deletions

1
.gitignore vendored
View File

@@ -12,3 +12,4 @@ coverage.xml
.netlify
test.db
log.txt
Pipfile.lock

View File

@@ -25,8 +25,8 @@ sqlalchemy = "*"
uvicorn = "*"
[packages]
starlette = "==0.12.0"
pydantic = "==0.28.0"
starlette = "==0.12.7"
pydantic = "==0.30.0"
databases = {extras = ["sqlite"],version = "*"}
hypercorn = "*"

987
Pipfile.lock generated
View File

@@ -1,987 +0,0 @@
{
"_meta": {
"hash": {
"sha256": "14f3b2d3a0457913244d4dc96fbf18ed356fbb43fbd10eae043270531afa61bb"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.6"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"aiocontextvars": {
"hashes": [
"sha256:885daf8261818767d8f7cbd79f9d4482d118f024b6586ef6e67980236a27bfa3",
"sha256:f027372dc48641f683c559f247bd84962becaacdc9ba711d583c3871fb5652aa"
],
"version": "==0.2.2"
},
"aiosqlite": {
"hashes": [
"sha256:ad84fbd7516ca7065d799504fc41d6845c938e5306d1b7dd960caaeda12e22a9"
],
"version": "==0.10.0"
},
"contextvars": {
"hashes": [
"sha256:f38c908aaa59c14335eeea12abea5f443646216c4e29380d7bf34d2018e2c39e"
],
"markers": "python_version < '3.7'",
"version": "==2.4"
},
"databases": {
"extras": [
"sqlite"
],
"hashes": [
"sha256:d365cff2035c5177ef5fd8c5abf6671da01189521da64848a01251c870daf48f"
],
"index": "pypi",
"version": "==0.2.2"
},
"dataclasses": {
"hashes": [
"sha256:454a69d788c7fda44efd71e259be79577822f5e3f53f029a22d08004e951dc9f",
"sha256:6988bd2b895eef432d562370bb707d540f32f7360ab13da45340101bc2307d84"
],
"markers": "python_version < '3.7'",
"version": "==0.6"
},
"h11": {
"hashes": [
"sha256:33d4bca7be0fa039f4e84d50ab00531047e53d6ee8ffbc83501ea602c169cae1",
"sha256:4bc6d6a1238b7615b266ada57e0618568066f57dd6fa967d1290ec9309b2f2f1"
],
"version": "==0.9.0"
},
"h2": {
"hashes": [
"sha256:c8f387e0e4878904d4978cd688a3195f6b169d49b1ffa572a3d347d7adc5e09f",
"sha256:fd07e865a3272ac6ef195d8904de92dc7b38dc28297ec39cfa22716b6d62e6eb"
],
"version": "==3.1.0"
},
"hpack": {
"hashes": [
"sha256:0edd79eda27a53ba5be2dfabf3b15780928a0dff6eb0c60a3d6767720e970c89",
"sha256:8eec9c1f4bfae3408a3f30500261f7e6a65912dc138526ea054f9ad98892e9d2"
],
"version": "==3.0.0"
},
"hypercorn": {
"hashes": [
"sha256:cfe7811a93ab7bc22c8a0d5514a2a7a512e812c1e4ee13b9731b705b79d4d453",
"sha256:f2577806223fa44d57d6f136b6c37a046794f961252699aec8afb15f35d226d5"
],
"index": "pypi",
"version": "==0.5.4"
},
"hyperframe": {
"hashes": [
"sha256:5187962cb16dcc078f23cb5a4b110098d546c3f41ff2d4038a9896893bbd0b40",
"sha256:a9f5c17f2cc3c719b917c4f33ed1c61bd1f8dfac4b1bd23b7c80b3400971b41f"
],
"version": "==5.2.0"
},
"immutables": {
"hashes": [
"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.9"
},
"pydantic": {
"hashes": [
"sha256:3aafa1b58181c53a8e2971dc3c6f45945ddd18192b6f9c8e17f5ef2adcdf3987",
"sha256:60fe5aa17ecb5ba949e7e1e1f9b9500dae780d6c79c798bfd78ac5dd9f9d9517",
"sha256:92e4fb2917e4837e53edfee9f99c9075c152f9b9fe2b19d047b8fb25ed9bc089",
"sha256:98c5faf742baee5cbbe9ff609829df6ff4234863c4a992f8d46679df4d68aeab",
"sha256:bc4a051b81f31597efc96b4bf6a3aa75ea95ca0a87c4666ad5638be373e0c66a",
"sha256:d3d29768ae85c1333da5d10cbe793c6a01dcb1ec8ff86f1fbe2c588089bc11ea"
],
"index": "pypi",
"version": "==0.28.0"
},
"pytoml": {
"hashes": [
"sha256:ca2d0cb127c938b8b76a9a0d0f855cf930c1d50cc3a0af6d3595b566519a1013"
],
"version": "==0.1.20"
},
"sqlalchemy": {
"hashes": [
"sha256:c30925d60af95443458ebd7525daf791f55762b106049ae71e18f8dd58084c2f"
],
"version": "==1.3.5"
},
"starlette": {
"hashes": [
"sha256:d313433ef5cc38e0a276b59688ca2b11b8f031c78808c1afdf9d55cb86f34590"
],
"index": "pypi",
"version": "==0.12.0"
},
"typing-extensions": {
"hashes": [
"sha256:07b2c978670896022a43c4b915df8958bec4a6b84add7f2c87b2b728bda3ba64",
"sha256:f3f0e67e1d42de47b5c67c32c9b26641642e9170fe7e292991793705cd5fef7c",
"sha256:fb2cd053238d33a8ec939190f30cfd736c00653a85a2919415cecf7dc3d9da71"
],
"version": "==3.7.2"
},
"wsproto": {
"hashes": [
"sha256:2b870f5b5b4a6d23dce080a4ee1cbb119b2378f82593bd6d66ae2cbd72a7c0ad",
"sha256:ed222c812aaea55d72d18a87df429cfd602e15b6c992a07a53b495858f083a14"
],
"version": "==0.14.1"
}
},
"develop": {
"appdirs": {
"hashes": [
"sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92",
"sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"
],
"version": "==1.4.3"
},
"atomicwrites": {
"hashes": [
"sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4",
"sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"
],
"version": "==1.3.0"
},
"attrs": {
"hashes": [
"sha256:69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79",
"sha256:f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399"
],
"version": "==19.1.0"
},
"autoflake": {
"hashes": [
"sha256:6b59e5b9b82e30077499578856282debb81186d10b4f899e8c2e1d616cdef973"
],
"index": "pypi",
"version": "==1.3"
},
"backcall": {
"hashes": [
"sha256:38ecd85be2c1e78f77fd91700c76e14667dc21e2713b63876c0eb901196e01e4",
"sha256:bbbf4b1e5cd2bdb08f915895b51081c041bac22394fdfcfdfbe9f14b77c08bf2"
],
"version": "==0.1.0"
},
"better-exceptions": {
"hashes": [
"sha256:bf79c87659bc849989d726bf0e4a2100edefe7eded112d201f54fe08467fdf63",
"sha256:c196cad849de615abb9f6eb67ca1b83f33b938818f0e2fe8fa157b22aeb7b992"
],
"index": "pypi",
"version": "==0.2.2"
},
"black": {
"hashes": [
"sha256:09a9dcb7c46ed496a9850b76e4e825d6049ecd38b611f1224857a79bd985a8cf",
"sha256:68950ffd4d9169716bcb8719a56c07a2f4485354fec061cdd5910aa07369731c"
],
"index": "pypi",
"version": "==19.3b0"
},
"bleach": {
"hashes": [
"sha256:213336e49e102af26d9cde77dd2d0397afabc5a6bf2fed985dc35b5d1e285a16",
"sha256:3fdf7f77adcf649c9911387df51254b813185e32b2c6619f690b593a617e19fa"
],
"version": "==3.1.0"
},
"certifi": {
"hashes": [
"sha256:046832c04d4e752f37383b628bc601a7ea7211496b4638f6514d0e5b9acc4939",
"sha256:945e3ba63a0b9f577b1395204e13c3a231f9bc0223888be653286534e5873695"
],
"version": "==2019.6.16"
},
"chardet": {
"hashes": [
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
],
"version": "==3.0.4"
},
"click": {
"hashes": [
"sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13",
"sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"
],
"version": "==7.0"
},
"coverage": {
"hashes": [
"sha256:0402b1822d513d0231589494bceddb067d20581f5083598c451b56c684b0e5d6",
"sha256:0644e28e8aea9d9d563607ee8b7071b07dd57a4a3de11f8684cd33c51c0d1b93",
"sha256:0874a283686803884ec0665018881130604956dbaa344f2539c46d82cbe29eda",
"sha256:0988c3837df4bc371189bb3425d5232cf150055452034c232dda9cbe04f9c38e",
"sha256:20bc3205b3100956bb72293fabb97f0ed972c81fed10b3251c90c70dcb0599ab",
"sha256:2cc9142a3367e74eb6b19d58c53ebb1dfd7336b91cdcc91a6a2888bf8c7af984",
"sha256:3ae9a0a59b058ce0761c3bd2c2d66ecb2ee2b8ac592620184370577f7a546fb3",
"sha256:3b2e30b835df58cb973f478d09f3d82e90c98c8e5059acc245a8e4607e023801",
"sha256:401e9b04894eb1498c639c6623ee78a646990ce5f095248e2440968aafd6e90e",
"sha256:41ec5812d5decdaa72708be3018e7443e90def4b5a71294236a4df192cf9eab9",
"sha256:475769b638a055e75b3d3219e054fe2a023c0b077ff15bff6c95aba7e93e6cac",
"sha256:61424f4e2e82c4129a4ba71e10ebacb32a9ecd6f80de2cd05bdead6ba75ed736",
"sha256:811969904d4dd0bee7d958898be8d9d75cef672d9b7e7db819dfeac3d20d2d0c",
"sha256:86224bb99abfd672bf2f9fcecad5e8d7a3fa94f7f71513f2210460a0350307cd",
"sha256:9a238a20a3af00665f8381f7e53e9c606f9bb652d2423f6b822f6cb790d887e8",
"sha256:a23b3fbc14d4e6182ecebfd22f3729beef0636d151d94764a1c28330d185e4e5",
"sha256:ac162b4ebe51b7a2b7f5e462c4402802633eb81e77c94f8a7c1ed8a556e72c75",
"sha256:b6187378726c84365bf297b5dcdae8789b6a5823b200bea23797777e5a63be09",
"sha256:bcd5723d905ed4a825f17410a53535f880b6d7548ae3d89078db7b1ceefcd853",
"sha256:c48a4f9c5fb385269bb7fbaf9c1326a94863b65ec7f5c96b2ea56b252f01ad08",
"sha256:cd40199d6f1c29c85b170d25589be9a97edff8ee7e62be180a2a137823896030",
"sha256:d1bc331a7d069485ac1d8c25a0ea1f6aab6cb2a87146fb652222481c1bddc9ff",
"sha256:d7e0cdc249aa0f94aa2e531b03999ddaf03a10b4fa090a894712d4c8066abd89",
"sha256:e9ee8fcd8e067fcc5d7276d46e07e863102b70a52545ef4254df1ff0893ce75f",
"sha256:eb313c23d983b7810504f42104e8dcd1c7ccdda8fbaab82aab92ab79fea19345",
"sha256:f9cfd478654b509941b85ed70f870f5e3c74678f566bec12fd26545e5340ba47",
"sha256:fae1fa144034d021a52cb9ea200eb8dedf91869c6df8202ad5d149b41ed91cc8"
],
"version": "==5.0a5"
},
"decorator": {
"hashes": [
"sha256:86156361c50488b84a3f148056ea716ca587df2f0de1d34750d35c21312725de",
"sha256:f069f3a01830ca754ba5258fde2278454a0b5b79e0d7f5c13b3b97e57d4acff6"
],
"version": "==4.4.0"
},
"defusedxml": {
"hashes": [
"sha256:6687150770438374ab581bb7a1b327a847dd9c5749e396102de3fad4e8a3ef93",
"sha256:f684034d135af4c6cbb949b8a4d2ed61634515257a67299e5f940fbaa34377f5"
],
"version": "==0.6.0"
},
"dnspython": {
"hashes": [
"sha256:36c5e8e38d4369a08b6780b7f27d790a292b2b08eea01607865bf0936c558e01",
"sha256:f69c21288a962f4da86e56c4905b49d11aba7938d3d740e80d9e366ee4f1632d"
],
"version": "==1.16.0"
},
"docutils": {
"hashes": [
"sha256:02aec4bd92ab067f6ff27a38a38a41173bf01bed8f89157768c1573f53e474a6",
"sha256:51e64ef2ebfb29cae1faa133b3710143496eca21c530f3f71424d77687764274",
"sha256:7a4bd47eaf6596e1295ecb11361139febe29b084a87bf005bf899f9a42edc3c6"
],
"version": "==0.14"
},
"email-validator": {
"hashes": [
"sha256:79966e318d6d68fed359c90f8f19d242bcc178b724011f1c07145bd093da6cc7"
],
"index": "pypi",
"version": "==1.0.4"
},
"entrypoints": {
"hashes": [
"sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19",
"sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"
],
"version": "==0.3"
},
"flake8": {
"hashes": [
"sha256:859996073f341f2670741b51ec1e67a01da142831aa1fdc6242dbf88dffbe661",
"sha256:a796a115208f5c03b18f332f7c11729812c8c3ded6c46319c59b53efd3819da8"
],
"index": "pypi",
"version": "==3.7.7"
},
"flit": {
"hashes": [
"sha256:1d93f7a833ed8a6e120ddc40db5c4763bc39bccc75c05081ec8285ece718aefb",
"sha256:6f6f0fb83c51ffa3a150fa41b5ac118df9ea4a87c2c06dff4ebf9adbe7b52b36"
],
"index": "pypi",
"version": "==1.3"
},
"h11": {
"hashes": [
"sha256:33d4bca7be0fa039f4e84d50ab00531047e53d6ee8ffbc83501ea602c169cae1",
"sha256:4bc6d6a1238b7615b266ada57e0618568066f57dd6fa967d1290ec9309b2f2f1"
],
"version": "==0.9.0"
},
"htmlmin": {
"hashes": [
"sha256:50c1ef4630374a5d723900096a961cff426dff46b48f34d194a81bbe14eca178"
],
"version": "==0.1.12"
},
"httptools": {
"hashes": [
"sha256:e00cbd7ba01ff748e494248183abc6e153f49181169d8a3d41bb49132ca01dfc"
],
"version": "==0.0.13"
},
"idna": {
"hashes": [
"sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407",
"sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"
],
"version": "==2.8"
},
"importlib-metadata": {
"hashes": [
"sha256:6dfd58dfe281e8d240937776065dd3624ad5469c835248219bd16cf2e12dbeb7",
"sha256:cb6ee23b46173539939964df59d3d72c3e0c1b5d54b84f1d8a7e912fe43612db"
],
"version": "==0.18"
},
"ipykernel": {
"hashes": [
"sha256:346189536b88859937b5f4848a6fd85d1ad0729f01724a411de5cae9b618819c",
"sha256:f0e962052718068ad3b1d8bcc703794660858f58803c3798628817f492a8769c"
],
"version": "==5.1.1"
},
"ipython": {
"hashes": [
"sha256:54c5a8aa1eadd269ac210b96923688ccf01ebb2d0f21c18c3c717909583579a8",
"sha256:e840810029224b56cd0d9e7719dc3b39cf84d577f8ac686547c8ba7a06eeab26"
],
"markers": "python_version >= '3.3'",
"version": "==7.5.0"
},
"ipython-genutils": {
"hashes": [
"sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8",
"sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"
],
"version": "==0.2.0"
},
"ipywidgets": {
"hashes": [
"sha256:0f2b5cde9f272cb49d52f3f0889fdd1a7ae1e74f37b48dac35a83152780d2b7b",
"sha256:a3e224f430163f767047ab9a042fc55adbcab0c24bbe6cf9f306c4f89fdf0ba3"
],
"version": "==7.4.2"
},
"isort": {
"hashes": [
"sha256:c40744b6bc5162bbb39c1257fe298b7a393861d50978b565f3ccd9cb9de0182a",
"sha256:f57abacd059dc3bd666258d1efb0377510a89777fda3e3274e3c01f7c03ae22d"
],
"index": "pypi",
"version": "==4.3.20"
},
"jedi": {
"hashes": [
"sha256:2bb0603e3506f708e792c7f4ad8fc2a7a9d9c2d292a358fbbd58da531695595b",
"sha256:2c6bcd9545c7d6440951b12b44d373479bf18123a401a52025cf98563fbd826c"
],
"version": "==0.13.3"
},
"jinja2": {
"hashes": [
"sha256:065c4f02ebe7f7cf559e49ee5a95fb800a9e4528727aec6f24402a5374c65013",
"sha256:14dd6caf1527abb21f08f86c784eac40853ba93edb79552aa1e4b8aef1b61c7b"
],
"version": "==2.10.1"
},
"jsmin": {
"hashes": [
"sha256:b6df99b2cd1c75d9d342e4335b535789b8da9107ec748212706ef7bbe5c2553b"
],
"version": "==2.2.2"
},
"jsonschema": {
"hashes": [
"sha256:0c0a81564f181de3212efa2d17de1910f8732fa1b71c42266d983cd74304e20d",
"sha256:a5f6559964a3851f59040d3b961de5e68e70971afb88ba519d27e6a039efff1a"
],
"version": "==3.0.1"
},
"jupyter": {
"hashes": [
"sha256:3e1f86076bbb7c8c207829390305a2b1fe836d471ed54be66a3b8c41e7f46cc7",
"sha256:5b290f93b98ffbc21c0c7e749f054b3267782166d72fa5e3ed1ed4eaf34a2b78",
"sha256:d9dc4b3318f310e34c82951ea5d6683f67bed7def4b259fafbfe4f1beb1d8e5f"
],
"index": "pypi",
"version": "==1.0.0"
},
"jupyter-client": {
"hashes": [
"sha256:b5f9cb06105c1d2d30719db5ffb3ea67da60919fb68deaefa583deccd8813551",
"sha256:c44411eb1463ed77548bc2d5ec0d744c9b81c4a542d9637c7a52824e2121b987"
],
"version": "==5.2.4"
},
"jupyter-console": {
"hashes": [
"sha256:308ce876354924fb6c540b41d5d6d08acfc946984bf0c97777c1ddcb42e0b2f5",
"sha256:cc80a97a5c389cbd30252ffb5ce7cefd4b66bde98219edd16bf5cb6f84bb3568"
],
"version": "==6.0.0"
},
"jupyter-core": {
"hashes": [
"sha256:927d713ffa616ea11972534411544589976b2493fc7e09ad946e010aa7eb9970",
"sha256:ba70754aa680300306c699790128f6fbd8c306ee5927976cbe48adacf240c0b7"
],
"version": "==4.4.0"
},
"livereload": {
"hashes": [
"sha256:78d55f2c268a8823ba499305dcac64e28ddeb9a92571e12d543cd304faf5817b",
"sha256:89254f78d7529d7ea0a3417d224c34287ebfe266b05e67e51facaf82c27f0f66"
],
"version": "==2.6.1"
},
"markdown": {
"hashes": [
"sha256:2e50876bcdd74517e7b71f3e7a76102050edec255b3983403f1a63e7c8a41e7a",
"sha256:56a46ac655704b91e5b7e6326ce43d5ef72411376588afa1dd90e881b83c7e8c"
],
"version": "==3.1.1"
},
"markdown-include": {
"hashes": [
"sha256:72a45461b589489a088753893bc95c5fa5909936186485f4ed55caa57d10250f"
],
"index": "pypi",
"version": "==0.5.1"
},
"markupsafe": {
"hashes": [
"sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473",
"sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161",
"sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235",
"sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5",
"sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff",
"sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b",
"sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1",
"sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e",
"sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183",
"sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66",
"sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1",
"sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1",
"sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e",
"sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b",
"sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905",
"sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735",
"sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d",
"sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e",
"sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d",
"sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c",
"sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21",
"sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2",
"sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5",
"sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b",
"sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6",
"sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f",
"sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f",
"sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"
],
"version": "==1.1.1"
},
"mccabe": {
"hashes": [
"sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
"sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"
],
"version": "==0.6.1"
},
"mistune": {
"hashes": [
"sha256:59a3429db53c50b5c6bcc8a07f8848cb00d7dc8bdb431a4ab41920d201d4756e",
"sha256:88a1051873018da288eee8538d476dffe1262495144b33ecb586c4ab266bb8d4"
],
"version": "==0.8.4"
},
"mkdocs": {
"hashes": [
"sha256:17d34329aad75d5de604b9ed4e31df3a4d235afefdc46ce7b1964fddb2e1e939",
"sha256:8cc8b38325456b9e942c981a209eaeb1e9f3f77b493ad755bfef889b9c8d356a"
],
"index": "pypi",
"version": "==1.0.4"
},
"mkdocs-material": {
"hashes": [
"sha256:451b949f6c8f0750b937f805e14c5bd40b81c5ff829072a74896efeaa6e8567f",
"sha256:8b5a042a0f2b54e631668e33d6a0777ff2c68331656066e26223c47159a255e1"
],
"index": "pypi",
"version": "==4.4.0"
},
"mkdocs-minify-plugin": {
"hashes": [
"sha256:3000a5069dd0f42f56a8aaf7fd5ea1222c67487949617e39585d6b6434b074b6",
"sha256:d54fdd5be6843dd29fd7af2f7fdd20a9eb4db46f1f6bed914e03b2f58d2d488e"
],
"version": "==0.2.1"
},
"more-itertools": {
"hashes": [
"sha256:2112d2ca570bb7c3e53ea1a35cd5df42bb0fd10c45f0fb97178679c3c03d64c7",
"sha256:c3e4748ba1aad8dba30a4886b0b1a2004f9a863837b8654e7059eebf727afa5a"
],
"markers": "python_version > '2.7'",
"version": "==7.0.0"
},
"mypy": {
"hashes": [
"sha256:2afe51527b1f6cdc4a5f34fc90473109b22bf7f21086ba3e9451857cf11489e6",
"sha256:56a16df3e0abb145d8accd5dbb70eba6c4bd26e2f89042b491faa78c9635d1e2",
"sha256:5764f10d27b2e93c84f70af5778941b8f4aa1379b2430f85c827e0f5464e8714",
"sha256:5bbc86374f04a3aa817622f98e40375ccb28c4836f36b66706cf3c6ccce86eda",
"sha256:6a9343089f6377e71e20ca734cd8e7ac25d36478a9df580efabfe9059819bf82",
"sha256:6c9851bc4a23dc1d854d3f5dfd5f20a016f8da86bcdbb42687879bb5f86434b0",
"sha256:b8e85956af3fcf043d6f87c91cbe8705073fc67029ba6e22d3468bfee42c4823",
"sha256:b9a0af8fae490306bc112229000aa0c2ccc837b49d29a5c42e088c132a2334dd",
"sha256:bbf643528e2a55df2c1587008d6e3bda5c0445f1240dfa85129af22ae16d7a9a",
"sha256:c46ab3438bd21511db0f2c612d89d8344154c0c9494afc7fbc932de514cf8d15",
"sha256:f7a83d6bd805855ef83ec605eb01ab4fa42bcef254b13631e451cbb44914a9b0"
],
"index": "pypi",
"version": "==0.701"
},
"mypy-extensions": {
"hashes": [
"sha256:37e0e956f41369209a3d5f34580150bcacfabaa57b33a15c0b25f4b5725e0812",
"sha256:b16cabe759f55e3409a7d231ebd2841378fb0c27a5d1994719e340e4f429ac3e"
],
"version": "==0.4.1"
},
"nbconvert": {
"hashes": [
"sha256:138381baa41d83584459b5cfecfc38c800ccf1f37d9ddd0bd440783346a4c39c",
"sha256:4a978548d8383f6b2cfca4a3b0543afb77bc7cb5a96e8b424337ab58c12da9bc"
],
"version": "==5.5.0"
},
"nbformat": {
"hashes": [
"sha256:b9a0dbdbd45bb034f4f8893cafd6f652ea08c8c1674ba83f2dc55d3955743b0b",
"sha256:f7494ef0df60766b7cabe0a3651556345a963b74dbc16bc7c18479041170d402"
],
"version": "==4.4.0"
},
"notebook": {
"hashes": [
"sha256:573e0ae650c5d76b18b6e564ba6d21bf321d00847de1d215b418acb64f056eb8",
"sha256:f64fa6624d2323fbef6210a621817d6505a45d0d4a9367f1843b20a38a4666ee"
],
"version": "==5.7.8"
},
"packaging": {
"hashes": [
"sha256:0c98a5d0be38ed775798ece1b9727178c4469d9c3b4ada66e8e6b7849f8732af",
"sha256:9e1cbf8c12b1f1ce0bb5344b8d7ecf66a6f8a6e91bcb0c84593ed6d3ab5c4ab3"
],
"version": "==19.0"
},
"pandocfilters": {
"hashes": [
"sha256:b3dd70e169bb5449e6bc6ff96aea89c5eea8c5f6ab5e207fc2f521a2cf4a0da9"
],
"version": "==1.4.2"
},
"parso": {
"hashes": [
"sha256:17cc2d7a945eb42c3569d4564cdf49bde221bc2b552af3eca9c1aad517dcdd33",
"sha256:2e9574cb12e7112a87253e14e2c380ce312060269d04bd018478a3c92ea9a376"
],
"version": "==0.4.0"
},
"pexpect": {
"hashes": [
"sha256:2094eefdfcf37a1fdbfb9aa090862c1a4878e5c7e0e7e7088bdb511c558e5cd1",
"sha256:9e2c1fd0e6ee3a49b28f95d4b33bc389c89b20af6a1255906e90ff1262ce62eb"
],
"markers": "sys_platform != 'win32'",
"version": "==4.7.0"
},
"pickleshare": {
"hashes": [
"sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca",
"sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"
],
"version": "==0.7.5"
},
"pluggy": {
"hashes": [
"sha256:0825a152ac059776623854c1543d65a4ad408eb3d33ee114dff91e57ec6ae6fc",
"sha256:b9817417e95936bf75d85d3f8767f7df6cdde751fc40aed3bb3074cbcb77757c"
],
"version": "==0.12.0"
},
"prometheus-client": {
"hashes": [
"sha256:ee0c90350595e4a9f36591f291e6f9933246ea67d7cd7d1d6139a9781b14eaae"
],
"version": "==0.7.0"
},
"prompt-toolkit": {
"hashes": [
"sha256:11adf3389a996a6d45cc277580d0d53e8a5afd281d0c9ec71b28e6f121463780",
"sha256:2519ad1d8038fd5fc8e770362237ad0364d16a7650fb5724af6997ed5515e3c1",
"sha256:977c6583ae813a37dc1c2e1b715892461fcbdaa57f6fc62f33a528c4886c8f55"
],
"version": "==2.0.9"
},
"ptyprocess": {
"hashes": [
"sha256:923f299cc5ad920c68f2bc0bc98b75b9f838b93b599941a6b63ddbc2476394c0",
"sha256:d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f"
],
"markers": "os_name != 'nt'",
"version": "==0.6.0"
},
"py": {
"hashes": [
"sha256:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa",
"sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53"
],
"version": "==1.8.0"
},
"pycodestyle": {
"hashes": [
"sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56",
"sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c"
],
"version": "==2.5.0"
},
"pyflakes": {
"hashes": [
"sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0",
"sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2"
],
"version": "==2.1.1"
},
"pygments": {
"hashes": [
"sha256:71e430bc85c88a430f000ac1d9b331d2407f681d6f6aec95e8bcfbc3df5b0127",
"sha256:881c4c157e45f30af185c1ffe8d549d48ac9127433f2c380c24b84572ad66297"
],
"version": "==2.4.2"
},
"pymdown-extensions": {
"hashes": [
"sha256:25b0a7967fa697b5035e23340a48594e3e93acb10b06d74574218ace3347d1df",
"sha256:6cf0cf36b5a03b291ace22dc2f320f4789ce56fbdb6635a3be5fadbf5d7694dd"
],
"version": "==6.0"
},
"pyparsing": {
"hashes": [
"sha256:1873c03321fc118f4e9746baf201ff990ceb915f433f23b395f5580d1840cb2a",
"sha256:9b6323ef4ab914af344ba97510e966d64ba91055d6b9afa6b30799340e89cc03"
],
"version": "==2.4.0"
},
"pyrsistent": {
"hashes": [
"sha256:16692ee739d42cf5e39cef8d27649a8c1fdb7aa99887098f1460057c5eb75c3a"
],
"version": "==0.15.2"
},
"pytest": {
"hashes": [
"sha256:4a784f1d4f2ef198fe9b7aef793e9fa1a3b2f84e822d9b3a64a181293a572d45",
"sha256:926855726d8ae8371803f7b2e6ec0a69953d9c6311fa7c3b6c1b929ff92d27da"
],
"index": "pypi",
"version": "==4.6.3"
},
"pytest-cov": {
"hashes": [
"sha256:2b097cde81a302e1047331b48cadacf23577e431b61e9c6f49a1170bbe3d3da6",
"sha256:e00ea4fdde970725482f1f35630d12f074e121a23801aabf2ae154ec6bdd343a"
],
"index": "pypi",
"version": "==2.7.1"
},
"python-dateutil": {
"hashes": [
"sha256:7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb",
"sha256:c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e"
],
"version": "==2.8.0"
},
"python-multipart": {
"hashes": [
"sha256:f7bb5f611fc600d15fa47b3974c8aa16e93724513b49b5f95c81e6624c83fa43"
],
"index": "pypi",
"version": "==0.0.5"
},
"pytoml": {
"hashes": [
"sha256:ca2d0cb127c938b8b76a9a0d0f855cf930c1d50cc3a0af6d3595b566519a1013"
],
"version": "==0.1.20"
},
"pyyaml": {
"hashes": [
"sha256:57acc1d8533cbe51f6662a55434f0dbecfa2b9eaf115bede8f6fd00115a0c0d3",
"sha256:588c94b3d16b76cfed8e0be54932e5729cc185caffaa5a451e7ad2f7ed8b4043",
"sha256:68c8dd247f29f9a0d09375c9c6b8fdc64b60810ebf07ba4cdd64ceee3a58c7b7",
"sha256:70d9818f1c9cd5c48bb87804f2efc8692f1023dac7f1a1a5c61d454043c1d265",
"sha256:86a93cccd50f8c125286e637328ff4eef108400dd7089b46a7be3445eecfa391",
"sha256:a0f329125a926876f647c9fa0ef32801587a12328b4a3c741270464e3e4fa778",
"sha256:a3c252ab0fa1bb0d5a3f6449a4826732f3eb6c0270925548cac342bc9b22c225",
"sha256:b4bb4d3f5e232425e25dda21c070ce05168a786ac9eda43768ab7f3ac2770955",
"sha256:cd0618c5ba5bda5f4039b9398bb7fb6a317bb8298218c3de25c47c4740e4b95e",
"sha256:ceacb9e5f8474dcf45b940578591c7f3d960e82f926c707788a570b51ba59190",
"sha256:fe6a88094b64132c4bb3b631412e90032e8cfe9745a58370462240b8cb7553cd"
],
"version": "==5.1.1"
},
"pyzmq": {
"hashes": [
"sha256:1651e52ed91f0736afd6d94ef9f3259b5534ce8beddb054f3d5ca989c4ef7c4f",
"sha256:5ccb9b3d4cd20c000a9b75689d5add8cd3bce67fcbd0f8ae1b59345247d803af",
"sha256:5e120c4cd3872e332fb35d255ad5998ebcee32ace4387b1b337416b6b90436c7",
"sha256:5e2a3707c69a7281a9957f83718815fd74698cba31f6d69f9ed359921f662221",
"sha256:63d51add9af8d0442dc90f916baf98fdc04e3b0a32afec4bfc83f8d85e72959f",
"sha256:65c5a0bdc49e20f7d6b03a661f71e2fda7a99c51270cafe71598146d09810d0d",
"sha256:66828fabe911aa545d919028441a585edb7c9c77969a5fea6722ef6e6ece38ab",
"sha256:7d79427e82d9dad6e9b47c0b3e7ae5f9d489b1601e3a36ea629bb49501a4daf3",
"sha256:824ee5d3078c4eae737ffc500fbf32f2b14e6ec89b26b435b7834febd70120cf",
"sha256:89dc0a83cccec19ff3c62c091e43e66e0183d1e6b4658c16ee4e659518131494",
"sha256:8b319805f6f7c907b101c864c3ca6cefc9db8ce0791356f180b1b644c7347e4c",
"sha256:90facfb379ab47f94b19519c1ecc8ec8d10813b69d9c163117944948bdec5d15",
"sha256:a0a178c7420021fc0730180a914a4b4b3092ce9696ceb8e72d0f60f8ce1655dd",
"sha256:a7a89591ae315baccb8072f216614b3e59aed7385aef4393a6c741783d6ee9cf",
"sha256:ba2578f0ae582452c02ed9fac2dc477b08e80ce05d2c0885becf5fff6651ccb0",
"sha256:c69b0055c55702f5b0b6b354133e8325b9a56dbc80e1be2d240bead253fb9825",
"sha256:ca434e1858fe222380221ddeb81e86f45522773344c9da63c311d17161df5e06",
"sha256:d4b8ecfc3d92f114f04d5c40f60a65e5196198b827503341521dda12d8b14939",
"sha256:d706025c47b09a54f005953ebe206f6d07a22516776faa4f509aaff681cc5468",
"sha256:d8f27e958f8a2c0c8ffd4d8855c3ce8ac3fa1e105f0491ce31729aa2b3229740",
"sha256:dbd264298f76b9060ce537008eb989317ca787c857e23cbd1b3ddf89f190a9b1",
"sha256:e926d66f0df8fdbf03ba20583af0f215e475c667fb033d45fd031c66c63e34c9",
"sha256:efc3bd48237f973a749f7312f68062f1b4ca5c2032a0673ca3ea8e46aa77187b",
"sha256:f59bc782228777cbfe04555707a9c56d269c787ed25d6d28ed9d0fbb41cb1ad2",
"sha256:f8da5322f4ff5f667a0d5a27e871b560c6637153c81e318b35cb012b2a98835c"
],
"version": "==18.0.1"
},
"qtconsole": {
"hashes": [
"sha256:4af84facdd6f00a6b9b2927255f717bb23ae4b7a20ba1d9ef0a5a5a8dbe01ae2",
"sha256:60d61d93f7d67ba2b265c6d599d413ffec21202fec999a952f658ff3a73d252b"
],
"version": "==4.5.1"
},
"requests": {
"hashes": [
"sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4",
"sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"
],
"index": "pypi",
"version": "==2.22.0"
},
"send2trash": {
"hashes": [
"sha256:60001cc07d707fe247c94f74ca6ac0d3255aabcb930529690897ca2a39db28b2",
"sha256:f1691922577b6fa12821234aeb57599d887c4900b9ca537948d2dac34aea888b"
],
"version": "==1.5.0"
},
"six": {
"hashes": [
"sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c",
"sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"
],
"version": "==1.12.0"
},
"sqlalchemy": {
"hashes": [
"sha256:c30925d60af95443458ebd7525daf791f55762b106049ae71e18f8dd58084c2f"
],
"version": "==1.3.5"
},
"terminado": {
"hashes": [
"sha256:d9d012de63acb8223ac969c17c3043337c2fcfd28f3aea1ee429b345d01ef460",
"sha256:de08e141f83c3a0798b050ecb097ab6259c3f0331b2f7b7750c9075ced2c20c2"
],
"version": "==0.8.2"
},
"testpath": {
"hashes": [
"sha256:46c89ebb683f473ffe2aab0ed9f12581d4d078308a3cb3765d79c6b2317b0109",
"sha256:b694b3d9288dbd81685c5d2e7140b81365d46c29f5db4bc659de5aa6b98780f8"
],
"version": "==0.4.2"
},
"toml": {
"hashes": [
"sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c",
"sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"
],
"version": "==0.10.0"
},
"tornado": {
"hashes": [
"sha256:1174dcb84d08887b55defb2cda1986faeeea715fff189ef3dc44cce99f5fca6b",
"sha256:2613fab506bd2aedb3722c8c64c17f8f74f4070afed6eea17f20b2115e445aec",
"sha256:44b82bc1146a24e5b9853d04c142576b4e8fa7a92f2e30bc364a85d1f75c4de2",
"sha256:457fcbee4df737d2defc181b9073758d73f54a6cfc1f280533ff48831b39f4a8",
"sha256:49603e1a6e24104961497ad0c07c799aec1caac7400a6762b687e74c8206677d",
"sha256:8c2f40b99a8153893793559919a355d7b74649a11e59f411b0b0a1793e160bc0",
"sha256:e1d897889c3b5a829426b7d52828fb37b28bc181cd598624e65c8be40ee3f7fa"
],
"version": "==6.0.2"
},
"traitlets": {
"hashes": [
"sha256:9c4bd2d267b7153df9152698efb1050a5d84982d3384a37b2c1f7723ba3e7835",
"sha256:c6cb5e6f57c5a9bdaa40fa71ce7b4af30298fbab9ece9815b5d995ab6217c7d9"
],
"version": "==4.3.2"
},
"typed-ast": {
"hashes": [
"sha256:132eae51d6ef3ff4a8c47c393a4ef5ebf0d1aecc96880eb5d6c8ceab7017cc9b",
"sha256:18141c1484ab8784006c839be8b985cfc82a2e9725837b0ecfa0203f71c4e39d",
"sha256:2baf617f5bbbfe73fd8846463f5aeafc912b5ee247f410700245d68525ec584a",
"sha256:3d90063f2cbbe39177e9b4d888e45777012652d6110156845b828908c51ae462",
"sha256:4304b2218b842d610aa1a1d87e1dc9559597969acc62ce717ee4dfeaa44d7eee",
"sha256:4983ede548ffc3541bae49a82675996497348e55bafd1554dc4e4a5d6eda541a",
"sha256:5315f4509c1476718a4825f45a203b82d7fdf2a6f5f0c8f166435975b1c9f7d4",
"sha256:6cdfb1b49d5345f7c2b90d638822d16ba62dc82f7616e9b4caa10b72f3f16649",
"sha256:7b325f12635598c604690efd7a0197d0b94b7d7778498e76e0710cd582fd1c7a",
"sha256:8d3b0e3b8626615826f9a626548057c5275a9733512b137984a68ba1598d3d2f",
"sha256:8f8631160c79f53081bd23446525db0bc4c5616f78d04021e6e434b286493fd7",
"sha256:912de10965f3dc89da23936f1cc4ed60764f712e5fa603a09dd904f88c996760",
"sha256:b010c07b975fe853c65d7bbe9d4ac62f1c69086750a574f6292597763781ba18",
"sha256:c908c10505904c48081a5415a1e295d8403e353e0c14c42b6d67f8f97fae6616",
"sha256:c94dd3807c0c0610f7c76f078119f4ea48235a953512752b9175f9f98f5ae2bd",
"sha256:ce65dee7594a84c466e79d7fb7d3303e7295d16a83c22c7c4037071b059e2c21",
"sha256:eaa9cfcb221a8a4c2889be6f93da141ac777eb8819f077e1d09fb12d00a09a93",
"sha256:f3376bc31bad66d46d44b4e6522c5c21976bf9bca4ef5987bb2bf727f4506cbb",
"sha256:f9202fa138544e13a4ec1a6792c35834250a85958fde1251b6a22e07d1260ae7"
],
"version": "==1.3.5"
},
"ujson": {
"hashes": [
"sha256:f66073e5506e91d204ab0c614a148d5aa938bdbf104751be66f8ad7a222f5f86"
],
"index": "pypi",
"version": "==1.35"
},
"urllib3": {
"hashes": [
"sha256:b246607a25ac80bedac05c6f282e3cdaf3afb65420fd024ac94435cabe6e18d1",
"sha256:dbe59173209418ae49d485b87d1681aefa36252ee85884c31346debd19463232"
],
"version": "==1.25.3"
},
"uvicorn": {
"hashes": [
"sha256:9114d22a569552258a3f2bf0da57c328049c3dfd428a88230cdf0966229ef180"
],
"index": "pypi",
"version": "==0.7.2"
},
"uvloop": {
"hashes": [
"sha256:0fcd894f6fc3226a962ee7ad895c4f52e3f5c3c55098e21efb17c071849a0573",
"sha256:2f31de1742c059c96cb76b91c5275b22b22b965c886ee1fced093fa27dde9e64",
"sha256:459e4649fcd5ff719523de33964aa284898e55df62761e7773d088823ccbd3e0",
"sha256:67867aafd6e0bc2c30a079603a85d83b94f23c5593b3cc08ec7e58ac18bf48e5",
"sha256:8c200457e6847f28d8bb91c5e5039d301716f5f2fce25646f5fb3fd65eda4a26",
"sha256:958906b9ca39eb158414fbb7d6b8ef1b7aee4db5c8e8e5d00fcbb69a1ce9dca7",
"sha256:ac1dca3d8f3ef52806059e81042ee397ac939e5a86c8a3cea55d6b087db66115",
"sha256:b284c22d8938866318e3b9d178142b8be316c52d16fcfe1560685a686718a021",
"sha256:c48692bf4587ce281d641087658eca275a5ad3b63c78297bbded96570ae9ce8f",
"sha256:fefc3b2b947c99737c348887db2c32e539160dcbeb7af9aa6b53db7a283538fe"
],
"version": "==0.12.2"
},
"wcwidth": {
"hashes": [
"sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e",
"sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c"
],
"version": "==0.1.7"
},
"webencodings": {
"hashes": [
"sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78",
"sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"
],
"version": "==0.5.1"
},
"websockets": {
"hashes": [
"sha256:04b42a1b57096ffa5627d6a78ea1ff7fad3bc2c0331ffc17bc32a4024da7fea0",
"sha256:08e3c3e0535befa4f0c4443824496c03ecc25062debbcf895874f8a0b4c97c9f",
"sha256:10d89d4326045bf5e15e83e9867c85d686b612822e4d8f149cf4840aab5f46e0",
"sha256:232fac8a1978fc1dead4b1c2fa27c7756750fb393eb4ac52f6bc87ba7242b2fa",
"sha256:4bf4c8097440eff22bc78ec76fe2a865a6e658b6977a504679aaf08f02c121da",
"sha256:51642ea3a00772d1e48fb0c492f0d3ae3b6474f34d20eca005a83f8c9c06c561",
"sha256:55d86102282a636e195dad68aaaf85b81d0bef449d7e2ef2ff79ac450bb25d53",
"sha256:564d2675682bd497b59907d2205031acbf7d3fadf8c763b689b9ede20300b215",
"sha256:5d13bf5197a92149dc0badcc2b699267ff65a867029f465accfca8abab95f412",
"sha256:5eda665f6789edb9b57b57a159b9c55482cbe5b046d7db458948370554b16439",
"sha256:5edb2524d4032be4564c65dc4f9d01e79fe8fad5f966e5b552f4e5164fef0885",
"sha256:79691794288bc51e2a3b8de2bc0272ca8355d0b8503077ea57c0716e840ebaef",
"sha256:7fcc8681e9981b9b511cdee7c580d5b005f3bb86b65bde2188e04a29f1d63317",
"sha256:8e447e05ec88b1b408a4c9cde85aa6f4b04f06aa874b9f0b8e8319faf51b1fee",
"sha256:90ea6b3e7787620bb295a4ae050d2811c807d65b1486749414f78cfd6fb61489",
"sha256:9e13239952694b8b831088431d15f771beace10edfcf9ef230cefea14f18508f",
"sha256:d40f081187f7b54d7a99d8a5c782eaa4edc335a057aa54c85059272ed826dc09",
"sha256:e1df1a58ed2468c7b7ce9a2f9752a32ad08eac2bcd56318625c3647c2cd2da6f",
"sha256:e98d0cec437097f09c7834a11c69d79fe6241729b23f656cfc227e93294fc242",
"sha256:f8d59627702d2ff27cb495ca1abdea8bd8d581de425c56e93bff6517134e0a9b",
"sha256:fc30cdf2e949a2225b012a7911d1d031df3d23e99b7eda7dfc982dc4a860dae9"
],
"version": "==7.0"
},
"widgetsnbextension": {
"hashes": [
"sha256:14b2c65f9940c9a7d3b70adbe713dbd38b5ec69724eebaba034d1036cf3d4740",
"sha256:fa618be8435447a017fd1bf2c7ae922d0428056cfc7449f7a8641edf76b48265"
],
"version": "==3.4.2"
},
"zipp": {
"hashes": [
"sha256:8c1019c6aad13642199fbe458275ad6a84907634cc9f0989877ccc4a2840139d",
"sha256:ca943a7e809cc12257001ccfb99e3563da9af99d52f261725e96dfe0f9275bc3"
],
"version": "==0.5.1"
}
}
}

View File

@@ -193,7 +193,7 @@ But then <a href="https://letsencrypt.org/" target="_blank">Let's Encrypt</a> wa
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 domains 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.

56
docs/external-links.md Normal file
View File

@@ -0,0 +1,56 @@
**FastAPI** has a great community constantly growing.
There are many posts, articles, tools, and projects, related to **FastAPI**.
Here's an incomplete list of some of them.
!!! tip
If you have an article, project, tool, or anything related to **FastAPI** that is not yet listed here, create a <a href="https://github.com/tiangolo/fastapi/edit/master/docs/external-links.md" target="_blank">Pull Request adding it</a>.
## Articles
### English
* <a href="https://medium.com/@williamhayes/fastapi-starlette-debug-vs-prod-5f7561db3a59" target="_blank">FastAPI/Starlette debug vs prod</a> by <a href="https://medium.com/@williamhayes" target="_blank">William Hayes</a>.
* <a href="https://medium.com/data-rebels/fastapi-google-as-an-external-authentication-provider-3a527672cf33" target="_blank">FastAPIGoogle as an external authentication provider</a> by <a href="https://medium.com/@nils_29588" target="_blank">Nils de Bruin</a>.
* <a href="https://medium.com/data-rebels/fastapi-how-to-add-basic-and-cookie-authentication-a45c85ef47d3" target="_blank">FastAPIHow to add basic and cookie authentication</a> by <a href="https://medium.com/@nils_29588" target="_blank">Nils de Bruin</a>.
* <a href="https://dev.to/errietta/introduction-to-the-fastapi-python-framework-2n10" target="_blank">Introduction to the fastapi python framework</a> by <a href="https://dev.to/errietta" target="_blank">Errieta Kostala</a>.
* <a href="http://nickc1.github.io/api,/scikit-learn/2019/01/10/scikit-fastapi.html" target="_blank">FastAPI and Scikit-Learn: Easily Deploy Models</a> by <a href="http://nickc1.github.io/" target="_blank">Nick Cortale</a>.
* <a href="https://medium.com/data-rebels/fastapi-authentication-revisited-enabling-api-key-authentication-122dc5975680" target="_blank">FastAPI authentication revisited: Enabling API key authentication</a> by <a href="https://medium.com/@nils_29588" target="_blank">Nils de Bruin</a>.
* <a href="https://blog.bartab.fr/fastapi-logging-on-the-fly/" target="_blank">FastAPI, a simple use case on logging</a> by <a href="https://blog.bartab.fr/" target="_blank">@euri10</a>.
### Japanese
* <a href="https://qiita.com/mtitg/items/47770e9a562dd150631d" target="_blank">FastAPIDB接続してCRUDするPython製APIサーバーを構築</a> by <a href="https://qiita.com/mtitg" target="_blank">@mtitg</a>.
* <a href="https://qiita.com/ryoryomaru/items/59958ed385b3571d50de" target="_blank">python製の最新APIフレームワーク FastAPI を触ってみた</a> by <a href="https://qiita.com/ryoryomaru" target="_blank">@ryoryomaru</a>.
* <a href="https://qiita.com/angel_katayoku/items/0e1f5dbbe62efc612a78" target="_blank">FastAPIでCORSを回避</a> by <a href="https://qiita.com/angel_katayoku" target="_blank">@angel_katayoku</a>.
* <a href="https://qiita.com/angel_katayoku/items/4fbc1a4e2b33fa2237d2" target="_blank">FastAPIをMySQLと接続してDockerで管理してみる</a> by <a href="https://qiita.com/angel_katayoku" target="_blank">@angel_katayoku</a>.
* <a href="https://qiita.com/angel_katayoku/items/8a458a8952f50b73f420" target="_blank">FastAPIでPOSTされたJSONのレスポンスbodyを受け取る</a> by <a href="https://qiita.com/angel_katayoku" target="_blank">@angel_katayoku</a>.
* <a href="https://qiita.com/hikarut/items/b178af2e2440c67c6ac4" target="_blank">フロントエンド開発者向けのDockerによるPython開発環境構築</a> by <a href="https://qiita.com/hikarut" target="_blank">Hikaru Takahashi</a>.
### Chinese
* <a href="https://cloud.tencent.com/developer/article/1431448" target="_blank">使用FastAPI框架快速构建高性能的api服务</a> by <a href="https://cloud.tencent.com/developer/user/5471722" target="_blank">逍遥散人</a>.
### Vietnamese
* <a href="https://fullstackstation.com/fastapi-trien-khai-bang-docker/" target="_blank">FASTAPI: TRIỂN KHAI BẰNG DOCKER</a> by <a href="https://fullstackstation.com/author/figonking/" target="_blank">Nguyễn Nhân</a>.
### Russian
* <a href="https://habr.com/ru/post/454440/" target="_blank">Мелкая питонячая радость #2: Starlette - Солидная примочка FastAPI</a> by <a href="https://habr.com/ru/users/57uff3r/" target="_blank">Andrey Korchak</a>.
## Podcasts
* <a href="https://pythonbytes.fm/episodes/show/123/time-to-right-the-py-wrongs?time_in_sec=855" target="_blank">FastAPI on PythonBytes</a> by <a href="https://pythonbytes.fm/" target="_blank">Python Bytes FM</a>.

View File

@@ -37,7 +37,7 @@ from datetime import date
from pydantic import BaseModel
# Declare a variable as an str
# Declare a variable as a str
# and get editor support inside the function
def main(user_id: str):
return user_id

View File

@@ -70,10 +70,8 @@ You can let me know:
## Vote for FastAPI
You can vote to include FastAPI in several "awesome lists":
* <a href="https://github.com/vinta/awesome-python/pull/1209" target="_blank">Vote to include **FastAPI** in `awesome-python`</a>.
* <a href="https://github.com/timofurrer/awesome-asyncio/pull/43" target="_blank">Vote to include **FastAPI** in `awesome-asyncio`</a>.
* <a href="https://www.slant.co/options/34241/~fastapi-review" target="_blank">Vote for **FastAPI** in Slant</a>.
## Help others with issues in GitHub

View File

@@ -1,5 +1,66 @@
## Latest changes
## 0.35.0
* Fix typo in routing `assert`. PR [#419](https://github.com/tiangolo/fastapi/pull/419) by [@pablogamboa](https://github.com/pablogamboa).
* Fix typo in docs. PR [#411](https://github.com/tiangolo/fastapi/pull/411) by [@bronsen](https://github.com/bronsen).
* Fix parsing a body type declared with `Union`. PR [#400](https://github.com/tiangolo/fastapi/pull/400) by [@koxudaxi](https://github.com/koxudaxi).
## 0.34.0
* Upgrade Starlette supported range to include the latest `0.12.7`. The new range is `0.11.1,<=0.12.7`. PR [#367](https://github.com/tiangolo/fastapi/pull/367) by [@dedsm](https://github.com/dedsm).
* Add test for OpenAPI schema with duplicate models from PR [#333](https://github.com/tiangolo/fastapi/pull/333) by [@dmontagu](https://github.com/dmontagu). PR [#385](https://github.com/tiangolo/fastapi/pull/385).
## 0.33.0
* Upgrade Pydantic version to `0.30.0`. PR [#384](https://github.com/tiangolo/fastapi/pull/384) by [@jekirl](https://github.com/jekirl).
## 0.32.0
* Fix typo in docs for features. PR [#380](https://github.com/tiangolo/fastapi/pull/380) by [@MartinoMensio](https://github.com/MartinoMensio).
* Fix source code `limit` for example in [Query Parameters](https://fastapi.tiangolo.com/tutorial/query-params/). PR [#366](https://github.com/tiangolo/fastapi/pull/366) by [@Smashman](https://github.com/Smashman).
* Update wording in docs about [OAuth2 scopes](https://fastapi.tiangolo.com/tutorial/security/oauth2-scopes/). PR [#371](https://github.com/tiangolo/fastapi/pull/371) by [@cjw296](https://github.com/cjw296).
* Update docs for `Enum`s to inherit from `str` and improve Swagger UI rendering. PR [#351](https://github.com/tiangolo/fastapi/pull/351).
* Fix regression, add Swagger UI deep linking again. PR [#350](https://github.com/tiangolo/fastapi/pull/350).
* Add test for having path templates in `prefix` of `.include_router`. PR [#349](https://github.com/tiangolo/fastapi/pull/349).
* Add note to docs: [Include the same router multiple times with different `prefix`](https://fastapi.tiangolo.com/tutorial/bigger-applications/#include-the-same-router-multiple-times-with-different-prefix). PR [#348](https://github.com/tiangolo/fastapi/pull/348).
* Fix OpenAPI/JSON Schema generation for two functions with the same name (in different modules) with the same composite bodies.
* Composite bodies' IDs are now based on path, not only on route name, as the auto-generated name uses the function names, that can be duplicated in different modules.
* The same new ID generation applies to response models.
* This also changes the generated title for those models.
* Only composite bodies and response models are affected because those are generated dynamically, they don't have a module (a Python file).
* This also adds the possibility of using `.include_router()` with the same `APIRouter` *multiple* times, with different prefixes, e.g. `/api/v2` and `/api/latest`, and it will now work correctly.
* PR [#347](https://github.com/tiangolo/fastapi/pull/347).
## 0.31.0
* Upgrade Pydantic supported version to `0.29.0`.
* New supported version range is `"pydantic >=0.28,<=0.29.0"`.
* This adds support for Pydantic [Generic Models](https://pydantic-docs.helpmanual.io/#generic-models), kudos to [@dmontagu](https://github.com/dmontagu).
* PR [#344](https://github.com/tiangolo/fastapi/pull/344).
## 0.30.1
* Add section in docs about [External Links and Articles](https://fastapi.tiangolo.com/external-links/). PR [#341](https://github.com/tiangolo/fastapi/pull/341).
* Remove `Pipfile.lock` from the repository as it is only used by FastAPI contributors (developers of FastAPI itself). See the PR for more details. PR [#340](https://github.com/tiangolo/fastapi/pull/340).
* Update section about [Help FastAPI - Get Help](https://fastapi.tiangolo.com/help-fastapi/). PR [#339](https://github.com/tiangolo/fastapi/pull/339).
* Refine internal type declarations to improve/remove Mypy errors in users' code. PR [#338](https://github.com/tiangolo/fastapi/pull/338).
* Update and clarify [SQL tutorial with SQLAlchemy](https://fastapi.tiangolo.com/tutorial/sql-databases/). PR [#331](https://github.com/tiangolo/fastapi/pull/331) by [@mariacamilagl](https://github.com/mariacamilagl).
* Add SQLite [online viewers to the docs](https://fastapi.tiangolo.com/tutorial/sql-databases/#interact-with-the-database-directly). PR [#330](https://github.com/tiangolo/fastapi/pull/330) by [@cyrilbois](https://github.com/cyrilbois).
## 0.30.0
* Add support for Pydantic's ORM mode:

View File

@@ -3,7 +3,7 @@ from enum import Enum
from fastapi import FastAPI
class ModelName(Enum):
class ModelName(str, Enum):
alexnet = "alexnet"
resnet = "resnet"
lenet = "lenet"

View File

@@ -6,5 +6,5 @@ fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"
@app.get("/items/")
async def read_item(skip: int = 0, limit: int = 100):
async def read_item(skip: int = 0, limit: int = 10):
return fake_items_db[skip : skip + limit]

View File

@@ -2,11 +2,11 @@ from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
SQLALCHEMY_DATABASE_URI = "sqlite:///./test.db"
# SQLALCHEMY_DATABASE_URI = "postgresql://user:password@postgresserver/db"
SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"
# SQLALCHEMY_DATABASE_URL = "postgresql://user:password@postgresserver/db"
engine = create_engine(
SQLALCHEMY_DATABASE_URI, connect_args={"check_same_thread": False}
SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

View File

@@ -174,7 +174,6 @@ from app.routers import items, users
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 the submodule `items` directly, instead of importing just its variable `router`.
@@ -216,7 +215,6 @@ It will include all the routes from that router as part of it.
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.
@@ -295,7 +293,6 @@ The end result is that the item paths are now:
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`:
@@ -309,3 +306,11 @@ And open the docs at <a href="http://127.0.0.1:8000/docs" target="_blank">http:/
You will see the automatic API docs, including the paths from all the submodules, using the correct paths (and prefixes) and the correct tags:
<img src="/img/tutorial/bigger-applications/image01.png">
## Include the same router multiple times with different `prefix`
You can also use `.include_router()` multiple times with the *same* router using different prefixes.
This could be useful, for example, to expose the same API under different prefixes, e.g. `/api/v1` and `/api/latest`.
This is an advanced usage that you might not really need, but it's there in case you do.

View File

@@ -119,7 +119,9 @@ If you have a *path operation* that receives a *path parameter*, but you want th
### Create an `Enum` class
Import `Enum` and create a sub-class that inherits from it.
Import `Enum` and create a sub-class that inherits from `str` and from `Enum`.
By inheriting from `str` the API docs will be able to know that the values must be of type `string` and will be able to render correctly.
And create class attributes with fixed values, those fixed values will be the available valid values:

View File

@@ -176,9 +176,9 @@ For this, we use `security_scopes.scopes`, that contains a `list` with all these
Let's review again this dependency tree and the scopes.
As the other dependency `get_current_active_user` has as a sub-dependency this `get_current_user`, the scope `"me"` declared at `get_current_active_user` will be included in the `security_scopes.scopes` `list` inside of `get_current_user`.
As the `get_current_active_user` dependency has as a sub-dependency on `get_current_user`, the scope `"me"` declared at `get_current_active_user` will be included in the list of required scopes in the `security_scopes.scopes` passed to `get_current_user`.
And as the *path operation* itself also declares a scope `"items"`, it will also be part of this `list` `security_scopes.scopes` in `get_current_user`.
The *path operation* itself also declares a scope, `"items"`, so this will also be in the list of `security_scopes.scopes` passed to `get_current_user`.
Here's how the hierarchy of dependencies and scopes looks like:

View File

@@ -39,15 +39,15 @@ For example a class `Pet` could represent a SQL table `pets`.
And each *instance* object of that class represents a row in the database.
For example an object `mr_furry` (an instance of `Pet`) could have an attribute `mr_furry.type`, for the column `type`. And the value of that attribute could be, e.g. `"cat"`.
For example an object `orion_cat` (an instance of `Pet`) could have an attribute `orion_cat.type`, for the column `type`. And the value of that attribute could be, e.g. `"cat"`.
These ORMs also have tools to make the connections or relations between tables or entities.
This way, you could also have an attribute `mr_furry.owner` and the owner would contain the data for this pet's owner, taken from the table *owners*.
This way, you could also have an attribute `orion_cat.owner` and the owner would contain the data for this pet's owner, taken from the table *owners*.
So, `mr_furry.owner.name` could be the name (from the `name` column in the `owners` table) of this pet's owner.
So, `orion_cat.owner.name` could be the name (from the `name` column in the `owners` table) of this pet's owner.
It could have a value like `"Alice"`.
It could have a value like `"Arquilian"`.
And the ORM will do all the work to get the information from the corresponding table *owners* when you try to access it from your pet object.
@@ -59,7 +59,7 @@ The same way, you could use Peewee or any other.
## File structure
For these examples, let's say you have a directory `sql_app` with a structure like this:
For these examples, let's say you have a directory named `my_super_project` that contains a sub-directory called `sql_app` with a structure like this:
```
├── sql_app
@@ -77,7 +77,7 @@ Now let's see what each file/module does.
## Create the SQLAlchemy parts
Let's see the file `sql_app/database.py`.
Let's refer to the file `sql_app/database.py`.
### Import the SQLAlchemy parts
@@ -100,7 +100,7 @@ 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"
SQLALCHEMY_DATABASE_URL = "postgresql://user:password@postgresserver/db"
```
...and adapt it with your database data and credentials (equivalently for MySQL, MariaDB or any other).
@@ -136,9 +136,9 @@ connect_args={"check_same_thread": False}
### Create a `SessionLocal` class
Each instance of the `SessionLocal` class will have a session/connection to the database.
Each instance of the `SessionLocal` class will be a database session. The class itself is not a database session yet.
This object (class) is not a session/connection to the database yet, but once we create an instance of this class, that instance will have the actual connection to the database.
But once we create an instance of the `SessionLocal` class, this instance will be the actual database session.
We name it `SessionLocal` to distinguish it from the `Session` we are importing from SQLAlchemy.
@@ -152,7 +152,7 @@ To create the `SessionLocal` class, use the function `sessionmaker`:
### Create a `Base` class
Now use the function `declarative_base()` that returns a class.
Now we will use the function `declarative_base()` that returns a class.
Later we will inherit from this class to create each of the database models or classes (the ORM models):
@@ -298,7 +298,7 @@ In the `Config` class, set the attribute `orm_mode = True`.
Pydantic's `orm_mode` will tell the Pydantic *model* to read the data even if it is not a `dict`, but an ORM model (or any other arbitrary object with attributes).
This way, Instead of only trying to get the `id` value from a `dict`, as in:
This way, instead of only trying to get the `id` value from a `dict`, as in:
```Python
id = data["id"]
@@ -456,7 +456,7 @@ This middleware (just a function) will create a new SQLAlchemy `SessionLocal` fo
<a href="https://www.starlette.io/requests/#other-state" target="_blank">`request.state` is a property of each Starlette `Request` object</a>, it is there to store arbitrary objects attached to the request itself, like the database session in this case.
For us in this case, it helps us ensuring a single session/database-connection is used through all the request, and then closed afterwards (in the middleware).
For us in this case, it helps us ensuring a single database session is used through all the request, and then closed afterwards (in the middleware).
### Create a dependency
@@ -547,6 +547,10 @@ For example, in a background task worker with <a href="http://www.celeryproject.
## Review all the files
Remember you should have a directory named `my_super_project` that contains a sub-directory called `sql_app`.
`sql_app` should have the following files:
* `sql_app/__init__.py`: is an empty file.
* `sql_app/database.py`:
@@ -609,3 +613,5 @@ If you want to explore the SQLite database (file) directly, independently of Fas
It will look like this:
<img src="/img/tutorial/sql-databases/image02.png">
You can also use an online SQLite browser like <a href="https://inloop.github.io/sqlite-viewer/" target="_blank">SQLite Viewer</a> or <a href="https://extendsclass.com/sqlite-browser.html" target="_blank">ExtendsClass</a>.

View File

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

View File

@@ -1,4 +1,4 @@
from typing import Any, Callable, Dict, List, Optional, Set, Type, Union
from typing import Any, Callable, Dict, List, Optional, Sequence, Set, Type, Union
from fastapi import routing
from fastapi.exception_handlers import (
@@ -130,7 +130,7 @@ class FastAPI(Starlette):
response_model: Type[Any] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: List[Depends] = None,
dependencies: Sequence[Depends] = None,
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
@@ -152,7 +152,7 @@ class FastAPI(Starlette):
response_model=response_model,
status_code=status_code,
tags=tags or [],
dependencies=dependencies or [],
dependencies=dependencies,
summary=summary,
description=description,
response_description=response_description,
@@ -176,7 +176,7 @@ class FastAPI(Starlette):
response_model: Type[Any] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: List[Depends] = None,
dependencies: Sequence[Depends] = None,
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
@@ -199,7 +199,7 @@ class FastAPI(Starlette):
response_model=response_model,
status_code=status_code,
tags=tags or [],
dependencies=dependencies or [],
dependencies=dependencies,
summary=summary,
description=description,
response_description=response_description,
@@ -237,7 +237,7 @@ class FastAPI(Starlette):
*,
prefix: str = "",
tags: List[str] = None,
dependencies: List[Depends] = None,
dependencies: Sequence[Depends] = None,
responses: Dict[Union[int, str], Dict[str, Any]] = None,
) -> None:
self.router.include_router(
@@ -255,7 +255,7 @@ class FastAPI(Starlette):
response_model: Type[Any] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: List[Depends] = None,
dependencies: Sequence[Depends] = None,
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
@@ -275,7 +275,7 @@ class FastAPI(Starlette):
response_model=response_model,
status_code=status_code,
tags=tags or [],
dependencies=dependencies or [],
dependencies=dependencies,
summary=summary,
description=description,
response_description=response_description,
@@ -298,7 +298,7 @@ class FastAPI(Starlette):
response_model: Type[Any] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: List[Depends] = None,
dependencies: Sequence[Depends] = None,
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
@@ -318,7 +318,7 @@ class FastAPI(Starlette):
response_model=response_model,
status_code=status_code,
tags=tags or [],
dependencies=dependencies or [],
dependencies=dependencies,
summary=summary,
description=description,
response_description=response_description,
@@ -341,7 +341,7 @@ class FastAPI(Starlette):
response_model: Type[Any] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: List[Depends] = None,
dependencies: Sequence[Depends] = None,
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
@@ -361,7 +361,7 @@ class FastAPI(Starlette):
response_model=response_model,
status_code=status_code,
tags=tags or [],
dependencies=dependencies or [],
dependencies=dependencies,
summary=summary,
description=description,
response_description=response_description,
@@ -384,7 +384,7 @@ class FastAPI(Starlette):
response_model: Type[Any] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: List[Depends] = None,
dependencies: Sequence[Depends] = None,
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
@@ -404,7 +404,7 @@ class FastAPI(Starlette):
response_model=response_model,
status_code=status_code,
tags=tags or [],
dependencies=dependencies or [],
dependencies=dependencies,
summary=summary,
description=description,
response_description=response_description,
@@ -427,7 +427,7 @@ class FastAPI(Starlette):
response_model: Type[Any] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: List[Depends] = None,
dependencies: Sequence[Depends] = None,
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
@@ -447,7 +447,7 @@ class FastAPI(Starlette):
response_model=response_model,
status_code=status_code,
tags=tags or [],
dependencies=dependencies or [],
dependencies=dependencies,
summary=summary,
description=description,
response_description=response_description,
@@ -470,7 +470,7 @@ class FastAPI(Starlette):
response_model: Type[Any] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: List[Depends] = None,
dependencies: Sequence[Depends] = None,
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
@@ -490,7 +490,7 @@ class FastAPI(Starlette):
response_model=response_model,
status_code=status_code,
tags=tags or [],
dependencies=dependencies or [],
dependencies=dependencies,
summary=summary,
description=description,
response_description=response_description,
@@ -513,7 +513,7 @@ class FastAPI(Starlette):
response_model: Type[Any] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: List[Depends] = None,
dependencies: Sequence[Depends] = None,
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
@@ -533,7 +533,7 @@ class FastAPI(Starlette):
response_model=response_model,
status_code=status_code,
tags=tags or [],
dependencies=dependencies or [],
dependencies=dependencies,
summary=summary,
description=description,
response_description=response_description,
@@ -556,7 +556,7 @@ class FastAPI(Starlette):
response_model: Type[Any] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: List[Depends] = None,
dependencies: Sequence[Depends] = None,
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
@@ -576,7 +576,7 @@ class FastAPI(Starlette):
response_model=response_model,
status_code=status_code,
tags=tags or [],
dependencies=dependencies or [],
dependencies=dependencies,
summary=summary,
description=description,
response_description=response_description,

View File

@@ -131,12 +131,17 @@ def get_flat_dependant(dependant: Dependant) -> Dependant:
def is_scalar_field(field: Field) -> bool:
return (
if not (
field.shape == Shape.SINGLETON
and not lenient_issubclass(field.type_, BaseModel)
and not lenient_issubclass(field.type_, sequence_types + (dict,))
and not isinstance(field.schema, params.Body)
)
):
return False
if field.sub_fields:
if not all(is_scalar_field(f) for f in field.sub_fields):
return False
return True
def is_scalar_sequence_field(field: Field) -> bool:
@@ -242,7 +247,7 @@ def get_param_field(
if isinstance(schema, params.Param) and getattr(schema, "in_", None) is None:
schema.in_ = default_schema.in_
if force_type:
schema.in_ = force_type
schema.in_ = force_type # type: ignore
else:
schema = default_schema(default_value)
required = default_value == Required
@@ -422,7 +427,7 @@ def request_params_to_args(
value = received_params.getlist(field.alias) or field.default
else:
value = received_params.get(field.alias)
schema: params.Param = field.schema
schema = field.schema
assert isinstance(schema, params.Param), "Params must be subclasses of Param"
if value is None:
if field.required:
@@ -458,7 +463,7 @@ async def request_body_to_args(
if len(required_params) == 1 and not embed:
received_body = {field.alias: received_body}
for field in required_params:
value = None
value: Any = None
if received_body is not None:
if field.shape in sequence_shapes and isinstance(
received_body, FormData

View File

@@ -40,7 +40,8 @@ def get_swagger_ui_html(
SwaggerUIBundle.presets.apis,
SwaggerUIBundle.SwaggerUIStandalonePreset
],
layout: "BaseLayout"
layout: "BaseLayout",
deepLinking: true
})
</script>
</body>

View File

@@ -122,7 +122,7 @@ class Schema(SchemaBase):
not_: Optional[List[SchemaBase]] = PSchema(None, alias="not") # type: ignore
items: Optional[SchemaBase] = None
properties: Optional[Dict[str, SchemaBase]] = None
additionalProperties: Optional[Union[SchemaBase, bool]] = None
additionalProperties: Optional[Union[SchemaBase, bool]] = None # type: ignore
class Example(BaseModel):
@@ -149,9 +149,9 @@ class Encoding(BaseModel):
class MediaType(BaseModel):
schema_: Optional[Union[Schema, Reference]] = PSchema(
schema_: Optional[Union[Schema, Reference]] = PSchema( # type: ignore
None, alias="schema"
) # type: ignore
)
example: Optional[Any] = None
examples: Optional[Dict[str, Union[Example, Reference]]] = None
encoding: Optional[Dict[str, Encoding]] = None
@@ -165,9 +165,9 @@ class ParameterBase(BaseModel):
style: Optional[str] = None
explode: Optional[bool] = None
allowReserved: Optional[bool] = None
schema_: Optional[Union[Schema, Reference]] = PSchema(
schema_: Optional[Union[Schema, Reference]] = PSchema( # type: ignore
None, alias="schema"
) # type: ignore
)
example: Optional[Any] = None
examples: Optional[Dict[str, Union[Example, Reference]]] = None
# Serialization rules for more complex scenarios

View File

@@ -8,7 +8,11 @@ from fastapi.encoders import jsonable_encoder
from fastapi.openapi.constants import METHODS_WITH_BODY, REF_PREFIX
from fastapi.openapi.models import OpenAPI
from fastapi.params import Body, Param
from fastapi.utils import get_flat_models_from_routes, get_model_definitions
from fastapi.utils import (
generate_operation_id_for_path,
get_flat_models_from_routes,
get_model_definitions,
)
from pydantic.fields import Field
from pydantic.schema import field_schema, get_model_name_map
from pydantic.utils import lenient_issubclass
@@ -71,7 +75,8 @@ def get_openapi_operation_parameters(
definitions: Dict[str, Dict] = {}
parameters = []
for param in all_route_params:
schema: Param = param.schema
schema = param.schema
schema = cast(Param, schema)
if "ValidationError" not in definitions:
definitions["ValidationError"] = validation_error_definition
definitions["HTTPValidationError"] = validation_error_response_definition
@@ -90,12 +95,12 @@ def get_openapi_operation_parameters(
def get_openapi_operation_request_body(
*, body_field: Field, model_name_map: Dict[Type, str]
*, body_field: Optional[Field], model_name_map: Dict[Type, str]
) -> Optional[Dict]:
if not body_field:
return None
assert isinstance(body_field, Field)
body_schema, _ = field_schema(
body_schema, _, _ = field_schema(
body_field, model_name_map=model_name_map, ref_prefix=REF_PREFIX
)
body_field.schema = cast(Body, body_field.schema)
@@ -112,10 +117,7 @@ def generate_operation_id(*, route: routing.APIRoute, method: str) -> str:
if route.operation_id:
return route.operation_id
path: str = route.path_format
operation_id = route.name + path
operation_id = operation_id.replace("{", "_").replace("}", "_").replace("/", "_")
operation_id = operation_id + "_" + method.lower()
return operation_id
return generate_operation_id_for_path(name=route.name, path=path, method=method)
def generate_operation_summary(*, route: routing.APIRoute, method: str) -> str:
@@ -182,7 +184,7 @@ def get_openapi_path(
), "An additional response must be a dict"
field = route.response_fields.get(additional_status_code)
if field:
response_schema, _ = field_schema(
response_schema, _, _ = field_schema(
field, model_name_map=model_name_map, ref_prefix=REF_PREFIX
)
response.setdefault("content", {}).setdefault(
@@ -199,7 +201,7 @@ def get_openapi_path(
response_schema = {"type": "string"}
if lenient_issubclass(route.response_class, JSONResponse):
if route.response_field:
response_schema, _ = field_schema(
response_schema, _, _ = field_schema(
route.response_field,
model_name_map=model_name_map,
ref_prefix=REF_PREFIX,

View File

@@ -1,7 +1,7 @@
import asyncio
import inspect
import logging
from typing import Any, Callable, Dict, List, Optional, Set, Type, Union
from typing import Any, Callable, Dict, List, Optional, Sequence, Set, Type, Union
from fastapi import params
from fastapi.dependencies.models import Dependant
@@ -13,7 +13,7 @@ from fastapi.dependencies.utils import (
)
from fastapi.encoders import jsonable_encoder
from fastapi.exceptions import RequestValidationError, WebSocketRequestValidationError
from fastapi.utils import create_cloned_field
from fastapi.utils import create_cloned_field, generate_operation_id_for_path
from pydantic import BaseConfig, BaseModel, Schema
from pydantic.error_wrappers import ErrorWrapper, ValidationError
from pydantic.fields import Field
@@ -147,7 +147,7 @@ def get_websocket_app(
if errors:
await websocket.close(code=WS_1008_POLICY_VIOLATION)
raise WebSocketRequestValidationError(errors)
assert dependant.call is not None, "dependant.call must me a function"
assert dependant.call is not None, "dependant.call must be a function"
await dependant.call(**values)
return app
@@ -184,14 +184,14 @@ class APIRoute(routing.Route):
response_model: Type[Any] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: List[params.Depends] = None,
dependencies: Sequence[params.Depends] = None,
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
name: str = None,
methods: List[str] = None,
methods: Optional[Union[Set[str], List[str]]] = None,
operation_id: str = None,
response_model_include: Set[str] = None,
response_model_exclude: Set[str] = set(),
@@ -205,12 +205,19 @@ class APIRoute(routing.Route):
self.path = path
self.endpoint = endpoint
self.name = get_name(endpoint) if name is None else name
self.path_regex, self.path_format, self.param_convertors = compile_path(path)
if methods is None:
methods = ["GET"]
self.methods = set([method.upper() for method in methods])
self.unique_id = generate_operation_id_for_path(
name=self.name, path=self.path_format, method=list(methods)[0]
)
self.response_model = response_model
if self.response_model:
assert lenient_issubclass(
response_class, JSONResponse
), "To declare a type the response must be a JSON response"
response_name = "Response_" + self.name
response_name = "Response_" + self.unique_id
self.response_field: Optional[Field] = Field(
name=response_name,
type_=self.response_model,
@@ -227,13 +234,18 @@ class APIRoute(routing.Route):
# would pass the validation and be returned as is.
# By being a new field, no inheritance will be passed as is. A new model
# will be always created.
self.secure_cloned_response_field = create_cloned_field(self.response_field)
self.secure_cloned_response_field: Optional[Field] = create_cloned_field(
self.response_field
)
else:
self.response_field = None
self.secure_cloned_response_field = None
self.status_code = status_code
self.tags = tags or []
self.dependencies = dependencies or []
if dependencies:
self.dependencies = list(dependencies)
else:
self.dependencies = []
self.summary = summary
self.description = description or inspect.cleandoc(self.endpoint.__doc__ or "")
self.response_description = response_description
@@ -246,7 +258,7 @@ class APIRoute(routing.Route):
assert lenient_issubclass(
model, BaseModel
), "A response model must be a Pydantic model"
response_name = f"Response_{additional_status_code}_{self.name}"
response_name = f"Response_{additional_status_code}_{self.unique_id}"
response_field = Field(
name=response_name,
type_=model,
@@ -262,9 +274,6 @@ class APIRoute(routing.Route):
else:
self.response_fields = {}
self.deprecated = deprecated
if methods is None:
methods = ["GET"]
self.methods = methods
self.operation_id = operation_id
self.response_model_include = response_model_include
self.response_model_exclude = response_model_exclude
@@ -273,7 +282,6 @@ class APIRoute(routing.Route):
self.include_in_schema = include_in_schema
self.response_class = response_class
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"
@@ -283,7 +291,7 @@ class APIRoute(routing.Route):
0,
get_parameterless_sub_dependant(depends=depends, path=self.path_format),
)
self.body_field = get_body_field(dependant=self.dependant, name=self.name)
self.body_field = get_body_field(dependant=self.dependant, name=self.unique_id)
self.dependency_overrides_provider = dependency_overrides_provider
self.app = request_response(
get_app(
@@ -322,13 +330,13 @@ class APIRouter(routing.Router):
response_model: Type[Any] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: List[params.Depends] = None,
dependencies: Sequence[params.Depends] = None,
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
methods: List[str] = None,
methods: Optional[Union[Set[str], List[str]]] = None,
operation_id: str = None,
response_model_include: Set[str] = None,
response_model_exclude: Set[str] = set(),
@@ -344,7 +352,7 @@ class APIRouter(routing.Router):
response_model=response_model,
status_code=status_code,
tags=tags or [],
dependencies=dependencies or [],
dependencies=dependencies,
summary=summary,
description=description,
response_description=response_description,
@@ -370,7 +378,7 @@ class APIRouter(routing.Router):
response_model: Type[Any] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: List[params.Depends] = None,
dependencies: Sequence[params.Depends] = None,
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
@@ -393,7 +401,7 @@ class APIRouter(routing.Router):
response_model=response_model,
status_code=status_code,
tags=tags or [],
dependencies=dependencies or [],
dependencies=dependencies,
summary=summary,
description=description,
response_description=response_description,
@@ -432,7 +440,7 @@ class APIRouter(routing.Router):
*,
prefix: str = "",
tags: List[str] = None,
dependencies: List[params.Depends] = None,
dependencies: Sequence[params.Depends] = None,
responses: Dict[Union[int, str], Dict[str, Any]] = None,
) -> None:
if prefix:
@@ -451,7 +459,8 @@ class APIRouter(routing.Router):
response_model=route.response_model,
status_code=route.status_code,
tags=(route.tags or []) + (tags or []),
dependencies=(dependencies or []) + (route.dependencies or []),
dependencies=list(dependencies or [])
+ list(route.dependencies or []),
summary=route.summary,
description=route.description,
response_description=route.response_description,
@@ -471,7 +480,7 @@ class APIRouter(routing.Router):
self.add_route(
prefix + route.path,
route.endpoint,
methods=route.methods,
methods=list(route.methods or []),
include_in_schema=route.include_in_schema,
name=route.name,
)
@@ -491,7 +500,7 @@ class APIRouter(routing.Router):
response_model: Type[Any] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: List[params.Depends] = None,
dependencies: Sequence[params.Depends] = None,
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
@@ -512,7 +521,7 @@ class APIRouter(routing.Router):
response_model=response_model,
status_code=status_code,
tags=tags or [],
dependencies=dependencies or [],
dependencies=dependencies,
summary=summary,
description=description,
response_description=response_description,
@@ -536,7 +545,7 @@ class APIRouter(routing.Router):
response_model: Type[Any] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: List[params.Depends] = None,
dependencies: Sequence[params.Depends] = None,
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
@@ -556,7 +565,7 @@ class APIRouter(routing.Router):
response_model=response_model,
status_code=status_code,
tags=tags or [],
dependencies=dependencies or [],
dependencies=dependencies,
summary=summary,
description=description,
response_description=response_description,
@@ -580,7 +589,7 @@ class APIRouter(routing.Router):
response_model: Type[Any] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: List[params.Depends] = None,
dependencies: Sequence[params.Depends] = None,
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
@@ -600,7 +609,7 @@ class APIRouter(routing.Router):
response_model=response_model,
status_code=status_code,
tags=tags or [],
dependencies=dependencies or [],
dependencies=dependencies,
summary=summary,
description=description,
response_description=response_description,
@@ -624,7 +633,7 @@ class APIRouter(routing.Router):
response_model: Type[Any] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: List[params.Depends] = None,
dependencies: Sequence[params.Depends] = None,
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
@@ -644,7 +653,7 @@ class APIRouter(routing.Router):
response_model=response_model,
status_code=status_code,
tags=tags or [],
dependencies=dependencies or [],
dependencies=dependencies,
summary=summary,
description=description,
response_description=response_description,
@@ -668,7 +677,7 @@ class APIRouter(routing.Router):
response_model: Type[Any] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: List[params.Depends] = None,
dependencies: Sequence[params.Depends] = None,
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
@@ -688,7 +697,7 @@ class APIRouter(routing.Router):
response_model=response_model,
status_code=status_code,
tags=tags or [],
dependencies=dependencies or [],
dependencies=dependencies,
summary=summary,
description=description,
response_description=response_description,
@@ -712,7 +721,7 @@ class APIRouter(routing.Router):
response_model: Type[Any] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: List[params.Depends] = None,
dependencies: Sequence[params.Depends] = None,
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
@@ -732,7 +741,7 @@ class APIRouter(routing.Router):
response_model=response_model,
status_code=status_code,
tags=tags or [],
dependencies=dependencies or [],
dependencies=dependencies,
summary=summary,
description=description,
response_description=response_description,
@@ -756,7 +765,7 @@ class APIRouter(routing.Router):
response_model: Type[Any] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: List[params.Depends] = None,
dependencies: Sequence[params.Depends] = None,
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
@@ -776,7 +785,7 @@ class APIRouter(routing.Router):
response_model=response_model,
status_code=status_code,
tags=tags or [],
dependencies=dependencies or [],
dependencies=dependencies,
summary=summary,
description=description,
response_description=response_description,
@@ -800,7 +809,7 @@ class APIRouter(routing.Router):
response_model: Type[Any] = None,
status_code: int = 200,
tags: List[str] = None,
dependencies: List[params.Depends] = None,
dependencies: Sequence[params.Depends] = None,
summary: str = None,
description: str = None,
response_description: str = "Successful Response",
@@ -820,7 +829,7 @@ class APIRouter(routing.Router):
response_model=response_model,
status_code=status_code,
tags=tags or [],
dependencies=dependencies or [],
dependencies=dependencies,
summary=summary,
description=description,
response_description=response_description,

View File

@@ -54,7 +54,7 @@ class APIKeyCookie(APIKeyBase):
self.auto_error = auto_error
async def __call__(self, request: Request) -> Optional[str]:
api_key: str = request.cookies.get(self.model.name)
api_key = request.cookies.get(self.model.name)
if not api_key:
if self.auto_error:
raise HTTPException(

View File

@@ -56,7 +56,9 @@ class HTTPBasic(HTTPBase):
self.realm = realm
self.auto_error = auto_error
async def __call__(self, request: Request) -> Optional[HTTPBasicCredentials]:
async def __call__( # type: ignore
self, request: Request
) -> Optional[HTTPBasicCredentials]:
authorization: str = request.headers.get("Authorization")
scheme, param = get_authorization_scheme_param(authorization)
if self.realm:

View File

@@ -2,7 +2,7 @@ from typing import List, Optional
from fastapi.exceptions import HTTPException
from fastapi.openapi.models import OAuth2 as OAuth2Model, OAuthFlows as OAuthFlowsModel
from fastapi.params import Form
from fastapi.param_functions import Form
from fastapi.security.base import SecurityBase
from fastapi.security.utils import get_authorization_scheme_param
from starlette.requests import Request

View File

@@ -10,9 +10,7 @@ from pydantic.utils import lenient_issubclass
from starlette.routing import BaseRoute
def get_flat_models_from_routes(
routes: Sequence[Type[BaseRoute]]
) -> Set[Type[BaseModel]]:
def get_flat_models_from_routes(routes: Sequence[BaseRoute]) -> Set[Type[BaseModel]]:
body_fields_from_routes: List[Field] = []
responses_from_routes: List[Field] = []
for route in routes:
@@ -39,7 +37,7 @@ def get_model_definitions(
) -> Dict[str, Any]:
definitions: Dict[str, Dict] = {}
for model in flat_models:
m_schema, m_definitions = model_process_schema(
m_schema, m_definitions, m_nested_models = model_process_schema(
model, model_name_map=model_name_map, ref_prefix=REF_PREFIX
)
definitions.update(m_definitions)
@@ -95,3 +93,10 @@ def create_cloned_field(field: Field) -> Field:
new_field.shape = field.shape
new_field._populate_validators()
return new_field
def generate_operation_id_for_path(*, name: str, path: str, method: str) -> str:
operation_id = name + path
operation_id = operation_id.replace("{", "_").replace("}", "_").replace("/", "_")
operation_id = operation_id + "_" + method.lower()
return operation_id

View File

@@ -90,6 +90,7 @@ nav:
- Project Generation - Template: 'project-generation.md'
- Alternatives, Inspiration and Comparisons: 'alternatives.md'
- History, Design and Future: 'history-design-future.md'
- External Links and Articles: 'external-links.md'
- Benchmarks: 'benchmarks.md'
- Help FastAPI - Get Help: 'help-fastapi.md'
- Development - Contributing: 'contributing.md'

View File

@@ -19,8 +19,8 @@ classifiers = [
"Topic :: Internet :: WWW/HTTP :: HTTP Servers",
]
requires = [
"starlette >=0.11.1,<=0.12.0",
"pydantic >=0.28,<=0.28.0"
"starlette >=0.11.1,<=0.12.7",
"pydantic >=0.30,<=0.30.0"
]
description-file = "README.md"
requires-python = ">=3.6"

View File

@@ -3,6 +3,6 @@
set -e
set -x
mypy fastapi --disallow-untyped-defs --follow-imports=skip
mypy fastapi --disallow-untyped-defs
black fastapi tests --check
isort --multi-line=3 --trailing-comma --force-grid-wrap=0 --combine-as --line-width 88 --recursive --check-only --thirdparty fastapi fastapi tests

View File

@@ -0,0 +1,23 @@
from fastapi import FastAPI
from pydantic import BaseModel
def test_get_openapi():
app = FastAPI()
class Model(BaseModel):
pass
class Model2(BaseModel):
a: Model
class Model3(BaseModel):
c: Model
d: Model2
@app.get("/", response_model=Model3)
def f():
pass # pragma: no cover
openapi = app.openapi()
assert isinstance(openapi, dict)

View File

View File

View File

@@ -0,0 +1,8 @@
from fastapi import APIRouter, Body
router = APIRouter()
@router.post("/compute")
def compute(a: int = Body(...), b: str = Body(...)):
return {"a": a, "b": b}

View File

@@ -0,0 +1,8 @@
from fastapi import APIRouter, Body
router = APIRouter()
@router.post("/compute/")
def compute(a: int = Body(...), b: str = Body(...)):
return {"a": a, "b": b}

View File

@@ -0,0 +1,8 @@
from fastapi import FastAPI
from . import a, b
app = FastAPI()
app.include_router(a.router, prefix="/a")
app.include_router(b.router, prefix="/b")

View File

@@ -0,0 +1,155 @@
from starlette.testclient import TestClient
from .app.main import app
client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"paths": {
"/a/compute": {
"post": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Compute",
"operationId": "compute_a_compute_post",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Body_compute_a_compute_post"
}
}
},
"required": True,
},
}
},
"/b/compute/": {
"post": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Compute",
"operationId": "compute_b_compute__post",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Body_compute_b_compute__post"
}
}
},
"required": True,
},
}
},
},
"components": {
"schemas": {
"Body_compute_b_compute__post": {
"title": "Body_compute_b_compute__post",
"required": ["a", "b"],
"type": "object",
"properties": {
"a": {"title": "A", "type": "integer"},
"b": {"title": "B", "type": "string"},
},
},
"Body_compute_a_compute_post": {
"title": "Body_compute_a_compute_post",
"required": ["a", "b"],
"type": "object",
"properties": {
"a": {"title": "A", "type": "integer"},
"b": {"title": "B", "type": "string"},
},
},
"ValidationError": {
"title": "ValidationError",
"required": ["loc", "msg", "type"],
"type": "object",
"properties": {
"loc": {
"title": "Location",
"type": "array",
"items": {"type": "string"},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
},
},
"HTTPValidationError": {
"title": "HTTPValidationError",
"type": "object",
"properties": {
"detail": {
"title": "Detail",
"type": "array",
"items": {"$ref": "#/components/schemas/ValidationError"},
}
},
},
}
},
}
def test_openapi_schema():
response = client.get("/openapi.json")
assert response.status_code == 200
assert response.json() == openapi_schema
def test_post_a():
data = {"a": 2, "b": "foo"}
response = client.post("/a/compute", json=data)
assert response.status_code == 200
data = response.json()
def test_post_a_invalid():
data = {"a": "bar", "b": "foo"}
response = client.post("/a/compute", json=data)
assert response.status_code == 422
def test_post_b():
data = {"a": 2, "b": "foo"}
response = client.post("/b/compute/", json=data)
assert response.status_code == 200
data = response.json()
def test_post_b_invalid():
data = {"a": "bar", "b": "foo"}
response = client.post("/b/compute/", json=data)
assert response.status_code == 422

View File

@@ -0,0 +1,23 @@
from fastapi import APIRouter, FastAPI
from starlette.testclient import TestClient
app = FastAPI()
router = APIRouter()
@router.get("/users/{id}")
def read_user(segment: str, id: str):
return {"segment": segment, "id": id}
app.include_router(router, prefix="/{segment}")
client = TestClient(app)
def test_get():
response = client.get("/seg/users/foo")
assert response.status_code == 200
assert response.json() == {"segment": "seg", "id": "foo"}

View File

@@ -66,7 +66,7 @@ openapi_schema = {
"content": {
"application/x-www-form-urlencoded": {
"schema": {
"$ref": "#/components/schemas/Body_read_current_user"
"$ref": "#/components/schemas/Body_read_current_user_login_post"
}
}
},
@@ -90,8 +90,8 @@ openapi_schema = {
},
"components": {
"schemas": {
"Body_read_current_user": {
"title": "Body_read_current_user",
"Body_read_current_user_login_post": {
"title": "Body_read_current_user_login_post",
"required": ["grant_type", "username", "password"],
"type": "object",
"properties": {

View File

@@ -73,7 +73,7 @@ openapi_schema = {
"content": {
"application/x-www-form-urlencoded": {
"schema": {
"$ref": "#/components/schemas/Body_read_current_user"
"$ref": "#/components/schemas/Body_read_current_user_login_post"
}
}
},
@@ -97,8 +97,8 @@ openapi_schema = {
},
"components": {
"schemas": {
"Body_read_current_user": {
"title": "Body_read_current_user",
"Body_read_current_user_login_post": {
"title": "Body_read_current_user_login_post",
"required": ["grant_type", "username", "password"],
"type": "object",
"properties": {

View File

@@ -14,7 +14,7 @@ openapi_schema = {
"content": {
"application/json": {
"schema": {
"title": "Response_Read_Notes",
"title": "Response_Read_Notes_Notes__Get",
"type": "array",
"items": {"$ref": "#/components/schemas/Note"},
}

View File

@@ -40,7 +40,9 @@ openapi_schema = {
"requestBody": {
"content": {
"application/json": {
"schema": {"$ref": "#/components/schemas/Body_update_item"}
"schema": {
"$ref": "#/components/schemas/Body_update_item_items__item_id__put"
}
}
},
"required": True,
@@ -70,8 +72,8 @@ openapi_schema = {
"full_name": {"title": "Full_Name", "type": "string"},
},
},
"Body_update_item": {
"title": "Body_update_item",
"Body_update_item_items__item_id__put": {
"title": "Body_update_item_items__item_id__put",
"required": ["item", "user", "importance"],
"type": "object",
"properties": {

View File

@@ -41,7 +41,9 @@ openapi_schema = {
"requestBody": {
"content": {
"application/json": {
"schema": {"$ref": "#/components/schemas/Body_update_item"}
"schema": {
"$ref": "#/components/schemas/Body_update_item_items__item_id__put"
}
}
},
"required": True,
@@ -71,8 +73,8 @@ openapi_schema = {
"tax": {"title": "Tax", "type": "number"},
},
},
"Body_update_item": {
"title": "Body_update_item",
"Body_update_item_items__item_id__put": {
"title": "Body_update_item_items__item_id__put",
"required": ["item"],
"type": "object",
"properties": {"item": {"$ref": "#/components/schemas/Item"}},

View File

@@ -44,7 +44,9 @@ openapi_schema = {
"requestBody": {
"content": {
"application/json": {
"schema": {"$ref": "#/components/schemas/Body_read_items"}
"schema": {
"$ref": "#/components/schemas/Body_read_items_items__item_id__put"
}
}
}
},
@@ -53,8 +55,8 @@ openapi_schema = {
},
"components": {
"schemas": {
"Body_read_items": {
"title": "Body_read_items",
"Body_read_items_items__item_id__put": {
"title": "Body_read_items_items__item_id__put",
"type": "object",
"properties": {
"start_datetime": {

View File

@@ -16,7 +16,7 @@ openapi_schema = {
"content": {
"application/json": {
"schema": {
"title": "Response_Read_Item",
"title": "Response_Read_Item_Items__Item_Id__Get",
"anyOf": [
{"$ref": "#/components/schemas/PlaneItem"},
{"$ref": "#/components/schemas/CarItem"},

View File

@@ -16,7 +16,7 @@ openapi_schema = {
"content": {
"application/json": {
"schema": {
"title": "Response_Read_Items",
"title": "Response_Read_Items_Items__Get",
"type": "array",
"items": {"$ref": "#/components/schemas/Item"},
}

View File

@@ -16,7 +16,7 @@ openapi_schema = {
"content": {
"application/json": {
"schema": {
"title": "Response_Read_Keyword_Weights",
"title": "Response_Read_Keyword_Weights_Keyword-Weights__Get",
"type": "object",
"additionalProperties": {"type": "number"},
}

View File

@@ -35,6 +35,7 @@ openapi_schema = {
"schema": {
"title": "Model_Name",
"enum": ["alexnet", "resnet", "lenet"],
"type": "string",
},
"name": "model_name",
"in": "path",

View File

@@ -33,7 +33,9 @@ openapi_schema = {
"requestBody": {
"content": {
"multipart/form-data": {
"schema": {"$ref": "#/components/schemas/Body_create_file"}
"schema": {
"$ref": "#/components/schemas/Body_create_file_files__post"
}
}
},
"required": True,
@@ -64,7 +66,7 @@ openapi_schema = {
"content": {
"multipart/form-data": {
"schema": {
"$ref": "#/components/schemas/Body_create_upload_file"
"$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post"
}
}
},
@@ -75,16 +77,16 @@ openapi_schema = {
},
"components": {
"schemas": {
"Body_create_file": {
"title": "Body_create_file",
"Body_create_upload_file_uploadfile__post": {
"title": "Body_create_upload_file_uploadfile__post",
"required": ["file"],
"type": "object",
"properties": {
"file": {"title": "File", "type": "string", "format": "binary"}
},
},
"Body_create_upload_file": {
"title": "Body_create_upload_file",
"Body_create_file_files__post": {
"title": "Body_create_file_files__post",
"required": ["file"],
"type": "object",
"properties": {

View File

@@ -33,7 +33,9 @@ openapi_schema = {
"requestBody": {
"content": {
"multipart/form-data": {
"schema": {"$ref": "#/components/schemas/Body_create_files"}
"schema": {
"$ref": "#/components/schemas/Body_create_files_files__post"
}
}
},
"required": True,
@@ -64,7 +66,7 @@ openapi_schema = {
"content": {
"multipart/form-data": {
"schema": {
"$ref": "#/components/schemas/Body_create_upload_files"
"$ref": "#/components/schemas/Body_create_upload_files_uploadfiles__post"
}
}
},
@@ -87,8 +89,8 @@ openapi_schema = {
},
"components": {
"schemas": {
"Body_create_files": {
"title": "Body_create_files",
"Body_create_upload_files_uploadfiles__post": {
"title": "Body_create_upload_files_uploadfiles__post",
"required": ["files"],
"type": "object",
"properties": {
@@ -99,8 +101,8 @@ openapi_schema = {
}
},
},
"Body_create_upload_files": {
"title": "Body_create_upload_files",
"Body_create_files_files__post": {
"title": "Body_create_files_files__post",
"required": ["files"],
"type": "object",
"properties": {

View File

@@ -32,7 +32,9 @@ openapi_schema = {
"requestBody": {
"content": {
"application/x-www-form-urlencoded": {
"schema": {"$ref": "#/components/schemas/Body_login"}
"schema": {
"$ref": "#/components/schemas/Body_login_login__post"
}
}
},
"required": True,
@@ -42,8 +44,8 @@ openapi_schema = {
},
"components": {
"schemas": {
"Body_login": {
"title": "Body_login",
"Body_login_login__post": {
"title": "Body_login_login__post",
"required": ["username", "password"],
"type": "object",
"properties": {

View File

@@ -34,7 +34,9 @@ openapi_schema = {
"requestBody": {
"content": {
"multipart/form-data": {
"schema": {"$ref": "#/components/schemas/Body_create_file"}
"schema": {
"$ref": "#/components/schemas/Body_create_file_files__post"
}
}
},
"required": True,
@@ -44,8 +46,8 @@ openapi_schema = {
},
"components": {
"schemas": {
"Body_create_file": {
"title": "Body_create_file",
"Body_create_file_files__post": {
"title": "Body_create_file_files__post",
"required": ["file", "fileb", "token"],
"type": "object",
"properties": {

View File

@@ -31,7 +31,9 @@ openapi_schema = {
"requestBody": {
"content": {
"application/x-www-form-urlencoded": {
"schema": {"$ref": "#/components/schemas/Body_login"}
"schema": {
"$ref": "#/components/schemas/Body_login_token_post"
}
}
},
"required": True,
@@ -54,8 +56,8 @@ openapi_schema = {
},
"components": {
"schemas": {
"Body_login": {
"title": "Body_login",
"Body_login_token_post": {
"title": "Body_login_token_post",
"required": ["username", "password"],
"type": "object",
"properties": {

View File

@@ -42,7 +42,7 @@ openapi_schema = {
"content": {
"application/x-www-form-urlencoded": {
"schema": {
"$ref": "#/components/schemas/Body_login_for_access_token"
"$ref": "#/components/schemas/Body_login_for_access_token_token_post"
}
}
},
@@ -116,8 +116,8 @@ openapi_schema = {
"token_type": {"title": "Token_Type", "type": "string"},
},
},
"Body_login_for_access_token": {
"title": "Body_login_for_access_token",
"Body_login_for_access_token_token_post": {
"title": "Body_login_for_access_token_token_post",
"required": ["username", "password"],
"type": "object",
"properties": {
@@ -177,6 +177,12 @@ openapi_schema = {
}
def test_openapi_schema():
response = client.get("/openapi.json")
assert response.status_code == 200
assert response.json() == openapi_schema
def get_access_token(username="johndoe", password="secret", scope=None):
data = {"username": username, "password": password}
if scope:
@@ -187,12 +193,6 @@ def get_access_token(username="johndoe", password="secret", scope=None):
return access_token
def test_openapi_schema():
response = client.get("/openapi.json")
assert response.status_code == 200
assert response.json() == openapi_schema
def test_login():
response = client.post("/token", data={"username": "johndoe", "password": "secret"})
assert response.status_code == 200

View File

@@ -16,7 +16,7 @@ openapi_schema = {
"content": {
"application/json": {
"schema": {
"title": "Response_Read_Users",
"title": "Response_Read_Users_Users__Get",
"type": "array",
"items": {"$ref": "#/components/schemas/User"},
}
@@ -168,7 +168,7 @@ openapi_schema = {
"content": {
"application/json": {
"schema": {
"title": "Response_Read_Items",
"title": "Response_Read_Items_Items__Get",
"type": "array",
"items": {"$ref": "#/components/schemas/Item"},
}

124
tests/test_union_body.py Normal file
View File

@@ -0,0 +1,124 @@
from typing import Optional, Union
from fastapi import FastAPI
from pydantic import BaseModel
from starlette.testclient import TestClient
app = FastAPI()
class Item(BaseModel):
name: Optional[str] = None
class OtherItem(BaseModel):
price: int
@app.post("/items/")
def save_union_body(item: Union[OtherItem, Item]):
return {"item": item}
client = TestClient(app)
item_openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"paths": {
"/items/": {
"post": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Save Union Body",
"operationId": "save_union_body_items__post",
"requestBody": {
"content": {
"application/json": {
"schema": {
"title": "Item",
"anyOf": [
{"$ref": "#/components/schemas/OtherItem"},
{"$ref": "#/components/schemas/Item"},
],
}
}
},
"required": True,
},
}
}
},
"components": {
"schemas": {
"OtherItem": {
"title": "OtherItem",
"required": ["price"],
"type": "object",
"properties": {"price": {"title": "Price", "type": "integer"}},
},
"Item": {
"title": "Item",
"type": "object",
"properties": {"name": {"title": "Name", "type": "string"}},
},
"ValidationError": {
"title": "ValidationError",
"required": ["loc", "msg", "type"],
"type": "object",
"properties": {
"loc": {
"title": "Location",
"type": "array",
"items": {"type": "string"},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
},
},
"HTTPValidationError": {
"title": "HTTPValidationError",
"type": "object",
"properties": {
"detail": {
"title": "Detail",
"type": "array",
"items": {"$ref": "#/components/schemas/ValidationError"},
}
},
},
}
},
}
def test_item_openapi_schema():
response = client.get("/openapi.json")
assert response.status_code == 200
assert response.json() == item_openapi_schema
def test_post_other_item():
response = client.post("/items/", json={"price": 100})
assert response.status_code == 200
assert response.json() == {"item": {"price": 100}}
def test_post_item():
response = client.post("/items/", json={"name": "Foo"})
assert response.status_code == 200
assert response.json() == {"item": {"name": "Foo"}}

View File

@@ -0,0 +1,140 @@
import sys
from typing import Optional, Union
import pytest
from fastapi import FastAPI
from pydantic import BaseModel
from starlette.testclient import TestClient
# In Python 3.6:
# u = Union[ExtendedItem, Item] == __main__.Item
# But in Python 3.7:
# u = Union[ExtendedItem, Item] == typing.Union[__main__.ExtendedItem, __main__.Item]
skip_py36 = pytest.mark.skipif(sys.version_info < (3, 7), reason="skip python3.6")
app = FastAPI()
class Item(BaseModel):
name: Optional[str] = None
class ExtendedItem(Item):
age: int
@app.post("/items/")
def save_union_different_body(item: Union[ExtendedItem, Item]):
return {"item": item}
client = TestClient(app)
inherited_item_openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"paths": {
"/items/": {
"post": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Save Union Different Body",
"operationId": "save_union_different_body_items__post",
"requestBody": {
"content": {
"application/json": {
"schema": {
"title": "Item",
"anyOf": [
{"$ref": "#/components/schemas/ExtendedItem"},
{"$ref": "#/components/schemas/Item"},
],
}
}
},
"required": True,
},
}
}
},
"components": {
"schemas": {
"Item": {
"title": "Item",
"type": "object",
"properties": {"name": {"title": "Name", "type": "string"}},
},
"ExtendedItem": {
"title": "ExtendedItem",
"required": ["age"],
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},
"age": {"title": "Age", "type": "integer"},
},
},
"ValidationError": {
"title": "ValidationError",
"required": ["loc", "msg", "type"],
"type": "object",
"properties": {
"loc": {
"title": "Location",
"type": "array",
"items": {"type": "string"},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
},
},
"HTTPValidationError": {
"title": "HTTPValidationError",
"type": "object",
"properties": {
"detail": {
"title": "Detail",
"type": "array",
"items": {"$ref": "#/components/schemas/ValidationError"},
}
},
},
}
},
}
@skip_py36
def test_inherited_item_openapi_schema():
response = client.get("/openapi.json")
assert response.status_code == 200
assert response.json() == inherited_item_openapi_schema
@skip_py36
def test_post_extended_item():
response = client.post("/items/", json={"name": "Foo", "age": 5})
assert response.status_code == 200
assert response.json() == {"item": {"name": "Foo", "age": 5}}
@skip_py36
def test_post_item():
response = client.post("/items/", json={"name": "Foo"})
assert response.status_code == 200
assert response.json() == {"item": {"name": "Foo"}}