Compare commits

...

54 Commits
0.2.1 ... 0.7.0

Author SHA1 Message Date
Sebastián Ramírez
c14ec50f73 🔖 Release 0.7.0, with support for UploadFile 2019-03-03 21:06:42 +04:00
Sebastián Ramírez
6b6ea0da2e 📝 Update release notes with UploadFile 2019-03-03 21:05:58 +04:00
Sebastián Ramírez
0b9fe62a10 Add support for UploadFile class annotations (#63)
*  Add support for UploadFile annotations

* 📝 Update File upload docs with FileUpload class

*  Add tests for UploadFile support

* 📝 Update UploadFile docs
2019-03-03 20:52:37 +04:00
Sebastián Ramírez
1f03e85f06 🔖 Release 0.6.4 2019-03-02 22:33:48 +04:00
Sebastián Ramírez
b98bf178a6 📝 Update release notes with WebSockets 2019-03-02 21:51:01 +04:00
Sebastián Ramírez
bbd2198fa2 Add docs for WebSockets (#62) 2019-03-02 21:45:15 +04:00
Sebastián Ramírez
e2723e8480 📝 Update release notes 2019-03-02 20:00:27 +04:00
Sebastián Ramírez
1896153d58 ✏️ Fix typos 2019-03-02 19:54:52 +04:00
Sebastián Ramírez
770b4421f9 📝 Add History, Design and Future to docs 2019-03-02 19:54:15 +04:00
Sebastián Ramírez
e89aacbdf7 📝 Add link to Python docs in debugging section 2019-03-02 17:56:30 +04:00
Sebastián Ramírez
cf25291650 📝 Update release notes 2019-03-02 17:55:07 +04:00
Sebastián Ramírez
13772fbd11 📝 Add note about bigger applications in Docker 2019-03-02 17:52:24 +04:00
Sebastián Ramírez
1d69b6f480 📝 Update release notes 2019-03-02 17:44:48 +04:00
Sebastián Ramírez
01d6aa3dd1 📝 Add docs for debugging 2019-03-02 17:40:01 +04:00
Sebastián Ramírez
74db8ddf9b 📝 Update release notes 2019-03-02 13:53:16 +04:00
Sebastián Ramírez
819b3b2516 📝 Add technical details about async def handling (#61)
#33
2019-03-02 13:48:06 +04:00
Sebastián Ramírez
76fb2879ed ✏️ Fix typo in release notes 2019-03-02 13:02:06 +04:00
Sebastián Ramírez
daaf654868 🔖 Release 0.6.3: favicons in docs 2019-02-24 01:49:04 +04:00
Sebastián Ramírez
6e0553b4cf 📝 Update release notes, favicons 2019-02-24 01:32:39 +04:00
Sebastián Ramírez
8e1ecaf221 💄 Add FastAPI favicons to docs (#53) 2019-02-24 01:31:50 +04:00
Sebastián Ramírez
9e610030fb ✏️ Fix typo in release notes 2019-02-24 01:12:33 +04:00
Sebastián Ramírez
9940c1511e 🔖 Release 0.6.2, SQL tutorial improvements and project generator 2019-02-24 01:09:49 +04:00
Sebastián Ramírez
24d94298d0 📝 Update release notes with SQL tutorial changes 2019-02-24 01:09:00 +04:00
Sebastián Ramírez
e3b4019fa3 Update SQL with dependency and intro project generator (#52) 2019-02-24 01:04:44 +04:00
Sebastián Ramírez
502ab432b8 💄 Add PNG images to improve link previews 2019-02-23 23:59:17 +04:00
Sebastián Ramírez
9051ec3816 📝 Improve naming of middleware in SQLAlchemy tutorial 2019-02-21 10:15:39 +04:00
Sebastián Ramírez
22f4e18cdd ✏️ Fix GraphQL typo 2019-02-20 22:02:19 +04:00
Sebastián Ramírez
4473e6a096 🔖 Release 0.6.1: GraphQL 2019-02-20 21:59:24 +04:00
Sebastián Ramírez
984dd71d13 Add docs for GraphQL (#48) 2019-02-20 21:58:26 +04:00
Sebastián Ramírez
bf53518141 📝 Include PR in Release Notes 2019-02-19 21:22:51 +04:00
Sebastián Ramírez
0ed55eb7d3 🔖 Release 0.6.0, upgrade Starlette, improve SQLAlchemy compatibility 2019-02-19 21:20:32 +04:00
Sebastián Ramírez
12e087f0b5 Use request.state for SQLAlchemy session in tutorial (#45) 2019-02-19 21:18:28 +04:00
Sebastián Ramírez
ba10838c30 ⬆️ Upgrade Starlette and fix compatibility (#44) 2019-02-19 20:27:48 +04:00
Sebastián Ramírez
656e1c7ce9 🙈 Add test.db to .gitignore 2019-02-18 22:55:48 +04:00
Sebastián Ramírez
88b31e6a4d 🔖 Release 0.5.1: docs 2019-02-18 22:52:03 +04:00
Sebastián Ramírez
2c3b826810 📝 Add contributing/development docs (#42) 2019-02-18 22:40:31 +04:00
Sebastián Ramírez
aa64eecda6 Update error handling docs, including Starlette's utils (#41)
📝 Update error handling docs, including Starlette's utils
2019-02-18 21:58:21 +04:00
Sebastián Ramírez
712b18a58a 📝 Update docs 2019-02-16 19:36:09 +04:00
Sebastián Ramírez
a809da5567 📝 Add note about path declaration order 2019-02-16 19:23:42 +04:00
Sebastián Ramírez
80b68cd97d 📝 Add section about help/getting help 2019-02-16 18:10:15 +04:00
Sebastián Ramírez
894e131e03 🔖 Release 0.5.0 with new HTTPException 2019-02-16 17:06:31 +04:00
Sebastián Ramírez
8772e2f2ee Add HTTPException with custom headers (#35)
* 📝 Update Release Notes with issue templates

*  Add HTTPException with support for headers

Including docs and tests

* 📝 Update Security docs to use new HTTPException
2019-02-16 17:01:29 +04:00
Sebastián Ramírez
7edbd9345b Update issue templates (#34)
Update issue templates
2019-02-16 14:09:20 +04:00
Sebastián Ramírez
56819fdd89 📝 Update Release Notes 2019-02-16 13:47:05 +04:00
euri10
febf8e7341 📝 Add docs for using the Starlette Request directly (#25)
Add docs for using the Starlette Request directly
2019-02-16 12:44:56 +04:00
Sebastián Ramírez
293ebd7cc2 📝 Update Release Notes 2019-02-15 23:19:19 +04:00
Sebastián Ramírez
54e3949f74 📝 Update SQLAlchemy docs, with current workaround 2019-02-15 22:05:18 +04:00
Sebastián Ramírez
acbcbba94f 🔖 Release 0.4.0 with openapi_prefix, #26 2019-02-14 23:04:55 +04:00
Sebastián Ramírez
f7b7a099c3 📝 Update Release Notes and openapi_prefix docs 2019-02-14 23:02:47 +04:00
Kabir Khan
0ea0d0e82a Add Open API prefix route - correct docs behind reverse proxy (#26)
Add Open API prefix route - correct docs behind reverse proxy.
2019-02-14 22:57:49 +04:00
Sebastián Ramírez
890f1f7899 📝 Add note about DB Browser for SQLite in SQL docs 2019-02-12 23:31:18 +04:00
Sebastián Ramírez
783816a7e3 📝 Update Release Notes 2019-02-12 23:07:54 +04:00
Sebastián Ramírez
7863490c8c 🔖 Release after SQLAlchemy fix: 0.3.0 2019-02-12 23:06:05 +04:00
Sebastián Ramírez
955e9fcb31 Update fix SQLAlchemy support with ORM (#30)
 SQLAlchemy ORM support

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

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

*  Add sqlalchemy to testing dependencies (not required to use FastAPI)
2019-02-12 23:02:21 +04:00
89 changed files with 2394 additions and 240 deletions

42
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,42 @@
---
name: Bug report
about: Create a report to help us improve
title: "[BUG]"
labels: bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Create a file with '...'
2. Add a path operation function with '....'
3. Open the browser and call it with a payload of '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Environment:**
- OS: [e.g. Linux / Windows / macOS]
- FastAPI Version [e.g. 0.3.0], get it with:
```Python
import fastapi
print(fastapi.__version__)
```
- Python version, get it with:
```bash
python --version
```
**Additional context**
Add any other context about the problem here.

View File

@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: "[FEATURE]"
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I want to be able to [...] but I can't because [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

17
.github/ISSUE_TEMPLATE/question.md vendored Normal file
View File

@@ -0,0 +1,17 @@
---
name: Question
about: Ask a question
title: "[QUESTION]"
labels: question
assignees: ''
---
**Description**
How can I [...]?
Is it possible to [...]?
**Additional context**
Add any other context or screenshots about the feature request here.

1
.gitignore vendored
View File

@@ -10,3 +10,4 @@ site
.coverage
coverage.xml
.netlify
test.db

View File

@@ -21,9 +21,10 @@ email-validator = "*"
ujson = "*"
flake8 = "*"
python-multipart = "*"
sqlalchemy = "*"
[packages]
starlette = "==0.10.1"
starlette = "==0.11.1"
pydantic = "==0.18.2"
[requires]

173
Pipfile.lock generated
View File

@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "20483e725e92e679c4c21ea3ff0043d759c74102b181f16b67908f979f854d5c"
"sha256": "6b55a2dcce8b6bd5a1be8f170acb18478149218a01d1b026981a6297800cd3fa"
},
"pipfile-spec": 6,
"requires": {
@@ -34,10 +34,10 @@
},
"starlette": {
"hashes": [
"sha256:7cc05c33d00db3b2ddfd7516a737544ed0a34c9dd0ced94076f29b581ce4f532"
"sha256:9d48b35d1fc7521d59ae53c421297ab3878d3c7cd4b75266d77f6c73cccb78bb"
],
"index": "pypi",
"version": "==0.10.1"
"version": "==0.11.1"
}
},
"develop": {
@@ -50,10 +50,10 @@
},
"atomicwrites": {
"hashes": [
"sha256:0312ad34fcad8fac3704d441f7b317e50af620823353ec657a53e981f92920c0",
"sha256:ec9ae8adaae229e4f8446952d204a3e4b5fdd2d099f9be3aaf556120135fb3ee"
"sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4",
"sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"
],
"version": "==1.2.1"
"version": "==1.3.0"
},
"attrs": {
"hashes": [
@@ -199,19 +199,19 @@
},
"flake8": {
"hashes": [
"sha256:09b9bb539920776da542e67a570a5df96ff933c9a08b62cfae920bcc789e4383",
"sha256:e0f8cd519cfc0072c0ee31add5def09d2b3ef6040b34dc426445c3af9b02163c"
"sha256:6d8c66a65635d46d54de59b027a1dda40abbe2275b3164b634835ac9c13fd048",
"sha256:6eab21c6e34df2c05416faa40d0c59963008fff29b6f0ccfe8fa28152ab3e383"
],
"index": "pypi",
"version": "==3.7.4"
"version": "==3.7.6"
},
"flit": {
"hashes": [
"sha256:6aefa6ff89a993af7a7af40d3df3d0387d6663df99797981ec41b1431ec6d1e1",
"sha256:9969db9708305b64fd8acf20043fcff144f910222397a221fd29871f02ed4a6f"
"sha256:1d93f7a833ed8a6e120ddc40db5c4763bc39bccc75c05081ec8285ece718aefb",
"sha256:6f6f0fb83c51ffa3a150fa41b5ac118df9ea4a87c2c06dff4ebf9adbe7b52b36"
],
"index": "pypi",
"version": "==1.2.1"
"version": "==1.3"
},
"idna": {
"hashes": [
@@ -229,11 +229,11 @@
},
"ipython": {
"hashes": [
"sha256:6a9496209b76463f1dec126ab928919aaf1f55b38beb9219af3fe202f6bbdd12",
"sha256:f69932b1e806b38a7818d9a1e918e5821b685715040b48e59c657b3c7961b742"
"sha256:06de667a9e406924f97781bda22d5d76bfb39762b678762d86a466e63f65dc39",
"sha256:5d3e020a6b5f29df037555e5c45ab1088d6a7cf3bd84f47e0ba501eeb0c3ec82"
],
"markers": "python_version >= '3.3'",
"version": "==7.2.0"
"version": "==7.3.0"
},
"ipython-genutils": {
"hashes": [
@@ -387,27 +387,27 @@
},
"mkdocs-material": {
"hashes": [
"sha256:4b4af83c704d2bab41be3a5228e800a5e1157003368fbf548d95073ce19e0f61",
"sha256:86c0042c803586985bf79c99962ebd4644c3f0ff095d5df541f09fa48f5b62cc"
"sha256:63c49a7020e5d187d5adcd441b259e0b81ad418599b22e2c2574b419ed833851",
"sha256:90a240f268f182a96098490d35bb75d5efc86b2f67d63a82b8750da20a72ef60"
],
"index": "pypi",
"version": "==3.3.0"
"version": "==4.0.1"
},
"more-itertools": {
"hashes": [
"sha256:38a936c0a6d98a38bcc2d03fdaaedaba9f412879461dd2ceff8d37564d6522e4",
"sha256:c0a5785b1109a6bd7fac76d6837fd1feca158e54e521ccd2ae8bfe393cc9d4fc",
"sha256:fe7a7cae1ccb57d33952113ff4fa1bc5f879963600ed74918f1236e212ee50b9"
"sha256:0125e8f60e9e031347105eb1682cef932f5e97d7b9a1a28d9bf00c22a5daef40",
"sha256:590044e3942351a1bdb1de960b739ff4ce277960f2425ad4509446dbace8d9d1"
],
"version": "==5.0.0"
"markers": "python_version > '2.7'",
"version": "==6.0.0"
},
"mypy": {
"hashes": [
"sha256:986a7f97808a865405c5fd98fae5ebfa963c31520a56c783df159e9a81e41b3e",
"sha256:cc5df73cc11d35655a8c364f45d07b13c8db82c000def4bd7721be13356533b4"
"sha256:308c274eb8482fbf16006f549137ddc0d69e5a589465e37b99c4564414363ca7",
"sha256:e80fd6af34614a0e898a57f14296d0dacb584648f0339c2e000ddbf0f4cc2f8d"
],
"index": "pypi",
"version": "==0.660"
"version": "==0.670"
},
"mypy-extensions": {
"hashes": [
@@ -418,10 +418,10 @@
},
"nbconvert": {
"hashes": [
"sha256:08d21cf4203fabafd0d09bbd63f06131b411db8ebeede34b0fd4be4548351779",
"sha256:a8a2749f972592aa9250db975304af6b7337f32337e523a2c995cc9e12c07807"
"sha256:302554a2e219bc0fc84f3edd3e79953f3767b46ab67626fdec16e38ba3f7efe4",
"sha256:5de8fb2284422272a1d45abc77c07b888127550a6d602ce619592a2b08a474ff"
],
"version": "==5.4.0"
"version": "==5.4.1"
},
"nbformat": {
"hashes": [
@@ -445,10 +445,10 @@
},
"parso": {
"hashes": [
"sha256:4b8f9ed80c3a4a3191aa3261505d868aa552dd25649cb13a7d73b6b7315edf2d",
"sha256:5a120be2e8863993b597f1c0437efca799e90e0793c98ae5d4e34ebd00140e31"
"sha256:4580328ae3f548b358f4901e38c0578229186835f0fa0846e47369796dd5bcc9",
"sha256:68406ebd7eafe17f8e40e15a84b56848eccbf27d7c1feb89e93d8fca395706db"
],
"version": "==0.3.2"
"version": "==0.3.4"
},
"pexpect": {
"hashes": [
@@ -531,17 +531,17 @@
},
"pyrsistent": {
"hashes": [
"sha256:5a3827d57ad3e46820e5ee4ed5b9e0ee7bc4686df6634a7368bc1863a5c48a77"
"sha256:07f7ae71291af8b0dbad8c2ab630d8223e4a8c4e10fc37badda158c02e753acf"
],
"version": "==0.14.9"
"version": "==0.14.10"
},
"pytest": {
"hashes": [
"sha256:65aeaa77ae87c7fc95de56285282546cfa9c886dc8e5dc78313db1c25e21bc07",
"sha256:6ac6d467d9f053e95aaacd79f831dbecfe730f419c6c7022cb316b365cd9199d"
"sha256:067a1d4bf827ffdd56ad21bd46674703fce77c5957f6c1eef731f6146bfcef1c",
"sha256:9687049d53695ad45cf5fdc7bbd51f0c49f1ea3ecfc4b7f3fde7501b541f17f4"
],
"index": "pypi",
"version": "==4.2.0"
"version": "==4.3.0"
},
"pytest-cov": {
"hashes": [
@@ -553,10 +553,10 @@
},
"python-dateutil": {
"hashes": [
"sha256:063df5763652e21de43de7d9e00ccf239f953a832941e37be541614732cdfc93",
"sha256:88f9287c0174266bb0d8cedd395cfba9c58e87e5ad86b2ce58859bc11be3cf02"
"sha256:7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb",
"sha256:c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e"
],
"version": "==2.7.5"
"version": "==2.8.0"
},
"python-multipart": {
"hashes": [
@@ -583,33 +583,23 @@
},
"pyzmq": {
"hashes": [
"sha256:25a0715c8f69cf72f67cfe5a68a3f3ed391c67c063d2257bec0fe7fc2c7f08f8",
"sha256:2bab63759632c6b9e0d5bf19cc63c3b01df267d660e0abcf230cf0afaa966349",
"sha256:30ab49d99b24bf0908ebe1cdfa421720bfab6f93174e4883075b7ff38cc555ba",
"sha256:32c7ca9fc547a91e3c26fc6080b6982e46e79819e706eb414dd78f635a65d946",
"sha256:41219ae72b3cc86d97557fe5b1ef5d1adc1057292ec597b50050874a970a39cf",
"sha256:4b8c48a9a13cea8f1f16622f9bd46127108af14cd26150461e3eab71e0de3e46",
"sha256:55724997b4a929c0d01b43c95051318e26ddbae23565018e138ae2dc60187e59",
"sha256:65f0a4afae59d4fc0aad54a917ab599162613a761b760ba167d66cc646ac3786",
"sha256:6f88591a8b246f5c285ee6ce5c1bf4f6bd8464b7f090b1333a446b6240a68d40",
"sha256:75022a4c60dcd8765bb9ca32f6de75a0ec83b0d96e0309dc479f4c7b21f26cb7",
"sha256:76ea493bfab18dcb090d825f3662b5612e2def73dffc196d51a5194b0294a81d",
"sha256:7b60c045b80709e4e3c085bab9b691e71761b44c2b42dbb047b8b498e7bc16b3",
"sha256:8e6af2f736734aef8ed6f278f9f552ec7f37b1a6b98e59b887484a840757f67d",
"sha256:9ac2298e486524331e26390eac14e4627effd3f8e001d4266ed9d8f1d2d31cce",
"sha256:9ba650f493a9bc1f24feca1d90fce0e5dd41088a252ac9840131dfbdbf3815ca",
"sha256:a02a4a385e394e46012dc83d2e8fd6523f039bb52997c1c34a2e0dd49ed839c1",
"sha256:a3ceee84114d9f5711fa0f4db9c652af0e4636c89eabc9b7f03a3882569dd1ed",
"sha256:a72b82ac1910f2cf61a49139f4974f994984475f771b0faa730839607eeedddf",
"sha256:ab136ac51027e7c484c53138a0fab4a8a51e80d05162eb7b1585583bcfdbad27",
"sha256:c095b224300bcac61e6c445e27f9046981b1ac20d891b2f1714da89d34c637c8",
"sha256:c5cc52d16c06dc2521340d69adda78a8e1031705924e103c0eb8fc8af861d810",
"sha256:d612e9833a89e8177f8c1dc68d7b4ff98d3186cd331acd616b01bbdab67d3a7b",
"sha256:e828376a23c66c6fe90dcea24b4b72cd774f555a6ee94081670872918df87a19",
"sha256:e9767c7ab2eb552796440168d5c6e23a99ecaade08dda16266d43ad461730192",
"sha256:ebf8b800d42d217e4710d1582b0c8bff20cdcb4faad7c7213e52644034300924"
"sha256:15f0bf7cd80020f165635595e197603aedb37fddf4164ad5ae226afc43242f7b",
"sha256:1756dc72e192c670490e38c788c3a35f901adc74ee436e5131d5a3e85fdd7dc6",
"sha256:1d1eb490da54679d724b08ef3ee04530849023670c4ba7e400ed2cdf906720c4",
"sha256:228402625796821f08706f58cc42a3c51c9897d723550babaefe4feec2b6dacc",
"sha256:3928534fa00a2aabfcfdb439c08ba37fbe99ab0cf57776c8db8d2b73a51693ba",
"sha256:3d2a295b1086d450981f73d3561ac204a0cc9c8ded386a4a34327d918f3b1d0a",
"sha256:4fd8621a309db6ec23ef1369f43cdf7a9b0dc217d8ff9ca4095a6e932b379bda",
"sha256:54fe55a1694ffe608c8e4c5183e83cab7a91f3e5c84bd6f188868d6676c12aba",
"sha256:618887be4ad754228c0cbba7631f6574608b4430fe93974e6322324f1304fdac",
"sha256:69130efb6efa936de601cb135a8a4eec1caccd4ea2b784237145ff4075c2d3ae",
"sha256:6e7f78eeac82140bde7e60e975c6e6b1b678a4dd377782ab63319c1c78bf3aa1",
"sha256:6ee760cdb84e43574da6b3f2f1fc1251e8acf87253900d28a06451c5f5de39e9",
"sha256:97cb1b7cd2c46e87b0a26651eccd2bbb8c758035efd1635ebb81ac36aa76a88c",
"sha256:abfa774dbadacc849121ed92eae05189d226daab583388b499472e1bbb17ef69",
"sha256:b30c339eb58355f51f4f54dd61d785f1ff58c86bca1c3a5916977631d121867b"
],
"version": "==17.1.2"
"version": "==18.0.0"
},
"qtconsole": {
"hashes": [
@@ -640,6 +630,13 @@
],
"version": "==1.12.0"
},
"sqlalchemy": {
"hashes": [
"sha256:7dede29f121071da9873e7b8c98091874617858e790dc364ffaab4b09d81216c"
],
"index": "pypi",
"version": "==1.3.0b3"
},
"terminado": {
"hashes": [
"sha256:55abf9ade563b8f9be1f34e4233c7b7bde726059947a593322e8a553cc4c067a",
@@ -663,9 +660,9 @@
},
"tornado": {
"hashes": [
"sha256:00ebd485a52bd7eaa3f35bdf8ab43c109aaa2edc722849b6905c1ffd8c958e82"
"sha256:d3b719a0cb7094e2b1ca94b31f4b601639fa7ad01a548a1a2ccdd6cbdfd56671"
],
"version": "==6.0a1"
"version": "==6.0b1"
},
"traitlets": {
"hashes": [
@@ -676,29 +673,27 @@
},
"typed-ast": {
"hashes": [
"sha256:023625bfa9359e29bd6e24cac2a4503495b49761d48a5f1e38333fc4ac4d93fe",
"sha256:07591f7a5fdff50e2e566c4c1e9df545c75d21e27d98d18cb405727ed0ef329c",
"sha256:153e526b0f4ffbfada72d0bb5ffe8574ba02803d2f3a9c605c8cf99dfedd72a2",
"sha256:3ad2bdcd46a4a1518d7376e9f5016d17718a9ed3c6a3f09203d832f6c165de4a",
"sha256:3ea98c84df53ada97ee1c5159bb3bc784bd734231235a1ede14c8ae0775049f7",
"sha256:51a7141ccd076fa561af107cfb7a8b6d06a008d92451a1ac7e73149d18e9a827",
"sha256:52c93cd10e6c24e7ac97e8615da9f224fd75c61770515cb323316c30830ddb33",
"sha256:6344c84baeda3d7b33e157f0b292e4dd53d05ddb57a63f738178c01cac4635c9",
"sha256:64699ca1b3bd5070bdeb043e6d43bc1d0cebe08008548f4a6bee782b0ecce032",
"sha256:74903f2e56bbffe29282ef8a5487d207d10be0f8513b41aff787d954a4cf91c9",
"sha256:7891710dba83c29ee2bd51ecaa82f60f6bede40271af781110c08be134207bf2",
"sha256:91976c56224e26c256a0de0f76d2004ab885a29423737684b4f7ebdd2f46dde2",
"sha256:9bad678a576ecc71f25eba9f1e3fd8d01c28c12a2834850b458428b3e855f062",
"sha256:b4726339a4c180a8b6ad9d8b50d2b6dc247e1b79b38fe2290549c98e82e4fd15",
"sha256:ba36f6aa3f8933edf94ea35826daf92cbb3ec248b89eccdc053d4a815d285357",
"sha256:bbc96bde544fd19e9ef168e4dfa5c3dfe704bfa78128fa76f361d64d6b0f731a",
"sha256:c0c927f1e44469056f7f2dada266c79b577da378bbde3f6d2ada726d131e4824",
"sha256:c0f9a3708008aa59f560fa1bd22385e05b79b8e38e0721a15a8402b089243442",
"sha256:f0bf6f36ff9c5643004171f11d2fdc745aa3953c5aacf2536a0685db9ceb3fb1",
"sha256:f5be39a0146be663cbf210a4d95c3c58b2d7df7b043c9047c5448e358f0550a2",
"sha256:fcd198bf19d9213e5cbf2cde2b9ef20a9856e716f76f9476157f90ae6de06cc6"
"sha256:035a54ede6ce1380599b2ce57844c6554666522e376bd111eb940fbc7c3dad23",
"sha256:037c35f2741ce3a9ac0d55abfcd119133cbd821fffa4461397718287092d9d15",
"sha256:049feae7e9f180b64efacbdc36b3af64a00393a47be22fa9cb6794e68d4e73d3",
"sha256:19228f7940beafc1ba21a6e8e070e0b0bfd1457902a3a81709762b8b9039b88d",
"sha256:2ea681e91e3550a30c2265d2916f40a5f5d89b59469a20f3bad7d07adee0f7a6",
"sha256:3a6b0a78af298d82323660df5497bcea0f0a4a25a0b003afd0ce5af049bd1f60",
"sha256:5385da8f3b801014504df0852bf83524599df890387a3c2b17b7caa3d78b1773",
"sha256:606d8afa07eef77280c2bf84335e24390055b478392e1975f96286d99d0cb424",
"sha256:69245b5b23bbf7fb242c9f8f08493e9ecd7711f063259aefffaeb90595d62287",
"sha256:6f6d839ab09830d59b7fa8fb6917023d8cb5498ee1f1dbd82d37db78eb76bc99",
"sha256:730888475f5ac0e37c1de4bd05eeb799fdb742697867f524dc8a4cd74bcecc23",
"sha256:9819b5162ffc121b9e334923c685b0d0826154e41dfe70b2ede2ce29034c71d8",
"sha256:9e60ef9426efab601dd9aa120e4ff560f4461cf8442e9c0a2b92548d52800699",
"sha256:af5fbdde0690c7da68e841d7fc2632345d570768ea7406a9434446d7b33b0ee1",
"sha256:b64efdbdf3bbb1377562c179f167f3bf301251411eb5ac77dec6b7d32bcda463",
"sha256:bac5f444c118aeb456fac1b0b5d14c6a71ea2a42069b09c176f75e9bd4c186f6",
"sha256:bda9068aafb73859491e13b99b682bd299c1b5fd50644d697533775828a28ee0",
"sha256:d659517ca116e6750101a1326107d3479028c5191f0ecee3c7203c50f5b915b0",
"sha256:eddd3fb1f3e0f82e5915a899285a39ee34ce18fd25d89582bc89fc9fb16cd2c6"
],
"version": "==1.2.0"
"version": "==1.3.1"
},
"ujson": {
"hashes": [

View File

@@ -1,5 +1,5 @@
<p align="center">
<a href="https://fastapi.tiangolo.com"><img src="https://fastapi.tiangolo.com/img/logo-margin/logo-teal-vector.svg" alt='FastAPI'></a>
<a href="https://fastapi.tiangolo.com"><img src="https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png" alt="FastAPI"></a>
</p>
<p align="center">
<em>FastAPI framework, high performance, easy to learn, fast to code, ready for production</em>

View File

@@ -123,7 +123,7 @@ There are several Flask REST frameworks, but after investing the time and work i
### <a href="https://marshmallow.readthedocs.io/en/3.0/" target="_blank">Marshmallow</a>
One of the main features needed by API systems is data "<abbr title="also called marshalling, convertion">serialization</abbr>" which is taking data from the code (Python) and converting it into something that can be sent through the network. For example, converting an object containing data from a database into a JSON object. Converting `datetime` objects into strings, etc.
One of the main features needed by API systems is data "<abbr title="also called marshalling, conversion">serialization</abbr>" which is taking data from the code (Python) and converting it into something that can be sent through the network. For example, converting an object containing data from a database into a JSON object. Converting `datetime` objects into strings, etc.
Another big feature needed by APIs is data validation, making sure that the data is valid, given certain parameters. For example, that some field is an `int`, and not some random string. This is especially useful for incoming data.
@@ -365,7 +365,7 @@ It is the recommended server for Starlette and **FastAPI**.
!!! check "**FastAPI** recommends it as"
The main web server to run **FastAPI** applications.
You can combine it with Gunicorn, to have an asynchronous multiprocess server.
You can combine it with Gunicorn, to have an asynchronous multi-process server.
Check more details in the <a href="/deployment/" target="_blank">Deployment</a> section.

View File

@@ -329,7 +329,7 @@ So, about the egg and the chicken, how do you call the first `async` function?
If you are working with **FastAPI** you don't have to worry about that, because that "first" function will be your path operation function, and FastAPI will know how to do the right thing.
But if you want to use `async` / `await` without FastAPI, <a href="https://docs.python.org/3/library/asyncio-task.html#coroutine" target="_blank">check the official Python docs</a>
But if you want to use `async` / `await` without FastAPI, <a href="https://docs.python.org/3/library/asyncio-task.html#coroutine" target="_blank">check the official Python docs</a>.
### Other forms of asynchronous code
@@ -362,3 +362,39 @@ Let's see the same phrase from above:
That should make more sense now.
All that is what powers FastAPI (through Starlette) and what makes it have such an impressive performance.
## Very Technical Details
!!! warning
You can probably skip this.
These are very technical details of how **FastAPI** works underneath.
If you have quite some technical knowledge (co-routines, threads, blocking, etc) and are curious about how FastAPI handles `async def` vs normal `def`, go ahead.
### Path operation functions
When you declare a *path operation function* with normal `def` instead of `async def`, it is run in an external threadpool that is then awaited, instead of being called directly (as it would block the server).
### Dependencies
The same applies for dependencies. If a dependency is a standard `def` function instead of `async def`, it is run in the external threadpool.
### Sub-dependencies
You can have multiple dependencies and sub-dependencies requiring each other (as parameters of the function definitions), some of them might be created with `async def` and some with normal `def`. It would still work, and the ones created with normal `def` would be called on an external thread instead of being "awaited".
### Other utility functions
Any other utility function that you call directly can be created with normal `def` or `async def` and FastAPI won't affect the way you call it.
This is in contrast to the functions that FastAPI calls for you: *path operation functions* and dependencies.
If your utility function is a normal function with `def`, it will be called directly (as you write it in your code), not in a threadpool, if the function is created with `async def` then you should await for that function when you call it in your code.
---
Again, these are very technical details that would probably be useful if you came searching for them.
Otherwise, you should be good with the guidelines from the section above: <a href="#in-a-hurry">In a hurry?</a>.

123
docs/contributing.md Normal file
View File

@@ -0,0 +1,123 @@
First, you might want to see the basic ways to <a href="https://fastapi.tiangolo.com/help-fastapi/" target="_blank">help FastAPI and get help</a>.
## Developing
If you already cloned the repository and you know that you need to deep dive in the code, here are some guidelines to set up your environment.
### Pipenv
If you are using <a href="https://pipenv.readthedocs.io/en/latest/" target="_blank">Pipenv</a>, you can create a virtual environment and install the packages with:
```bash
pipenv install --dev
```
Then you can activate that virtual environment with:
```bash
pipenv shell
```
### No Pipenv
If you are not using Pipenv, you can create a virtual environment with your preferred tool, and install the packages listed in the file `Pipfile`.
### Flit
**FastAPI** uses <a href="https://flit.readthedocs.io/en/latest/index.html" target="_blank">Flit</a> to build, package and publish the project.
If you installed the development dependencies with one of the methods above, you already have the `flit` command.
To install your local version of FastAPI as a package in your local environment, run:
```bash
flit install --symlink
```
It will install your local FastAPI in your local environment.
#### Using your local FastAPI
If you create a Python file that imports and uses FastAPI, and run it with the Python from your local environment, it will use your local FastAPI source code.
And if you update that local FastAPI source code, as it is installed with `--symlink`, when you run that Python file again, it will use the fresh version of FastAPI you just edited.
That way, you don't have to "install" your local version to be able to test every change.
### Format
There is a script that you can run that will format and clean all your code:
```bash
bash scripts/lint.sh
```
It will also auto-sort all your imports.
For it to sort them correctly, you need to have FastAPI installed locally in your environment, with the command in the section above:
```bash
flit install --symlink
```
### Docs
The documentation uses <a href="https://www.mkdocs.org/" target="_blank">MkDocs</a>.
All the documentation is in Markdown format in the directory `./docs`.
Many of the tutorials have blocks of code.
In most of the cases, these blocks of code are actual complete applications that can be run as is.
In fact, those blocks of code are not written inside the Markdown, they are Python files in the `./docs/src/` directory.
And those Python files are included/injected in the documentation when generating the site.
#### Docs for tests
Most of the tests actually run against the example source files in the documentation.
This helps making sure that:
* The documentation is up to date.
* The documentation examples can be run as is.
* Most of the features are covered by the documentation, ensured by the coverage tests.
During local development, there is a script that builds the site and checks for any changes, live-reloading:
```bash
bash scripts/docs-live.sh
```
It will serve the documentation on `http://0.0.0.0:8008`.
That way, you can edit the documentation/source files and see the changes live.
#### Apps and docs at the same time
And if you run the examples with, e.g.:
```bash
uvicorn tutorial001:app --debug
```
as Uvicorn by default will use the port `8000`, the documentation on port `8008` won't clash.
### Tests
There is a script that you can run locally to test all the code and generate coverage reports in HTML:
```bash
bash scripts/test-cov-html.sh
```
This command generates a directory `./htmlcov/`, if you open the file `./htmlcov/index.html` in your browser, you can explore interactively the regions of code that are covered by the tests, and notice if there is any region missing.

View File

@@ -81,6 +81,18 @@ docker run -d --name mycontainer -p 80:80 myimage
Now you have an optimized FastAPI server in a Docker container. Auto-tuned for your current server (and number of CPU cores).
#### Bigger Applications
If you followed the section about creating <a href="" target="_blank">Bigger Applications with Multiple Files
</a>, your `Dockerfile` might instead look like:
```Dockerfile
FROM tiangolo/uvicorn-gunicorn-fastapi:python3.7
COPY ./app /app/app
```
### Check it
You should be able to check it in your Docker container's URL, for example: <a href="http://192.168.99.100/items/5?q=somequery" target="_blank">http://192.168.99.100/items/5?q=somequery</a> or <a href="http://127.0.0.1/items/5?q=somequery" target="_blank">http://127.0.0.1/items/5?q=somequery</a> (or equivalent, using your Docker host).
@@ -145,7 +157,7 @@ Now, from a developer's perspective, here are several things to have in mind whi
* It goes encrypted, but the encrypted contents are the same HTTP protocol.
It is a common practice to have one program/HTTP server runing in the server (the machine, host, etc) and managing all the HTTPS parts, sending the decrypted HTTP requests to the actual HTTP application running in the same server (the **FastAPI** application, in this case), take the HTTP response from the application, encrypt it using the appropriate certificate and sending it back to the client using HTTPS. This server is ofter called a <a href="https://en.wikipedia.org/wiki/TLS_termination_proxy" target="_blank">TLS Termination Proxy</a>.
It is a common practice to have one program/HTTP server running in the server (the machine, host, etc) and managing all the HTTPS parts, sending the decrypted HTTP requests to the actual HTTP application running in the same server (the **FastAPI** application, in this case), take the HTTP response from the application, encrypt it using the appropriate certificate and sending it back to the client using HTTPS. This server is ofter called a <a href="https://en.wikipedia.org/wiki/TLS_termination_proxy" target="_blank">TLS Termination Proxy</a>.
### Let's Encrypt

View File

@@ -71,7 +71,7 @@ my_second_user: User = User(**second_user_data)
### Editor support
All the framework was designed to be easy and intuitive to use, all the decisons where tested on multiple editors even before starting development, to ensure the best development experience.
All the framework was designed to be easy and intuitive to use, all the decisions where tested on multiple editors even before starting development, to ensure the best development experience.
In the last Python developer survey it was clear <a href="https://www.jetbrains.com/research/python-developers-survey-2017/#tools-and-features" target="_blank">that the most used feature is "autocompletion"</a>.
@@ -89,7 +89,7 @@ Here's how your editor might help you:
![editor support](img/pycharm-completion.png)
You will get completion in code you might even consider imposible before. As for example, the `price` key inside a JSON body (that could have been nested) that comes from a request.
You will get completion in code you might even consider impossible before. As for example, the `price` key inside a JSON body (that could have been nested) that comes from a request.
No more typing the wrong key names, coming back and forth between docs, or scrolling up and down to find if you finally used `username` or `user_name`.
@@ -201,4 +201,4 @@ With **FastAPI** you get all of **Pydantic**'s features (as FastAPI is based on
* You can have deeply **nested JSON** objects and have them all validated and annotated.
* **Extendible**:
* Pydantic allows custom data types to be defined or you can extend validation with methods on a model decorated with the validator decorator.
* 100% test coverage.
* 100% test coverage.

100
docs/help-fastapi.md Normal file
View File

@@ -0,0 +1,100 @@
Are you liking **FastAPI**?
Would you like to help FastAPI, other users, and the author?
Or would you like to get help with **FastAPI**?
There are very simple ways to help (several involve just one or two clicks).
And there are several ways to get help too.
## Star **FastAPI** in GitHub
You can "star" FastAPI in GitHub (clicking the star button at the top right): <a href="https://github.com/tiangolo/fastapi" target="_blank">https://github.com/tiangolo/fastapi</a>.
By adding a star, other users will be able to find it more easily and see that it has been already useful for others.
## Watch the GitHub repository for releases
You can "watch" FastAPI in GitHub (clicking the "watch" button at the top right): <a href="https://github.com/tiangolo/fastapi" target="_blank">https://github.com/tiangolo/fastapi</a>.
There you can select "Releases only".
Doing it, you will receive notifications (in your email) whenever there's a new release (a new version) of **FastAPI** with bug fixes and new features.
## Connect with the author
You can connect with me (Sebastián Ramírez / `tiangolo`), the author.
You can:
* <a href="https://github.com/tiangolo" target="_blank">Follow me on **GitHub**</a>.
* See other Open Source projects I have created that could help you.
* Follow me to see when I create a new Open Source project.
* <a href="https://twitter.com/tiangolo" target="_blank">Follow me on **Twitter**</a>.
* Tell me how you use FastAPI (I love to hear that).
* Ask questions.
* <a href="https://www.linkedin.com/in/tiangolo/" target="_blank">Connect with me on **Linkedin**</a>.
* Talk to me.
* Endorse me or recommend me :)
* <a href="https://medium.com/@tiangolo" target="_blank">Read what I write (or follow me) on **Medium**</a>.
* Read other ideas, articles and tools I have created.
* Follow me to see when I publish something new.
## Tweet about **FastAPI**
<a href="http://twitter.com/home/?status=I'm loving FastAPI because... https://github.com/tiangolo/fastapi cc @tiangolo" target="_blank">Tweet about **FastAPI**</a> and let me and others why you like it.
## Let me know how are you using **FastAPI**
I love to hear about how **FastAPI** is being used, what have you liked in it, in which project/company are you using it, etc.
You can let me know:
* <a href="http://twitter.com/home/?status=Hey @tiangolo, I'm using FastAPI at..." target="_blank">On **Twitter**</a>.
* <a href="https://www.linkedin.com/in/tiangolo/" target="_blank">On **Linkedin**</a>.
* <a href="https://medium.com/@tiangolo" target="_blank">On **Medium**</a>.
## 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>.
## Help others with issues in GitHub
You can see <a href="https://github.com/tiangolo/fastapi/issues" target="_blank">existing issues</a> and try and help others.
## Watch the GitHub repository
You can "watch" FastAPI in GitHub (clicking the "watch" button at the top right): <a href="https://github.com/tiangolo/fastapi" target="_blank">https://github.com/tiangolo/fastapi</a>.
If you select "Watching" instead of "Releases only", you will receive notifications when someone creates a new issue.
Then you can try and help them solving those issues.
## Create issues
You can <a href="https://github.com/tiangolo/fastapi/issues/new/choose" target="_blank">create a new issue</a> in the GitHub repository, for example to:
* Report a bug/issue.
* Suggest a new feature.
* Ask a question.
## Create a Pull Request
You can <a href="https://github.com/tiangolo/fastapi" target="_blank">create a Pull Request</a>, for example:
* To fix a typo you found on the documentation.
* To propose new documentation sections.
* To fix an existing issue/bug.
* To add a new feature.
---
Thanks!

View File

@@ -0,0 +1,83 @@
Some time ago, <a href="https://github.com/tiangolo/fastapi/issues/3#issuecomment-454956920" target="_blank">a **FastAPI** user asked</a>:
> Whats the history of this project? It seems to have come from nowhere to awesome in a few weeks [...]
Here's a little bit of that history.
## Alternatives
I have been creating APIs with complex requirements for several years (Machine Learning, distributed systems, asynchronous jobs, NoSQL databases, etc), leading several teams of developers.
As part of that, I needed to investigate, test and use many alternatives.
The history of **FastAPI** is in great part the history of its predecessors.
As said in the section <a href="https://fastapi.tiangolo.com/alternatives/" target="_blank">Alternatives</a>:
<blockquote markdown="1">
**FastAPI** wouldn't exist if not for the previous work of others.
There have been many tools created before that have helped inspire its creation.
I have been avoiding the creation of a new framework for several years. First I tried to solve all the features covered by **FastAPI** using many different frameworks, plug-ins, and tools.
But at some point, there was no other option than creating something that provided all these features, taking the best ideas from previous tools, and combining them in the best way possible, using language features that weren't even available before (Python 3.6+ type hints).
</blockquote>
## Investigation
By using all the previous alternatives I had the chance to learn from all of them, take ideas, and combine them in the best way I could find for myself and the teams of developers I have worked with.
For example, it was clear that ideally it should be based on standard Python type hints.
Also, the best approach was to use already existing standards.
So, before even starting to code **FastAPI**, I spent several months studying the specs for OpenAPI, JSON Schema, OAuth2, etc. Understanding their relationship, overlap, and differences.
## Design
Then I spent some time designing the developer "API" I wanted to have as a user (as a developer using FastAPI).
I tested several ideas in the most popular Python editors: PyCharm, VS Code, Jedi based editors.
By the last <a href="https://www.jetbrains.com/research/python-developers-survey-2018/#development-tools" target="_blank">Python Developer Survey</a>, that covers about 80% of the users.
It means that **FastAPI** was specifically tested with the editors used by 80% of the Python developers. And as most of the other editors tend to work similarly, all its benefits should work for virtually all editors.
That way I could find the best ways to reduce code duplication as much as possible, to have completion everywhere, type and error checks, etc.
All in a way that provided the best development experience for all the developers.
## Requirements
After testing several alternatives, I decided that I was going to use <a href="https://pydantic-docs.helpmanual.io/" target="_blank">**Pydantic**</a> for its advantages.
Then I contributed to it, to make it fully compliant with JSON Schema, to support different ways to define constraint declarations, and to improve editor support (type checks, autocompletion) based on the tests in several editors.
During the development, I also contributed to <a href="https://www.starlette.io/" target="_blank">**Starlette**</a>, the other key requirement.
## Development
By the time I started creating **FastAPI** itself, most of the pieces were already in place, the design was defined, the requirements and tools were ready, and the knowledge about the standards and specifications was clear and fresh.
## Future
By this point, it's already clear that **FastAPI** with its ideas is being useful for many people.
It is being chosen over previous alternatives for suiting many use cases better.
Many developers and teams already depend on **FastAPI** for their projects (including me and my team).
But still, there are many improvements and features to come.
**FastAPI** has a great future ahead.
And <a href="https://fastapi.tiangolo.com/help-fastapi/" target="_blank">your help</a> is greatly appreciated.

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

BIN
docs/img/icon-white-bg.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@@ -1,5 +1,5 @@
<p align="center">
<a href="https://fastapi.tiangolo.com"><img src="https://fastapi.tiangolo.com/img/logo-margin/logo-teal-vector.svg" alt='FastAPI'></a>
<a href="https://fastapi.tiangolo.com"><img src="https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png" alt="FastAPI"></a>
</p>
<p align="center">
<em>FastAPI framework, high performance, easy to learn, fast to code, ready for production</em>

View File

@@ -1,5 +1,56 @@
There is a project generator that you can use to get started, with a lot of the initial set up, security, database and first API endpoints already done for you.
## Full-Stack-FastAPI-PostgreSQL
GitHub: <a href="https://github.com/tiangolo/full-stack-fastapi-postgresql" target="_blank">https://github.com/tiangolo/full-stack-fastapi-postgresql</a>
### Features
* Full **Docker** integration (Docker based).
* Docker Swarm Mode deployment.
* **Docker Compose** integration and optimization for local development
* **Production ready** Python web server using Uvicorn and Gunicorn.
* Python **[FastAPI](https://github.com/tiangolo/fastapi)** backend:
* **Fast**: Very high performance, on par with **NodeJS** and **Go** (thanks to Starlette and Pydantic).
* **Intuitive**: Great editor support. <abbr title="also known as auto-complete, autocompletion, IntelliSense">Completion</abbr> everywhere. Less time debugging.
* **Easy**: Designed to be easy to use and learn. Less time reading docs.
* **Short**: Minimize code duplication. Multiple features from each parameter declaration.
* **Robust**: Get production-ready code. With automatic interactive documentation.
* **Standards-based**: Based on (and fully compatible with) the open standards for APIs: <a href="https://github.com/OAI/OpenAPI-Specification" target="_blank">OpenAPI</a> and <a href="http://json-schema.org/" target="_blank">JSON Schema</a>.
* [**Many other features**](https://github.com/tiangolo/fastapi) including automatic validation, serialization, interactive documentation, authentication with OAuth2 JWT tokens, etc.
* **Secure password** hashing by default.
* **JWT token** authentication.
* **SQLAlchemy** models (independent of Flask extensions, so they can be used with Celery workers directly).
* Basic starting models for users (modify and remove as you need).
* **Alembic** migrations.
* **CORS** (Cross Origin Resource Sharing).
* **Celery** worker that can import and use models and code from the rest of the backend selectively (you don't have to install the complete app in each worker).
* REST backend tests based on **Pytest**, integrated with Docker, so you can test the full API interaction, independent on the database. As it runs in Docker, it can build a new data store from scratch each time (so you can use ElasticSearch, MongoDB, CouchDB, or whatever you want, and just test that the API works).
* Easy Python integration with **Jupyter Kernels** for remote or in-Docker development with extensions like Atom Hydrogen or Visual Studio Code Jupyter.
* **Vue** frontend:
* Generated with Vue CLI.
* **JWT Authentication** handling.
* Login view.
* After login, main dashboard view.
* Main dashboard with user creation and edition.
* Self user edition.
* **Vuex**.
* **Vue-router**.
* **Vuetify** for beautiful material design components.
* **TypeScript**.
* Docker server based on **Nginx** (configured to play nicely with Vue-router).
* Docker multi-stage building, so you don't need to save or commit compiled code.
* Frontend tests ran at build time (can be disabled too).
* Made as modular as possible, so it works out of the box, but you can re-generate with Vue CLI or create it as you need, and re-use what you want.
* **PGAdmin** for PostgreSQL database, you can modify it to use PHPMyAdmin and MySQL easily.
* **Flower** for Celery jobs monitoring.
* Load balancing between frontend and backend with **Traefik**, so you can have both under the same domain, separated by path, but served by different containers.
* Traefik integration, including Let's Encrypt **HTTPS** certificates automatic generation.
* GitLab **CI** (continuous integration), including frontend and backend testing.
## Full-Stack-FastAPI-Couchbase
GitHub: <a href="https://github.com/tiangolo/full-stack-fastapi-couchbase" target="_blank">https://github.com/tiangolo/full-stack-fastapi-couchbase</a>
@@ -10,7 +61,17 @@ GitHub: <a href="https://github.com/tiangolo/full-stack-fastapi-couchbase" targe
* Docker Swarm Mode deployment.
* **Docker Compose** integration and optimization for local development.
* **Production ready** Python web server using Uvicorn and Gunicorn.
* Python **FastAPI** backend with all its features.
* Python **[FastAPI](https://github.com/tiangolo/fastapi)** backend:
* **Fast**: Very high performance, on par with **NodeJS** and **Go** (thanks to Starlette and Pydantic).
* **Intuitive**: Great editor support. <abbr title="also known as auto-complete, autocompletion, IntelliSense">Completion</abbr> everywhere. Less time debugging.
* **Easy**: Designed to be easy to use and learn. Less time reading docs.
* **Short**: Minimize code duplication. Multiple features from each parameter declaration.
* **Robust**: Get production-ready code. With automatic interactive documentation.
* **Standards-based**: Based on (and fully compatible with) the open standards for APIs: <a href="https://github.com/OAI/OpenAPI-Specification" target="_blank">OpenAPI</a> and <a href="http://json-schema.org/" target="_blank">JSON Schema</a>.
* [**Many other features**](https://github.com/tiangolo/fastapi) including automatic validation, serialization, interactive documentation, authentication with OAuth2 JWT tokens, etc.
* **Secure password** hashing by default.
* **JWT token** authentication.
* **CORS** (Cross Origin Resource Sharing).
* **Celery** worker that can import and use code from the rest of the backend selectively (you don't have to install the complete app in each worker).
* **NoSQL Couchbase** database that supports direct synchronization via Couchbase Sync Gateway for offline-first applications.
* **Full Text Search** integrated, using Couchbase.
@@ -36,7 +97,7 @@ GitHub: <a href="https://github.com/tiangolo/full-stack-fastapi-couchbase" targe
* Docker multi-stage building, so you don't need to save or commit compiled code.
* Frontend tests ran at build time (can be disabled too).
* Made as modular as possible, so it works out of the box, but you can re-generate with Vue CLI or create it as you need, and re-use what you want.
* Flower for Celery jobs monitoring.
* **Flower** for Celery jobs monitoring.
* Load balancing between frontend and backend with **Traefik**, so you can have both under the same domain, separated by path, but served by different containers.
* Traefik integration, including Let's Encrypt **HTTPS** certificates automatic generation.
* GitLab **CI** (continuous integration), including frontend and backend testing.

View File

@@ -49,7 +49,7 @@ But then you have to call "that method that converts the first letter to upper c
Was it `upper`? Was it `uppercase`? `first_uppercase`? `capitalize`?
Then, you try with the old programer's friend, editor autocompletion.
Then, you try with the old programmer's friend, editor autocompletion.
You type the first parameter of the function, `first_name`, then a dot (`.`) and then hit `Ctrl+Space` to trigger the completion.

View File

@@ -1,13 +1,89 @@
## Next
## 0.7.0
* Add support for `UploadFile` in `File` parameter annotations.
* This includes a file-like interface.
* Here's the updated documentation for declaring <a href="https://fastapi.tiangolo.com/tutorial/request-files/#file-parameters-with-uploadfile" target="_blank"> `File` parameters with `UploadFile`</a>.
* And here's the updated documentation for using <a href="https://fastapi.tiangolo.com/tutorial/request-forms-and-files/" target="_blank">`Form` parameters mixed with `File` parameters, supporting `bytes` and `UploadFile`</a> at the same time.
* PR <a href="https://github.com/tiangolo/fastapi/pull/63" target="_blank">#63</a>.
## 0.6.4
* Add <a href="https://fastapi.tiangolo.com/async/#very-technical-details" target="_blank">technical details about `async def` handling to docs</a>. PR <a href="https://github.com/tiangolo/fastapi/pull/61" target="_blank">#61</a>.
* Add docs for <a href="https://fastapi.tiangolo.com/tutorial/debugging/" target="_blank">Debugging FastAPI applications in editors</a>.
* Clarify <a href="https://fastapi.tiangolo.com/deployment/#bigger-applications" target="_blank">Bigger Applications deployed with Docker</a>.
* Fix typos in docs.
* Add section about <a href="https://fastapi.tiangolo.com/history-design-future/" target="_blank">History, Design and Future</a>.
* Add docs for using <a href="https://fastapi.tiangolo.com/tutorial/websockets/" target="_blank">WebSockets with **FastAPI**</a>. PR <a href="https://github.com/tiangolo/fastapi/pull/62" target="_blank">#62</a>.
## 0.6.3
* Add Favicons to docs. PR <a href="https://github.com/tiangolo/fastapi/pull/53" target="_blank">#53</a>.
## 0.6.2
* Introduce new project generator based on FastAPI and PostgreSQL: <a href="https://github.com/tiangolo/full-stack-fastapi-postgresql" target="_blank">https://github.com/tiangolo/full-stack-fastapi-postgresql</a>. PR <a href="https://github.com/tiangolo/fastapi/pull/52" target="_blank">#52</a>.
* Update <a href="https://fastapi.tiangolo.com/tutorial/sql-databases/" target="_blank">SQL tutorial with SQLAlchemy, using `Depends` to improve editor support and reduce code repetition</a>. PR <a href="https://github.com/tiangolo/fastapi/pull/52" target="_blank">#52</a>.
* Improve middleware naming in tutorial for SQL with SQLAlchemy <a href="https://fastapi.tiangolo.com/tutorial/sql-databases/" target="_blank">https://fastapi.tiangolo.com/tutorial/sql-databases/</a>.
## 0.6.1
* Add docs for GraphQL: <a href="https://fastapi.tiangolo.com/tutorial/graphql/" target="_blank">https://fastapi.tiangolo.com/tutorial/graphql/</a>. PR <a href="https://github.com/tiangolo/fastapi/pull/48" target="_blank">#48</a>.
## 0.6.0
* Update SQL with SQLAlchemy tutorial at <a href="https://fastapi.tiangolo.com/tutorial/sql-databases/" target="_blank">https://fastapi.tiangolo.com/tutorial/sql-databases/</a> using the new official `request.state`. PR <a href="https://github.com/tiangolo/fastapi/pull/45" target="_blank">#45</a>.
* Upgrade Starlette to version `0.11.1` and add required compatibility changes. PR <a href="https://github.com/tiangolo/fastapi/pull/44" target="_blank">#44</a>.
## 0.5.1
* Add section about <a href="https://fastapi.tiangolo.com/help-fastapi/" target="_blank">helping and getting help with **FastAPI**</a>.
* Add note about <a href="https://fastapi.tiangolo.com/tutorial/path-params/#order-matters" target="_blank">path operations order in docs</a>.
* Update <a href="https://fastapi.tiangolo.com/tutorial/handling-errors/" target="_blank">section about error handling</a> with more information and make relation with Starlette error handling utilities more explicit. PR <a href="https://github.com/tiangolo/fastapi/pull/41" target="_blank">#41</a>.
* Add <a href="" target="_blank">Development - Contributing section to the docs</a>. PR <a href="https://github.com/tiangolo/fastapi/pull/42" target="_blank">#42</a>.
## 0.5.0
* Add new `HTTPException` with support for custom headers. With new documentation for handling errors at: <a href="https://fastapi.tiangolo.com/tutorial/handling-errors/" target="_blank">https://fastapi.tiangolo.com/tutorial/handling-errors/</a>. PR <a href="https://github.com/tiangolo/fastapi/pull/35" target="_blank">#35</a>.
* Add <a href="https://fastapi.tiangolo.com/tutorial/using-request-directly/" target="_blank">documentation to use Starlette `Request` object</a> directly. Check <a href="https://github.com/tiangolo/fastapi/pull/25" target="_blank">#25</a> by <a href="https://github.com/euri10" target="_blank">@euri10</a>.
* Add issue templates to simplify reporting bugs, getting help, etc: <a href="https://github.com/tiangolo/fastapi/pull/34" target="_blank">#34</a>.
* Update example for the SQLAlchemy tutorial at <a href="https://fastapi.tiangolo.com/tutorial/sql-databases/" target="_blank">https://fastapi.tiangolo.com/tutorial/sql-databases/</a> using middleware and database session attached to request.
## 0.4.0
* Add `openapi_prefix`, support for reverse proxy and mounting sub-applications. See the docs at <a href="https://fastapi.tiangolo.com/tutorial/sub-applications-proxy/" target="_blank">https://fastapi.tiangolo.com/tutorial/sub-applications-proxy/</a>: <a href="https://github.com/tiangolo/fastapi/pull/26" target="_blank">#26</a> by <a href="https://github.com/kabirkhan" target="_blank">@kabirkhan</a>.
* Update <a href="https://fastapi.tiangolo.com/tutorial/sql-databases/" target="_blank">docs/tutorial for SQLAlchemy</a> including note about *DB Browser for SQLite*.
## 0.3.0
* Fix/add SQLAlchemy support, including ORM, and update <a href="https://fastapi.tiangolo.com/tutorial/sql-databases/" target="_blank">docs for SQLAlchemy</a>: <a href="https://github.com/tiangolo/fastapi/pull/30" target="_blank">#30</a>.
## 0.2.1
* Fix `jsonable_encoder` for Pydantic models with `Config` but without `json_encoders`: <a href="https://github.com/tiangolo/fastapi/pull/29" target="_blank">#29</a>.
## 0.2.0
* Fix typos in Security section: <a href="https://github.com/tiangolo/fastapi/pull/24" target="_blank">#24</a> by <a href="https://github.com/kkinder" target="_blank">@kkinder</a>
* Fix typos in Security section: <a href="https://github.com/tiangolo/fastapi/pull/24" target="_blank">#24</a> by <a href="https://github.com/kkinder" target="_blank">@kkinder</a>.
* Add support for Pydantic custom JSON encoders: <a href="https://github.com/tiangolo/fastapi/pull/21" target="_blank">#21</a> by <a href="https://github.com/euri10" target="_blank">@euri10</a>
* Add support for Pydantic custom JSON encoders: <a href="https://github.com/tiangolo/fastapi/pull/21" target="_blank">#21</a> by <a href="https://github.com/euri10" target="_blank">@euri10</a>.
## 0.1.19
* Upgrade Starlette version to the current latest `0.10.1`: <a href="https://github.com/tiangolo/fastapi/pull/17" target="_blank">#17</a> by <a href="https://github.com/euri10" target="_blank">@euri10</a>
* Upgrade Starlette version to the current latest `0.10.1`: <a href="https://github.com/tiangolo/fastapi/pull/17" target="_blank">#17</a> by <a href="https://github.com/euri10" target="_blank">@euri10</a>.

View File

@@ -0,0 +1,15 @@
import uvicorn
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def root():
a = "a"
b = "b" + a
return {"hello world": b}
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)

View File

@@ -0,0 +1,14 @@
import graphene
from fastapi import FastAPI
from starlette.graphql import GraphQLApp
class Query(graphene.ObjectType):
hello = graphene.String(name=graphene.String(default_value="stranger"))
def resolve_hello(self, info, name):
return "Hello " + name
app = FastAPI()
app.add_route("/", GraphQLApp(schema=graphene.Schema(query=Query)))

View File

@@ -0,0 +1,12 @@
from fastapi import FastAPI, HTTPException
app = FastAPI()
items = {"foo": "The Foo Wrestlers"}
@app.get("/items/{item_id}")
async def create_item(item_id: str):
if item_id not in items:
raise HTTPException(status_code=404, detail="Item not found")
return {"item": items[item_id]}

View File

@@ -0,0 +1,16 @@
from fastapi import FastAPI, HTTPException
app = FastAPI()
items = {"foo": "The Foo Wrestlers"}
@app.get("/items-header/{item_id}")
async def create_item_header(item_id: str):
if item_id not in items:
raise HTTPException(
status_code=404,
detail="Item not found",
headers={"X-Error": "There goes my error"},
)
return {"item": items[item_id]}

View File

@@ -0,0 +1,15 @@
from fastapi import FastAPI
from starlette.exceptions import HTTPException
from starlette.responses import PlainTextResponse
app = FastAPI()
@app.exception_handler(HTTPException)
async def http_exception(request, exc):
return PlainTextResponse(str(exc.detail), status_code=exc.status_code)
@app.get("/")
async def root():
return {"message": "Hello World"}

View File

@@ -1,10 +1,13 @@
from uuid import UUID
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/{item_id}")
async def read_item(item_id: UUID):
return {"item_id": item_id}
@app.get("/users/me")
async def read_user_me():
return {"user_id": "the current user"}
@app.get("/users/{user_id}")
async def read_user(user_id: str):
return {"user_id": user_id}

View File

@@ -1,8 +1,13 @@
from fastapi import FastAPI, File
from fastapi import FastAPI, File, UploadFile
app = FastAPI()
@app.post("/files/")
async def create_file(*, file: bytes = File(...)):
async def create_file(file: bytes = File(...)):
return {"file_size": len(file)}
@app.post("/uploadfile/")
async def create_upload_file(file: UploadFile = File(...)):
return {"filename": file.filename}

View File

@@ -1,8 +1,14 @@
from fastapi import FastAPI, File, Form
from fastapi import FastAPI, File, Form, UploadFile
app = FastAPI()
@app.post("/files/")
async def create_file(*, file: bytes = File(...), token: str = Form(...)):
return {"file_size": len(file), "token": token}
async def create_file(
file: bytes = File(...), fileb: UploadFile = File(...), token: str = Form(...)
):
return {
"file_size": len(file),
"token": token,
"fileb_content_type": fileb.content_type,
}

View File

@@ -1,7 +1,6 @@
from fastapi import Depends, FastAPI, Security
from fastapi import Depends, FastAPI, HTTPException, Security
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from pydantic import BaseModel
from starlette.exceptions import HTTPException
fake_users_db = {
"johndoe": {

View File

@@ -1,12 +1,11 @@
from datetime import datetime, timedelta
import jwt
from fastapi import Depends, FastAPI, Security
from fastapi import Depends, FastAPI, HTTPException, Security
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jwt import PyJWTError
from passlib.context import CryptContext
from pydantic import BaseModel
from starlette.exceptions import HTTPException
from starlette.status import HTTP_403_FORBIDDEN
# to get a string like this run:

View File

@@ -1,16 +1,17 @@
from fastapi import FastAPI
from fastapi import Depends, FastAPI
from sqlalchemy import Boolean, Column, Integer, String, create_engine
from sqlalchemy.ext.declarative import declarative_base, declared_attr
from sqlalchemy.orm import scoped_session, sessionmaker
from sqlalchemy.orm import Session, sessionmaker
from starlette.requests import Request
# SQLAlchemy specific code, as with any other app
SQLALCHEMY_DATABASE_URI = "postgresql://user:password@postgresserver/db"
SQLALCHEMY_DATABASE_URI = "sqlite:///./test.db"
# SQLALCHEMY_DATABASE_URI = "postgresql://user:password@postgresserver/db"
engine = create_engine(SQLALCHEMY_DATABASE_URI, convert_unicode=True)
db_session = scoped_session(
sessionmaker(autocommit=False, autoflush=False, bind=engine)
engine = create_engine(
SQLALCHEMY_DATABASE_URI, connect_args={"check_same_thread": False}
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
class CustomBase:
@@ -30,15 +31,42 @@ class User(Base):
is_active = Column(Boolean(), default=True)
def get_user(username, db_session):
return db_session.query(User).filter(User.id == username).first()
Base.metadata.create_all(bind=engine)
db_session = SessionLocal()
first_user = db_session.query(User).first()
if not first_user:
u = User(email="johndoe@example.com", hashed_password="notreallyhashed")
db_session.add(u)
db_session.commit()
db_session.close()
# Utility
def get_user(db_session: Session, user_id: int):
return db_session.query(User).filter(User.id == user_id).first()
# Dependency
def get_db(request: Request):
return request.state.db
# FastAPI specific code
app = FastAPI()
@app.get("/users/{username}")
def read_user(username: str):
user = get_user(username, db_session)
@app.get("/users/{user_id}")
def read_user(user_id: int, db: Session = Depends(get_db)):
user = get_user(db, user_id=user_id)
return user
@app.middleware("http")
async def db_session_middleware(request: Request, call_next):
request.state.db = SessionLocal()
response = await call_next(request)
request.state.db.close()
return response

View File

@@ -0,0 +1,19 @@
from fastapi import FastAPI
app = FastAPI()
@app.get("/app")
def read_main():
return {"message": "Hello World from main app"}
subapi = FastAPI(openapi_prefix="/subapi")
@subapi.get("/sub")
def read_sub():
return {"message": "Hello World from sub API"}
app.mount("/subapi", subapi)

View File

@@ -0,0 +1,10 @@
from fastapi import FastAPI
from starlette.requests import Request
app = FastAPI()
@app.get("/items/{item_id}")
def read_root(item_id: str, request: Request):
client_host = request.client.host
return {"client_host": client_host, "item_id": item_id}

View File

@@ -0,0 +1,53 @@
from fastapi import FastAPI
from starlette.responses import HTMLResponse
from starlette.websockets import WebSocket
app = FastAPI()
html = """
<!DOCTYPE html>
<html>
<head>
<title>Chat</title>
</head>
<body>
<h1>WebSocket Chat</h1>
<form action="" onsubmit="sendMessage(event)">
<input type="text" id="messageText" autocomplete="off"/>
<button>Send</button>
</form>
<ul id='messages'>
</ul>
<script>
var ws = new WebSocket("ws://localhost:8000/ws");
ws.onmessage = function(event) {
var messages = document.getElementById('messages')
var message = document.createElement('li')
var content = document.createTextNode(event.data)
message.appendChild(content)
messages.appendChild(message)
};
function sendMessage(event) {
var input = document.getElementById("messageText")
ws.send(input.value)
input.value = ''
event.preventDefault()
}
</script>
</body>
</html>
"""
@app.get("/")
async def get():
return HTMLResponse(html)
@app.websocket_route("/ws")
async def websocket_endpoint(websocket: WebSocket):
await websocket.accept()
while True:
data = await websocket.receive_text()
await websocket.send_text(f"Message text was: {data}")
await websocket.close()

View File

@@ -0,0 +1,87 @@
You can connect the debugger in your editor, for example with Visual Studio Code or PyCharm.
## Call `uvicorn`
In your FastAPI application, import and run `uvicorn` directly:
```Python hl_lines="1 15"
{!./src/debugging/tutorial001.py!}
```
### About `__name__ == "__main__"`
The main purpose of the `__name__ == "__main__"` is to have some code that is executed when your file is called with:
```bash
python myapp.py
```
but is not called when another file imports it, like in:
```Python
from myapp import app
```
#### More details
Let's say your file is named `myapp.py`.
If you run it with:
```bash
python myapp.py
```
then the internal variable `__name__` in your file, created automatically by Python, will have as value the string `"__main__"`.
So, the section:
```Python
uvicorn.run(app, host="0.0.0.0", port=8000)
```
will run.
---
This won't happen if you import that module (file).
So, if you have another file `importer.py` with:
```Python
from myapp import app
# Some more code
```
in that case, the automatic variable inside of `myapp.py` will not have the variable `__name__` with a value of `"__main__"`.
So, the line:
```Python
uvicorn.run(app, host="0.0.0.0", port=8000)
```
will not be executed.
!!! info
For more information, check <a href="https://docs.python.org/3/library/__main__.html" target="_blank">the official Python docs</a>.
## Run your code with your debugger
Because you are running the Uvicorn server directly from your code, you can call your Python program (your FastAPI application) directly form the debugger.
---
For example, in Visual Studio Code, you can:
* Go to the "Debug" panel.
* "Add configuration...".
* Select "Python"
* Run the debugger with the option "`Python: Current File (Integrated Terminal)`".
It will then start the server with your **FastAPI** code, stop at your breakpoints, etc.
Here's how it might look:
<img src="/img/tutorial/debugging/image01.png">

View File

@@ -109,7 +109,7 @@ UserInDB(**user_in.dict())
So, we get a Pydantic model from the data in another Pydantic model.
#### Unrapping a `dict` and extra keywords
#### Unwrapping a `dict` and extra keywords
And then adding the extra keyword argument `hashed_password=hashed_password`, like in:

View File

@@ -1 +0,0 @@
Coming soon...

View File

@@ -69,7 +69,7 @@ A "schema" is a definition or description of something. Not the code that implem
In this case, OpenAPI is a specification that dictates how to define a schema of your API.
This OpenAPI schema would include your API paths, the posible parameters they take, etc.
This OpenAPI schema would include your API paths, the possible parameters they take, etc.
#### Data "schema"

44
docs/tutorial/graphql.md Normal file
View File

@@ -0,0 +1,44 @@
**FastAPI** has optional support for GraphQL (provided by Starlette directly), using the `graphene` library.
You can combine normal FastAPI path operations with GraphQL on the same application.
## Import and use `graphene`
GraphQL is implemented with Graphene, you can check <a href="https://docs.graphene-python.org/en/latest/quickstart/" target="_blank">Graphene's docs</a> for more details.
Import `graphene` and define your GraphQL data:
```Python hl_lines="1 6 7 8 9 10"
{!./src/graphql/tutorial001.py!}
```
## Add Starlette's `GraphQLApp`
Then import and add Starlette's `GraphQLApp`:
```Python hl_lines="3 14"
{!./src/graphql/tutorial001.py!}
```
!!! info
Here we are using `.add_route`, that is the way to add a route in Starlette (inherited by FastAPI) without declaring the specific operation (as would be with `.get()`, `.post()`, etc).
## Check it
Run it with Uvicorn and open your browser at <a href="http://127.0.0.1:8000" target="_blank">http://127.0.0.1:8000</a>.
You will see GraphiQL web user interface:
<img src="/img/tutorial/graphql/image01.png">
## More details
For more details, including:
* Accessing request information
* Adding background tasks
* Using normal or async functions
check the official <a href="https://www.starlette.io/graphql/" target="_blank">Starlette GraphQL docs</a>.

View File

@@ -0,0 +1,99 @@
There are many situations in where you need to notify an error to the client that is using your API.
This client could be a browser with a frontend, the code from someone else, an IoT device, etc.
You could need to tell that client that:
* He doesn't have enough privileges for that operation.
* He doesn't have access to that resource.
* The item he was trying to access doesn't exist.
* etc.
In these cases, you would normally return an **HTTP status code** in the range of **400** (from 400 to 499).
This is similar to the 200 HTTP status codes (from 200 to 299). Those "200" status codes mean that somehow there was a "success" in the request.
The status codes in the 400 range mean that there was an error from the client.
Remember all those **"404 Not Found"** errors (and jokes)?
## Use `HTTPException`
To return HTTP responses with errors to the client you use `HTTPException`.
### Import `HTTPException`
```Python hl_lines="1"
{!./src/handling_errors/tutorial001.py!}
```
### Raise an `HTTPException` in your code
`HTTPException` is a normal Python exception with additional data relevant for APIs.
Because it's a Python exception, you don't `return` it, you `raise` it.
This also means that if you are inside a utility function that you are calling inside of your path operation function, and you raise the `HTTPException` from inside of that utility function, it won't run the rest of the code in the path operation function, it will terminate that request right away and send the HTTP error from the `HTTPException` to the client.
The benefit of raising an exception over `return`ing a value will be more evident in the section about Dependencies and Security.
In this example, when the client request an item by an ID that doesn't exist, raise an exception with a status code of `404`:
```Python hl_lines="11"
{!./src/handling_errors/tutorial001.py!}
```
### The resulting response
If the client requests `http://example.com/items/foo` (an `item_id` `"foo"`), he will receive an HTTP status code of 200, and a JSON response of:
```JSON
{
"item": "The Foo Wrestlers"
}
```
But if the client requests `http://example.com/items/bar` (a non-existent `item_id` `"bar"`), he will receive an HTTP status code of 404 (the "not found" error), and a JSON response of:
```JSON
{
"detail": "Item not found"
}
```
!!! tip
When raising an `HTTPException`, you can pass any value that can be converted to JSON as the parameter `detail`, not only `str`.
You could pass a `dict`, a `list`, etc.
They are handled automatically by **FastAPI** and converted to JSON.
### Adding custom headers
There are some situations in where it's useful to be able to add custom headers to the HTTP error. For example, for some types of security.
You probably won't need to use it directly in your code.
But in case you needed it for an advanced scenario, you can add custom headers:
```Python hl_lines="14"
{!./src/handling_errors/tutorial002.py!}
```
### Installing custom handlers
If you need to add other custom exception handlers, or override the default one (that sends the errors as JSON), you can use <a href="https://www.starlette.io/exceptions/" target="_blank">the same exception utilities from Starlette</a>.
For example, you could override the default exception handler with:
```Python hl_lines="2 3 8 9 10"
{!./src/handling_errors/tutorial003.py!}
```
...this would make it return "plain text" responses with the errors, instead of JSON responses.
!!! info
Note that in this example we set the exception handler with Starlette's `HTTPException` instead of FastAPI's `HTTPException`.
This would ensure that if you use a plug-in or any other third-party tool that raises Starlette's `HTTPException` directly, it will be caught by your exception handler.

View File

@@ -1,6 +1,6 @@
This tutorial shows you how to use **FastAPI** with all its features, step by step.
Eeach section gradually builds on the previous ones, but it's structured to separate topics, so that you can go directly to any specific one to solve your specific API needs.
Each section gradually builds on the previous ones, but it's structured to separate topics, so that you can go directly to any specific one to solve your specific API needs.
It is also built to work as a future reference.

View File

@@ -98,6 +98,24 @@ You can use the same type declarations with `str`, `float`, `bool` and many othe
These are explored in the next chapters of the tutorial.
## Order matters
When creating *path operations*, you can find situations where you have a fixed path.
Like `/users/me`, let's say that it's to get data about the current user.
And then you can also have a path `/users/{user_id}` to get data about a specific user by some user ID.
Because path operations are evaluated in order, you need to make sure that the path for `/users/me` is declared before the one for `/users/{user_id}`:
```Python hl_lines="6 11"
{!./src/path_params/tutorial003.py!}
```
Otherwise, the path for `/users/{user_id}` would match also for `/users/me`, "thinking" that it's receiving a parameter `user_id` with a value of `"me"`.
## Recap
With **FastAPI**, by using short, intuitive and standard Python type declarations, you get:

View File

@@ -1,4 +1,4 @@
**FastAPI** allows you to declare additonal information and validation for your parameters.
**FastAPI** allows you to declare additional information and validation for your parameters.
Let's take this application as example:

View File

@@ -2,7 +2,7 @@ You can define files to be uploaded by the client using `File`.
## Import `File`
Import `File` from `fastapi`:
Import `File` and `UploadFile` from `fastapi`:
```Python hl_lines="1"
{!./src/request_files/tutorial001.py!}
@@ -16,14 +16,78 @@ Create file parameters the same way you would for `Body` or `Form`:
{!./src/request_files/tutorial001.py!}
```
The files will be uploaded as form data and you will receive the contents as `bytes`.
!!! info
`File` is a class that inherits directly from `Form`.
!!! info
To declare File bodies, you need to use `File`, because otherwise the parameters would be interpreted as query parameters or body (JSON) parameters.
The files will be uploaded as "form data".
If you declare the type of your *path operation function* parameter as `bytes`, **FastAPI** will read the file for you and you will receive the contents as `bytes`.
Have in mind that this means that the whole contents will be stored in memory. This will work well for small files.
But there are several cases in where you might benefit from using `UploadFile`.
## `File` parameters with `UploadFile`
Define a `File` parameter with a type of `UploadFile`:
```Python hl_lines="12"
{!./src/request_files/tutorial001.py!}
```
Using `UploadFile` has several advantages over `bytes`:
* It uses a "spooled" file:
* A file stored in memory up to a maximum size limit, and after passing this limit it will be stored in disk.
* This means that it will work well for large files like images, videos, large binaries, etc. All without consuming all the memory.
* You can get metadata from the uploaded file.
* It has a <a href="https://docs.python.org/3/glossary.html#term-file-like-object" target="_blank">file-like</a> `async` interface.
* It exposes an actual Python <a href="https://docs.python.org/3/library/tempfile.html#tempfile.SpooledTemporaryFile" target="_blank">`SpooledTemporaryFile`</a> object that you can pass directly to other libraries that expect a file-like object.
### `UploadFile`
`UploadFile` has the following attributes:
* `filename`: A `str` with the original file name that was uploaded (e.g. `myimage.jpg`).
* `content_type`: A `str` with the content type (MIME type / media type) (e.g. `image/jpeg`).
* `file`: A <a href="https://docs.python.org/3/library/tempfile.html#tempfile.SpooledTemporaryFile" target="_blank">`SpooledTemporaryFile`</a> (a <a href="https://docs.python.org/3/glossary.html#term-file-like-object" target="_blank">file-like</a> object). This is the actual Python file that you can pass directly to other functions or libraries that expect a "file-like" object.
`UploadFile` has the following `async` methods. They all call the corresponding file methods underneath (using the internal `SpooledTemporaryFile`).
* `write(data)`: Writes `data` (`str` or `bytes`) to the file.
* `read(size)`: Reads `size` (`int`) bytes/characters of the file.
* `seek(offset)`: Goes to the byte position `offset` (`int`) in the file.
* E.g., `myfile.seek(0)` would go to the start of the file.
* This is especially useful if you run `myfile.read()` once and then need to read the contents again.
* `close()`: Closes the file.
As all these methods are `async` methods, you need to "await" them.
For example, inside of an `async` *path operation function* you can get the contents with:
```Python
contents = await myfile.read()
```
If you are inside of a normal `def` *path operation function*, you can access the `UploadFile.file` directly, for example:
```Python
contents = myfile.file.read()
```
!!! note "`async` Technical Details"
When you use the `async` methods, **FastAPI** runs the file methods in a threadpool and awaits for them.
!!! note "Starlette Technical Details"
**FastAPI**'s `UploadFile` inherits directly from **Starlette**'s `UploadFile`, but adds some necessary parts to make it compatible with **Pydantic** and the other parts of FastAPI.
## "Form Data"?
The way HTML forms (`<form></form>`) sends the data to the server normally uses a "special" encoding for that data, it's different from JSON.
@@ -35,7 +99,7 @@ The way HTML forms (`<form></form>`) sends the data to the server normally uses
But when the form includes files, it is encoded as `multipart/form-data`. If you use `File`, **FastAPI** will know it has to get the files from the correct part of the body.
If you want to read more about these encondings and form fields, head to the <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST" target="_blank"><abbr title="Mozilla Developer Network">MDN</abbr> web docs for <code>POST</code></a>.
If you want to read more about these encodings and form fields, head to the <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST" target="_blank"><abbr title="Mozilla Developer Network">MDN</abbr> web docs for <code>POST</code></a>.
!!! warning

View File

@@ -10,12 +10,14 @@ You can define files and form fields at the same time using `File` and `Form`.
Create file and form parameters the same way you would for `Body` or `Query`:
```Python hl_lines="7"
```Python hl_lines="8"
{!./src/request_forms_and_files/tutorial001.py!}
```
The files and form fields will be uploaded as form data and you will receive the files and form fields.
And you can declare some of the files as `bytes` and some as `UploadFile`.
!!! warning
You can declare multiple `File` and `Form` parameters in a path operation, but you can't also declare `Body` fields that you expect to receive as JSON, as the request will have the body encoded using `multipart/form-data` instead of `application/json`.

View File

@@ -39,7 +39,7 @@ The way HTML forms (`<form></form>`) sends the data to the server normally uses
But when the form includes files, it is encoded as `multipart/form-data`. You'll read about handling files in the next chapter.
If you want to read more about these encondings and form fields, head to the <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST" target="_blank"><abbr title="Mozilla Developer Network">MDN</abbr> web docs for <code>POST</code></a>.
If you want to read more about these encodings and form fields, head to the <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST" target="_blank"><abbr title="Mozilla Developer Network">MDN</abbr> web docs for <code>POST</code></a>.
!!! warning

View File

@@ -40,7 +40,7 @@ And we are using this model to declare our input and the same model to declare o
Now, whenever a browser is creating a user with a password, the API will return the same password in the response.
In this case, it might not be a problem, becase the user himself is sending the password.
In this case, it might not be a problem, because the user himself is sending the password.
But if we use the same model for another path operation, we could be sending the passwords of our users to every client.

View File

@@ -30,7 +30,7 @@ So, we can have sub-dependencies using `Security` too.
`get_current_user` will have a `Security` dependency with the same `oauth2_scheme` we created before.
The same as we were doing before in the path operation direclty, our new dependency will receive a `token` as a `str` from the `Security` dependency:
The same as we were doing before in the path operation directly, our new dependency will receive a `token` as a `str` from the `Security` dependency:
```Python hl_lines="25"
{!./src/security/tutorial002.py!}

View File

@@ -81,7 +81,7 @@ And another utility to verify if a received password matches the hash stored.
And another one to authenticate and return a user.
```Python hl_lines="7 51 58 59 62 63 72 73 74 75 76 77 78"
```Python hl_lines="7 50 57 58 61 62 71 72 73 74 75 76 77"
{!./src/security/tutorial004.py!}
```
@@ -112,7 +112,7 @@ Define a Pydantic Model that will be used in the token endpoint for the response
Create a utility function to generate a new access token.
```Python hl_lines="3 6 14 15 16 17 31 32 33 81 82 83 84 85 86 87 88 89"
```Python hl_lines="3 6 13 14 15 16 30 31 32 80 81 82 83 84 85 86 87 88"
{!./src/security/tutorial004.py!}
```
@@ -124,7 +124,7 @@ Decode the received token, verify it, and return the current user.
If the token is invalid, return an HTTP error right away.
```Python hl_lines="92 93 94 95 96 97 98 99 100 101"
```Python hl_lines="91 92 93 94 95 96 97 98 99 100"
{!./src/security/tutorial004.py!}
```
@@ -134,7 +134,7 @@ Create a `timedelta` with the expiration time of the token.
Create a real JWT access token and return it.
```Python hl_lines="115 116 117 118 119"
```Python hl_lines="114 115 116 117 118"
{!./src/security/tutorial004.py!}
```

View File

@@ -24,14 +24,14 @@ The form field name is `scope` (in singular), but it is actually a long string w
Each "scope" is just a string (without spaces).
They are normally used to declare specific security permissions, for exampe:
They are normally used to declare specific security permissions, for example:
* `"users:read"` or `"users:write"` are common examples.
* `instagram_basic` is used by Facebook / Instagram.
* `https://www.googleapis.com/auth/drive` is used by Google.
!!! info
In OAuth2 a "scope" is just a string that declares a specific permision required.
In OAuth2 a "scope" is just a string that declares a specific permission required.
It doesn't matter if it has other characters like `:`, or if it is a URL.
@@ -78,9 +78,9 @@ Now, get the user data from the (fake) database, using the `username` from the f
If there is no such user, we return an error saying "incorrect username or password".
For the error, we use the exception `HTTPException` provided by Starlette directly:
For the error, we use the exception `HTTPException`:
```Python hl_lines="4 74 75 76"
```Python hl_lines="1 73 74 75"
{!./src/security/tutorial003.py!}
```
@@ -108,7 +108,7 @@ If your database is stolen, the thief won't have your users' plaintext passwords
So, the thief won't be able to try to use that password in another system (as many users use the same password everywhere, this would be dangerous).
```Python hl_lines="77 78 79 80"
```Python hl_lines="76 77 78 79"
{!./src/security/tutorial003.py!}
```
@@ -146,7 +146,7 @@ For this simple example, we are going to just be completely insecure and return
But for now, let's focus on the specific details we need.
```Python hl_lines="82"
```Python hl_lines="81"
{!./src/security/tutorial003.py!}
```
@@ -162,7 +162,7 @@ Both of these dependencies will just return an HTTP error if the user doesn't ex
So, in our endpoint, we will only get a user if the user exists, was correctly authenticated, and is active:
```Python hl_lines="57 58 59 60 61 62 63 66 67 68 69 86"
```Python hl_lines="56 57 58 59 60 61 62 65 66 67 68 85"
{!./src/security/tutorial003.py!}
```

View File

@@ -12,7 +12,13 @@ You can easily adapt it to any database supported by SQLAlchemy, like:
* Oracle
* Microsoft SQL Server, etc.
In this example, we'll use **PostgreSQL**.
In this example, we'll use **SQLite**, because it uses a single file and Python has integrated support. So, you can copy this example and run it as is.
Later, for your production application, you might want to use a database server like **PostgreSQL**.
!!! tip
There is an official project generator with **FastAPI** and **PostgreSQL**, all based on **Docker**, including a frontend and more tools: <a href="https://github.com/tiangolo/full-stack-fastapi-postgresql" target="_blank">https://github.com/tiangolo/full-stack-fastapi-postgresql</a>
!!! note
Notice that most of the code is the standard `SQLAlchemy` code you would use with any framework.
@@ -23,41 +29,111 @@ In this example, we'll use **PostgreSQL**.
For now, don't pay attention to the rest, only the imports:
```Python hl_lines="3 4 5"
```Python hl_lines="2 3 4"
{!./src/sql_databases/tutorial001.py!}
```
## Define the database
Define the database that SQLAlchemy should connect to:
Define the database that SQLAlchemy should "connect" to:
```Python hl_lines="8"
{!./src/sql_databases/tutorial001.py!}
```
!!! tip
This is the main line that you would have to modify if you wanted to use a different database than **PostgreSQL**.
In this example, we are "connecting" to a SQLite database (opening a file with the SQLite database).
## Create the SQLAlchemy `engine`
The file will be located at the same directory in the file `test.db`. That's why the last part is `./test.db`.
```Python hl_lines="10"
{!./src/sql_databases/tutorial001.py!}
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"
```
## Create a `scoped_session`
...and adapt it with your database data and credentials (equivalently for MySQL, MariaDB or any other).
!!! tip
This is the main line that you would have to modify if you wanted to use a different database.
## Create the SQLAlchemy `engine`
```Python hl_lines="11 12 13"
{!./src/sql_databases/tutorial001.py!}
```
!!! note "Very Technical Details"
Don't worry too much if you don't understand this. You can still use the code.
### Note
This `scoped_session` is a feature of SQLAlchemy.
The argument:
The resulting object, the `db_session` can then be used anywhere a a normal SQLAlchemy session.
```Python
connect_args={"check_same_thread": False}
```
...is needed only for `SQLite`. It's not needed for other databases.
!!! info "Technical Details"
That argument `check_same_thread` is there mainly to be able to run the tests that cover this example.
It can be used as a global because it is implemented to work independently on each "<abbr title="A sequence of code being executed by the program, while at the same time, or at intervals, there can be others being executed too.">thread</abbr>", so the actions you perform with it in one path operation function won't affect the actions performed (possibly concurrently) by other path operation functions.
## Create a `SessionLocal` class
Each instance of the `SessionLocal` class will have a connection to the database.
This object (class) is not a connection to the database yet, but once we create an instance of this class, that instance will have the actual connection to the database.
We name it `SessionLocal` to distinguish it form the `Session` we are importing from SQLAlchemy.
We will use `Session` to declare types later and getter better editor support and completion.
For now, create the `SessionLocal`:
```Python hl_lines="14"
{!./src/sql_databases/tutorial001.py!}
```
## Create a middleware to handle sessions
Now let's temporarily jump to the end of the file, to use the `SessionLocal` class we created above.
We need to have an independent database session/connection (`SessionLocal`) per request, use the same session through all the request and then close it after the request is finished.
And then a new session will be created for the next request.
For that, we will create a new middleware.
A "middleware" is a function that is always executed for each request, and have code before and after the request.
This middleware (just a function) will create a new SQLAlchemy `SessionLocal` for each request, add it to the request and then close it once the request is finished.
```Python hl_lines="67 68 69 70 71 72"
{!./src/sql_databases/tutorial001.py!}
```
### About `request.state`
<a href="https://www.starlette.io/requests/#other-state" target="_blank">`request.state` is a property of each Starlette `Request` object</a>, it is there to store arbitrary objects attached to the request itself, like the database session in this case.
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).
## Create a dependency
To simplify the code, reduce repetition and get better editor support, we will create a dependency that returns this same database session from the request.
And when using the dependency in a path operation function, we declare it with the type `Session` we imported directly from SQLAlchemy.
This will then give us better editor support inside the path operation function, because the editor will know that the `db` parameter is of type `Session`.
```Python hl_lines="53 54 68"
{!./src/sql_databases/tutorial001.py!}
```
!!! info "Technical Details"
The parameter `db` is actually of type `SessionLocal`, but this class (created with `sessionmaker()`) is a "proxy" of a SQLAlchemy `Session`, so, the editor doesn't really know what methods are provided.
But by declaring the type as `Session`, the editor now can know the available methods (`.add()`, `.query()`, `.commit()`, etc) and can provide better support (like completion). The type declaration doesn't affect the actual object.
## Create a `CustomBase` model
@@ -65,17 +141,17 @@ This is more of a trick to facilitate your life than something required.
But by creating this `CustomBase` class and inheriting from it, your models will have automatic `__tablename__` attributes (that are required by SQLAlchemy).
That way you don't have to declare them explicitly.
That way you don't have to declare them explicitly in every model.
So, your models will behave very similarly to, for example, Flask-SQLAlchemy.
```Python hl_lines="16 17 18 19 20"
```Python hl_lines="17 18 19 20 21"
{!./src/sql_databases/tutorial001.py!}
```
## Create the SQLAlchemy `Base` model
```Python hl_lines="23"
```Python hl_lines="24"
{!./src/sql_databases/tutorial001.py!}
```
@@ -85,15 +161,43 @@ Now this is finally code specific to your app.
Here's a user model that will be a table in the database:
```Python hl_lines="26 27 28 29 30"
```Python hl_lines="27 28 29 30 31"
{!./src/sql_databases/tutorial001.py!}
```
## Initialize your application
In a very simplistic way, initialize your database (create the tables, etc) and make sure you have a first user:
```Python hl_lines="34 36 38 39 40 41 42 44"
{!./src/sql_databases/tutorial001.py!}
```
!!! info
Notice that we close the session with `db_session.close()`.
We close this session because we only used it to create this first user.
Every new request will get its own new session.
### Note
Normally you would probably initialize your database (create tables, etc) with <a href="https://alembic.sqlalchemy.org/en/latest/" target="_blank">Alembic</a>.
And you would also use Alembic for migrations (that's its main job). For whenever you change the structure of your database, add a new column, a new table, etc.
The same way, you would probably make sure there's a first user in an external script that runs before your application, or as part of the application startup.
In this example we are doing those two operations in a very simple way, directly in the code, to focus on the main points.
Also, as all the functionality is self-contained in the same code, you can copy it and run it directly, and it will work as is.
## Get a user
By creating a function that is only dedicated to getting your user from a `username` (or any other parameter) independent of your path operation function, you can more easily re-use it in multiple parts and also add <abbr title="Automated test, written in code, that checks if another piece of code is working correctly.">unit tests</abbr> for it:
By creating a function that is only dedicated to getting your user from a `user_id` (or any other parameter) independent of your path operation function, you can more easily re-use it in multiple parts and also add <abbr title="Automated tests, written in code, that check if another piece of code is working correctly.">unit tests</abbr> for it:
```Python hl_lines="33 34"
```Python hl_lines="48 49"
{!./src/sql_databases/tutorial001.py!}
```
@@ -103,40 +207,103 @@ Now, finally, here's the standard **FastAPI** code.
Create your app and path operation function:
```Python hl_lines="38 41 42 43 44"
```Python hl_lines="58 61 62 63 64"
{!./src/sql_databases/tutorial001.py!}
```
As we are using SQLAlchemy's `scoped_session`, we don't even have to create a dependency with `Depends`.
We are creating the database session before each request, attaching it to the request, and then closing it afterwards.
We can just call `get_user` directly from inside of the path operation function and use the global `db_session`.
All of this is done in the middleware explained above.
Then, in the dependency `get_db()` we are extracting the database session from the request.
And then we can create the dependency in the path operation function, to get that session directly.
With that, we can just call `get_user` directly from inside of the path operation function and use that session.
Having this 3-step process (middleware, dependency, path operation) in this simple example might seem like an overkill. But imagine if you had 20 or 100 path operations, doing this, you would be reducing a lot of code repetition, and getting better support/checks/completion in all those path operation functions.
## Create the path operation function
Here we are using SQLAlchemy code inside of the path operation function, and it in turn will go and communicate with an external database.
Here we are using SQLAlchemy code inside of the path operation function, and in turn it will go and communicate with an external database.
That could potentially require some "waiting".
But as SQLAlchemy doesn't have compatibility for using `await`, as would be with something like:
But as SQLAlchemy doesn't have compatibility for using `await` directly, as would be with something like:
```Python
user = await get_user(username, db_session)
user = await get_user(db_session, user_id=user_id)
```
...and instead we are using:
```Python
user = get_user(username, db_session)
user = get_user(db_session, user_id=user_id)
```
Then we should declare the path operation without `async def`, just with a normal `def`:
```Python hl_lines="42"
```Python hl_lines="62"
{!./src/sql_databases/tutorial001.py!}
```
!!! note "Very Technical Details"
If you are curious and have a deep technical knowledge, you can check <a href="https://fastapi.tiangolo.com/async/#very-technical-details" target="_blank">the very technical details of how this `async def` vs `def` is handled</a>.
## Migrations
Because we are using SQLAlchemy directly and we don't require any kind of plug-in for it to work with **FastAPI**, we could integrate database <abbr title="Automatically updating the database to have any new column we define in our models.">migrations</abbr> with <a href="https://alembic.sqlalchemy.org" target="_blank">Alembic</a> directly.
You would probably want to declare your database and models in a different file or set of files, this would allow Alembic to import it and use it without even needing to have **FastAPI** installed for the migrations.
## Check it
You can copy this code and use it as is.
!!! info
In fact, the code shown here is part of the tests. As most of the code in these docs.
You can copy it, let's say, to a file `main.py`.
Then you can run it with Uvicorn:
```bash
uvicorn main:app --debug
```
And then, you can open your browser at <a href="http://127.0.0.1:8000/docs" target="_blank">http://127.0.0.1:8000/docs</a>.
And you will be able to interact with your **FastAPI** application, reading data from a real database:
<img src="/img/tutorial/sql-databases/image01.png">
## Response schema and security
This section has the minimum code to show how it works and how you can integrate SQLAlchemy with FastAPI.
But it is recommended that you also create a response model with Pydantic, as described in the section about <a href="/tutorial/extra-models/" target="_blank">Extra Models</a>.
That way you will document the schema of the responses of your API, and you will be able to limit/filter the returned data.
Limiting the returned data is important for security, as for example, you shouldn't be returning the `hashed_password` to the clients.
That's something that you can improve in this example application, here's the current response data:
```JSON
{
"is_active": true,
"hashed_password": "notreallyhashed",
"email": "johndoe@example.com",
"id": 1
}
```
## Interact with the database directly
If you want to explore the SQLite database (file) directly, independently of FastAPI, to debug its contents, add tables, columns, records, modify data, etc. you can use <a href="https://sqlitebrowser.org/" target="_blank">DB Browser for SQLite</a>.
It will look like this:
<img src="/img/tutorial/sql-databases/image02.png">

View File

@@ -0,0 +1,95 @@
There are at least two situations where you could need to create your **FastAPI** application using some specific paths.
But then you need to set them up to be served with a path prefix.
It could happen if you have a:
* **Proxy** server.
* You are "**mounting**" a FastAPI application inside another FastAPI application (or inside another ASGI application, like Starlette).
## Proxy
Having a proxy in this case means that you could declare a path at `/app`, but then, you could need to add a layer on top (the Proxy) that would put your **FastAPI** application under a path like `/api/v1`.
In this case, the original path `/app` will actually be served at `/api/v1/app`.
Even though your application "thinks" it is serving at `/app`.
And the Proxy could be re-writing the path "on the fly" to keep your application convinced that it is serving at `/app`.
Up to here, everything would work as normally.
But then, when you open the integrated docs, they would expect to get the OpenAPI schema at `/openapi.json`, instead of `/api/v1/openapi.json`.
So, the frontend (that runs in the browser) would try to reach `/openapi.json` and wouldn't be able to get the OpenAPI schema.
So, it's needed that the frontend looks for the OpenAPI schema at `/api/v1/openapi.json`.
And it's also needed that the returned JSON OpenAPI schema has the defined path at `/api/v1/app` (behind the proxy) instead of `/app`.
---
For these cases, you can declare an `openapi_prefix` parameter in your `FastAPI` application.
See the section below, about "mounting", for an example.
## Mounting a **FastAPI** application
"Mounting" means adding a complete "independent" application in a specific path, that then takes care of handling all the sub-paths.
You could want to do this if you have several "independent" applications that you want to separate, having their own independent OpenAPI schema and user interfaces.
### Top-level application
First, create the main, top-level, **FastAPI** application, and its path operations:
```Python hl_lines="3 6 7 8"
{!./src/sub_applications/tutorial001.py!}
```
### Sub-application
Then, create your sub-application, and its path operations.
This sub-application is just another standard FastAPI application, but this is the one that will be "mounted".
When creating the sub-application, use the parameter `openapi_prefix`. In this case, with a prefix of `/subapi`:
```Python hl_lines="11 14 15 16"
{!./src/sub_applications/tutorial001.py!}
```
### Mount the sub-application
In your top-level application, `app`, mount the sub-application, `subapi`.
Here you need to make sure you use the same path that you used for the `openapi_prefix`, in this case, `/subapi`:
```Python hl_lines="11 19"
{!./src/sub_applications/tutorial001.py!}
```
## Check the automatic API docs
Now, run `uvicorn`, if your file is at `main.py`, it would be:
```bash
uvicorn main:app --debug
```
And open the docs at <a href="http://127.0.0.1:8000/docs" target="_blank">http://127.0.0.1:8000/docs</a>.
You will see the automatic API docs for the main app, including only its own paths:
<img src="/img/tutorial/sub-applications/image01.png">
And then, open the docs for the sub-application, at <a href="http://127.0.0.1:8000/subapi/docs" target="_blank">http://127.0.0.1:8000/subapi/docs</a>.
You will see the automatic API docs for the sub-application, including only its own sub-paths, with their correct prefix:
<img src="/img/tutorial/sub-applications/image02.png">
If you try interacting with any of the two user interfaces, they will work, because the browser will be able to talk to the correct path (or sub-path).

View File

@@ -0,0 +1,55 @@
Up to now, you have been declaring the parts of the request that you need with their types.
Taking data from:
* The path as parameters.
* Headers.
* Cookies.
* etc.
And by doing so, **FastAPI** is validating that data, converting it and generating documentation for your API automatically.
But there are situations where you might need to access the `Request` object directly.
## Details about the `Request` object
As **FastAPI** is actually **Starlette** underneath, with a layer of several tools on top, you can use Starlette's <a href="https://www.starlette.io/requests/" target="_blank">`Request`</a> object directly when you need to.
It would also mean that if you get data from the `Request` object directly (for example, read the body) it won't be validated, converted or annotated (with OpenAPI, for the automatic documentation) by FastAPI.
Although any other parameter declared normally (for example, the body with a Pydantic model) would still be validated, converted, annotated, etc.
But there are specific cases where it's useful to get the `Request` object.
## Use the `Request` object directly
Let's imagine you want to get the client's IP address/host inside of your *path operation function*.
For that you need to access the request directly.
### Import the `Request`
First, import the `Request` class from Starlette:
```Python hl_lines="2"
{!./src/using_request_directly/tutorial001.py!}
```
### Declare the `Request` parameter
Then declare a *path operation function* parameter with the type being the `Request` class:
```Python hl_lines="8"
{!./src/using_request_directly/tutorial001.py!}
```
!!! tip
Note that in this case, we are declaring a path parameter besides the request parameter.
So, the path parameter will be extracted, validated, converted to the specified type and annotated with OpenAPI.
The same way, you can declare any other parameter as normally, and additionally, get the `Request` too.
## `Request` documentation
You can read more details about the <a href="https://www.starlette.io/requests/" target="_blank">`Request` object in the official Starlette documentation site</a>.

View File

@@ -0,0 +1,93 @@
You can use <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API" target="_blank">WebSockets</a> with **FastAPI**.
## WebSockets client
### In production
In your production system, you probably have a frontend created with a modern framework like React, Vue.js or Angular.
And to communicate using WebSockets with your backend you would probably use your frontend's utilities.
Or you might have a native mobile application that communicates with your WebSocket backend directly, in native code.
Or you might have any other way to communicate with the WebSocket endpoint.
---
But for this example, we'll use a very simple HTML document with some JavaScript, all inside a long string.
This, of course, is not optimal and you wouldn't use it for production.
In production you would have one of the options above.
But it's the simplest way to focus on the server-side of WebSockets and have a working example:
```Python hl_lines="2 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 42 43 44"
{!./src/websockets/tutorial001.py!}
```
## Create a `websocket_route`
In your **FastAPI** application, create a `websocket_route`:
```Python hl_lines="3 47 48"
{!./src/websockets/tutorial001.py!}
```
!!! tip
In this example we are importing `WebSocket` from `starlette.websockets` to use it in the type declaration in the WebSocket route function.
That is not required, but it's recommended as it will provide you completion and checks inside the function.
!!! info
This `websocket_route` we are using comes directly from <a href="https://www.starlette.io/applications/" target="_blank">Starlette</a>.
That's why the naming convention is not the same as with other API path operations (`get`, `post`, etc).
## Await for messages and send messages
In your WebSocket route you can `await` for messages and send messages.
```Python hl_lines="49 50 51 52 53"
{!./src/websockets/tutorial001.py!}
```
You can receive and send binary, text, and JSON data.
To learn more about the options, check Starlette's documentation for:
* <a href="https://www.starlette.io/applications/" target="_blank">Applications (`websocket_route`)</a>.
* <a href="https://www.starlette.io/websockets/" target="_blank">The `WebSocket` class</a>.
* <a href="https://www.starlette.io/endpoints/#websocketendpoint" target="_blank">Class-based WebSocket handling</a>.
## Test it
If your file is named `main.py`, run your application with:
```bash
uvicorn main:app --debug
```
Open your browser at <a href="http://127.0.0.1:8000" target="_blank">http://127.0.0.1:8000</a>.
You will see a simple page like:
<img src="/img/tutorial/websockets/image01.png">
You can type messages in the input box, and send them:
<img src="/img/tutorial/websockets/image02.png">
And your **FastAPI** application with WebSockets will respond back:
<img src="/img/tutorial/websockets/image03.png">
You can send (and receive) many messages:
<img src="/img/tutorial/websockets/image04.png">
And all of them will use the same WebSocket connection.

View File

@@ -1,7 +1,9 @@
"""FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
__version__ = "0.2.1"
__version__ = "0.7.0"
from .applications import FastAPI
from .routing import APIRouter
from .params import Body, Path, Query, Header, Cookie, Form, File, Security, Depends
from .exceptions import HTTPException
from .datastructures import UploadFile

View File

@@ -7,42 +7,48 @@ from pydantic import BaseModel
from starlette.applications import Starlette
from starlette.exceptions import ExceptionMiddleware, HTTPException
from starlette.middleware.errors import ServerErrorMiddleware
from starlette.middleware.lifespan import LifespanMiddleware
from starlette.requests import Request
from starlette.responses import JSONResponse, Response
from starlette.routing import BaseRoute
async def http_exception(request: Request, exc: HTTPException) -> JSONResponse:
return JSONResponse({"detail": exc.detail}, status_code=exc.status_code)
headers = getattr(exc, "headers", None)
if headers:
return JSONResponse(
{"detail": exc.detail}, status_code=exc.status_code, headers=headers
)
else:
return JSONResponse({"detail": exc.detail}, status_code=exc.status_code)
class FastAPI(Starlette):
def __init__(
self,
debug: bool = False,
routes: List[BaseRoute] = None,
template_directory: str = None,
title: str = "Fast API",
description: str = "",
version: str = "0.1.0",
openapi_url: Optional[str] = "/openapi.json",
openapi_prefix: str = "",
docs_url: Optional[str] = "/docs",
redoc_url: Optional[str] = "/redoc",
**extra: Dict[str, Any],
) -> None:
self._debug = debug
self.router: routing.APIRouter = routing.APIRouter()
self.router: routing.APIRouter = routing.APIRouter(routes)
self.exception_middleware = ExceptionMiddleware(self.router, debug=debug)
self.error_middleware = ServerErrorMiddleware(
self.exception_middleware, debug=debug
)
self.lifespan_middleware = LifespanMiddleware(self.error_middleware)
self.schema_generator = None
self.template_env = self.load_template_env(template_directory)
self.title = title
self.description = description
self.version = version
self.openapi_url = openapi_url
self.openapi_prefix = openapi_prefix.rstrip("/")
self.docs_url = docs_url
self.redoc_url = redoc_url
self.extra = extra
@@ -66,6 +72,7 @@ class FastAPI(Starlette):
openapi_version=self.openapi_version,
description=self.description,
routes=self.routes,
openapi_prefix=self.openapi_prefix,
)
return self.openapi_schema
@@ -80,7 +87,8 @@ class FastAPI(Starlette):
self.add_route(
self.docs_url,
lambda r: get_swagger_ui_html(
openapi_url=self.openapi_url, title=self.title + " - Swagger UI"
openapi_url=self.openapi_prefix + self.openapi_url,
title=self.title + " - Swagger UI",
),
include_in_schema=False,
)
@@ -88,7 +96,8 @@ class FastAPI(Starlette):
self.add_route(
self.redoc_url,
lambda r: get_redoc_html(
openapi_url=self.openapi_url, title=self.title + " - ReDoc"
openapi_url=self.openapi_prefix + self.openapi_url,
title=self.title + " - ReDoc",
),
include_in_schema=False,
)

15
fastapi/datastructures.py Normal file
View File

@@ -0,0 +1,15 @@
from typing import Any, Callable, Iterable, Type
from starlette.datastructures import UploadFile as StarletteUploadFile
class UploadFile(StarletteUploadFile):
@classmethod
def __get_validators__(cls: Type["UploadFile"]) -> Iterable[Callable]:
yield cls.validate
@classmethod
def validate(cls: Type["UploadFile"], v: Any) -> Any:
if not isinstance(v, StarletteUploadFile):
raise ValueError(f"Expected UploadFile, received: {type(v)}")
return v

View File

@@ -17,6 +17,7 @@ from pydantic.fields import Field, Required, Shape
from pydantic.schema import get_annotation_from_schema
from pydantic.utils import lenient_issubclass
from starlette.concurrency import run_in_threadpool
from starlette.datastructures import UploadFile
from starlette.requests import Headers, QueryParams, Request
param_supported_types = (
@@ -323,6 +324,12 @@ async def request_body_to_args(
else:
values[field.name] = deepcopy(field.default)
continue
if (
isinstance(field.schema, params.File)
and lenient_issubclass(field.type_, bytes)
and isinstance(value, UploadFile)
):
value = await value.read()
v_, errors_ = field.validate(value, values, loc=("body", field.alias))
if isinstance(errors_, ErrorWrapper):
errors.append(errors_)
@@ -333,6 +340,21 @@ async def request_body_to_args(
return values, errors
def get_schema_compatible_field(*, field: Field) -> Field:
if lenient_issubclass(field.type_, UploadFile):
return Field(
name=field.name,
type_=bytes,
class_validators=field.class_validators,
model_config=field.model_config,
default=field.default,
required=field.required,
alias=field.alias,
schema=field.schema,
)
return field
def get_body_field(*, dependant: Dependant, name: str) -> Field:
flat_dependant = get_flat_dependant(dependant)
if not flat_dependant.body_params:
@@ -340,11 +362,11 @@ def get_body_field(*, dependant: Dependant, name: str) -> Field:
first_param = flat_dependant.body_params[0]
embed = getattr(first_param.schema, "embed", None)
if len(flat_dependant.body_params) == 1 and not embed:
return first_param
return get_schema_compatible_field(field=first_param)
model_name = "Body_" + name
BodyModel = create_model(model_name)
for f in flat_dependant.body_params:
BodyModel.__fields__[f.name] = f
BodyModel.__fields__[f.name] = get_schema_compatible_field(field=f)
required = any(True for f in flat_dependant.body_params if f.required)
if any(isinstance(f.schema, params.File) for f in flat_dependant.body_params):
BodySchema: Type[params.Body] = params.File

View File

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

9
fastapi/exceptions.py Normal file
View File

@@ -0,0 +1,9 @@
from starlette.exceptions import HTTPException as StarletteHTTPException
class HTTPException(StarletteHTTPException):
def __init__(
self, status_code: int, detail: str = None, headers: dict = None
) -> None:
super().__init__(status_code=status_code, detail=detail)
self.headers = headers

View File

@@ -8,6 +8,7 @@ def get_swagger_ui_html(*, openapi_url: str, title: str) -> HTMLResponse:
<html>
<head>
<link type="text/css" rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swagger-ui-dist@3/swagger-ui.css">
<link rel="shortcut icon" href="https://fastapi.tiangolo.com/img/favicon.png">
<title>
"""
+ title
@@ -55,6 +56,7 @@ def get_redoc_html(*, openapi_url: str, title: str) -> HTMLResponse:
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
<link rel="shortcut icon" href="https://fastapi.tiangolo.com/img/favicon.png">
<!--
ReDoc doesn't change outer page styles

View File

@@ -215,7 +215,8 @@ def get_openapi(
version: str,
openapi_version: str = "3.0.2",
description: str = None,
routes: Sequence[BaseRoute]
routes: Sequence[BaseRoute],
openapi_prefix: str = ""
) -> Dict:
info = {"title": title, "version": version}
if description:
@@ -234,7 +235,7 @@ def get_openapi(
if result:
path, security_schemes, path_definitions = result
if path:
paths.setdefault(route.path, {}).update(path)
paths.setdefault(openapi_prefix + route.path, {}).update(path)
if security_schemes:
components.setdefault("securitySchemes", {}).update(
security_schemes

View File

@@ -15,7 +15,6 @@ from pydantic.utils import lenient_issubclass
from starlette import routing
from starlette.concurrency import run_in_threadpool
from starlette.exceptions import HTTPException
from starlette.formparsers import UploadFile
from starlette.requests import Request
from starlette.responses import JSONResponse, Response
from starlette.routing import compile_path, get_name, request_response
@@ -57,10 +56,7 @@ def get_app(
raw_body = await request.form()
form_fields = {}
for field, value in raw_body.items():
if isinstance(value, UploadFile):
form_fields[field] = await value.read()
else:
form_fields[field] = value
form_fields[field] = value
if form_fields:
body = form_fields
else:

View File

@@ -40,6 +40,7 @@ nav:
- Form Data: 'tutorial/request-forms.md'
- Request Files: 'tutorial/request-files.md'
- Request Forms and Files: 'tutorial/request-forms-and-files.md'
- Handling Errors: 'tutorial/handling-errors.md'
- Path Operation Configuration: 'tutorial/path-operation-configuration.md'
- Path Operation Advanced Configuration: 'tutorial/path-operation-advanced-configuration.md'
- Custom Response: 'tutorial/custom-response.md'
@@ -54,16 +55,23 @@ nav:
- Get Current User: 'tutorial/security/get-current-user.md'
- Simple OAuth2 with Password and Bearer: 'tutorial/security/simple-oauth2.md'
- OAuth2 with Password (and hashing), Bearer with JWT tokens: 'tutorial/security/oauth2-jwt.md'
- Using the Request Directly: 'tutorial/using-request-directly.md'
- SQL (Relational) Databases: 'tutorial/sql-databases.md'
- NoSQL (Distributed / Big Data) Databases: 'tutorial/nosql-databases.md'
- Bigger Applications - Multiple Files: 'tutorial/bigger-applications.md'
- Sub Applications - Behind a Proxy: 'tutorial/sub-applications-proxy.md'
- Application Configuration: 'tutorial/application-configuration.md'
- Extra Starlette options: 'tutorial/extra-starlette.md'
- GraphQL: 'tutorial/graphql.md'
- WebSockets: 'tutorial/websockets.md'
- Debugging: 'tutorial/debugging.md'
- Concurrency and async / await: 'async.md'
- Deployment: 'deployment.md'
- Project Generation - Template: 'project-generation.md'
- Alternatives, Inspiration and Comparisons: 'alternatives.md'
- History, Design and Future: 'history-design-future.md'
- Benchmarks: 'benchmarks.md'
- Help FastAPI - Get Help: 'help-fastapi.md'
- Development - Contributing: 'contributing.md'
- Release Notes: release-notes.md
markdown_extensions:

View File

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

View File

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

View File

@@ -0,0 +1,7 @@
import pytest
from fastapi import UploadFile
def test_upload_file_invalid():
with pytest.raises(ValueError):
UploadFile.validate("not a Starlette UploadFile")

View File

@@ -0,0 +1,156 @@
from fastapi import FastAPI, HTTPException
from starlette.exceptions import HTTPException as StarletteHTTPException
from starlette.testclient import TestClient
app = FastAPI()
items = {"foo": "The Foo Wrestlers"}
@app.get("/items/{item_id}")
async def create_item(item_id: str):
if item_id not in items:
raise HTTPException(
status_code=404,
detail="Item not found",
headers={"X-Error": "Some custom header"},
)
return {"item": items[item_id]}
@app.get("/starlette-items/{item_id}")
async def create_item(item_id: str):
if item_id not in items:
raise StarletteHTTPException(status_code=404, detail="Item not found")
return {"item": items[item_id]}
client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"paths": {
"/items/{item_id}": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Create Item Get",
"operationId": "create_item_items__item_id__get",
"parameters": [
{
"required": True,
"schema": {"title": "Item_Id", "type": "string"},
"name": "item_id",
"in": "path",
}
],
}
},
"/starlette-items/{item_id}": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Create Item Get",
"operationId": "create_item_starlette-items__item_id__get",
"parameters": [
{
"required": True,
"schema": {"title": "Item_Id", "type": "string"},
"name": "item_id",
"in": "path",
}
],
}
},
},
"components": {
"schemas": {
"ValidationError": {
"title": "ValidationError",
"required": ["loc", "msg", "type"],
"type": "object",
"properties": {
"loc": {
"title": "Location",
"type": "array",
"items": {"type": "string"},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
},
},
"HTTPValidationError": {
"title": "HTTPValidationError",
"type": "object",
"properties": {
"detail": {
"title": "Detail",
"type": "array",
"items": {"$ref": "#/components/schemas/ValidationError"},
}
},
},
}
},
}
def test_openapi_schema():
response = client.get("/openapi.json")
assert response.status_code == 200
assert response.json() == openapi_schema
def test_get_item():
response = client.get("/items/foo")
assert response.status_code == 200
assert response.json() == {"item": "The Foo Wrestlers"}
def test_get_item_not_found():
response = client.get("/items/bar")
assert response.status_code == 404
assert response.headers.get("x-error") == "Some custom header"
assert response.json() == {"detail": "Item not found"}
def test_get_starlette_item():
response = client.get("/starlette-items/foo")
assert response.status_code == 200
assert response.json() == {"item": "The Foo Wrestlers"}
def test_get_starlette_item_not_found():
response = client.get("/starlette-items/bar")
assert response.status_code == 404
assert response.headers.get("x-error") is None
assert response.json() == {"detail": "Item not found"}

View File

View File

@@ -0,0 +1,90 @@
from starlette.testclient import TestClient
from handling_errors.tutorial001 import app
client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"paths": {
"/items/{item_id}": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Create Item Get",
"operationId": "create_item_items__item_id__get",
"parameters": [
{
"required": True,
"schema": {"title": "Item_Id", "type": "string"},
"name": "item_id",
"in": "path",
}
],
}
}
},
"components": {
"schemas": {
"ValidationError": {
"title": "ValidationError",
"required": ["loc", "msg", "type"],
"type": "object",
"properties": {
"loc": {
"title": "Location",
"type": "array",
"items": {"type": "string"},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
},
},
"HTTPValidationError": {
"title": "HTTPValidationError",
"type": "object",
"properties": {
"detail": {
"title": "Detail",
"type": "array",
"items": {"$ref": "#/components/schemas/ValidationError"},
}
},
},
}
},
}
def test_openapi_schema():
response = client.get("/openapi.json")
assert response.status_code == 200
assert response.json() == openapi_schema
def test_get_item():
response = client.get("/items/foo")
assert response.status_code == 200
assert response.json() == {"item": "The Foo Wrestlers"}
def test_get_item_not_found():
response = client.get("/items/bar")
assert response.status_code == 404
assert response.headers.get("x-error") is None
assert response.json() == {"detail": "Item not found"}

View File

@@ -0,0 +1,90 @@
from starlette.testclient import TestClient
from handling_errors.tutorial002 import app
client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"paths": {
"/items-header/{item_id}": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Create Item Header Get",
"operationId": "create_item_header_items-header__item_id__get",
"parameters": [
{
"required": True,
"schema": {"title": "Item_Id", "type": "string"},
"name": "item_id",
"in": "path",
}
],
}
}
},
"components": {
"schemas": {
"ValidationError": {
"title": "ValidationError",
"required": ["loc", "msg", "type"],
"type": "object",
"properties": {
"loc": {
"title": "Location",
"type": "array",
"items": {"type": "string"},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
},
},
"HTTPValidationError": {
"title": "HTTPValidationError",
"type": "object",
"properties": {
"detail": {
"title": "Detail",
"type": "array",
"items": {"$ref": "#/components/schemas/ValidationError"},
}
},
},
}
},
}
def test_openapi_schema():
response = client.get("/openapi.json")
assert response.status_code == 200
assert response.json() == openapi_schema
def test_get_item_header():
response = client.get("/items-header/foo")
assert response.status_code == 200
assert response.json() == {"item": "The Foo Wrestlers"}
def test_get_item_not_found_header():
response = client.get("/items-header/bar")
assert response.status_code == 404
assert response.headers.get("x-error") == "There goes my error"
assert response.json() == {"detail": "Item not found"}

View File

@@ -39,7 +39,39 @@ openapi_schema = {
"required": True,
},
}
}
},
"/uploadfile/": {
"post": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Create Upload File Post",
"operationId": "create_upload_file_uploadfile__post",
"requestBody": {
"content": {
"multipart/form-data": {
"schema": {
"$ref": "#/components/schemas/Body_create_upload_file"
}
}
},
"required": True,
},
}
},
},
"components": {
"schemas": {
@@ -51,6 +83,14 @@ openapi_schema = {
"file": {"title": "File", "type": "string", "format": "binary"}
},
},
"Body_create_upload_file": {
"title": "Body_create_upload_file",
"required": ["file"],
"type": "object",
"properties": {
"file": {"title": "File", "type": "string", "format": "binary"}
},
},
"ValidationError": {
"title": "ValidationError",
"required": ["loc", "msg", "type"],
@@ -131,3 +171,14 @@ def test_post_large_file(tmpdir):
response = client.post("/files/", files={"file": open(path, "rb")})
assert response.status_code == 200
assert response.json() == {"file_size": default_pydantic_max_size + 1}
def test_post_upload_file(tmpdir):
path = os.path.join(tmpdir, "test.txt")
with open(path, "wb") as file:
file.write(b"<file content>")
client = TestClient(app)
response = client.post("/uploadfile/", files={"file": open(path, "rb")})
assert response.status_code == 200
assert response.json() == {"filename": "test.txt"}

View File

@@ -1,4 +1,5 @@
import os
from pathlib import Path
from starlette.testclient import TestClient
@@ -45,10 +46,11 @@ openapi_schema = {
"schemas": {
"Body_create_file": {
"title": "Body_create_file",
"required": ["file", "token"],
"required": ["file", "fileb", "token"],
"type": "object",
"properties": {
"file": {"title": "File", "type": "string", "format": "binary"},
"fileb": {"title": "Fileb", "type": "string", "format": "binary"},
"token": {"title": "Token", "type": "string"},
},
},
@@ -94,20 +96,32 @@ file_required = {
"loc": ["body", "file"],
"msg": "field required",
"type": "value_error.missing",
}
},
{
"loc": ["body", "fileb"],
"msg": "field required",
"type": "value_error.missing",
},
]
}
token_required = {
"detail": [
{
"loc": ["body", "fileb"],
"msg": "field required",
"type": "value_error.missing",
},
{
"loc": ["body", "token"],
"msg": "field required",
"type": "value_error.missing",
}
},
]
}
# {'detail': [, {'loc': ['body', 'token'], 'msg': 'field required', 'type': 'value_error.missing'}]}
file_and_token_required = {
"detail": [
{
@@ -115,6 +129,11 @@ file_and_token_required = {
"msg": "field required",
"type": "value_error.missing",
},
{
"loc": ["body", "fileb"],
"msg": "field required",
"type": "value_error.missing",
},
{
"loc": ["body", "token"],
"msg": "field required",
@@ -153,14 +172,24 @@ def test_post_file_no_token(tmpdir):
assert response.json() == token_required
def test_post_file_and_token(tmpdir):
path = os.path.join(tmpdir, "test.txt")
with open(path, "wb") as file:
file.write(b"<file content>")
def test_post_files_and_token(tmpdir):
patha = Path(tmpdir) / "test.txt"
pathb = Path(tmpdir) / "testb.txt"
patha.write_text("<file content>")
pathb.write_text("<file b content>")
client = TestClient(app)
response = client.post(
"/files/", data={"token": "foo"}, files={"file": open(path, "rb")}
"/files/",
data={"token": "foo"},
files={
"file": patha.open("rb"),
"fileb": ("testb.txt", pathb.open("rb"), "text/plain"),
},
)
assert response.status_code == 200
assert response.json() == {"file_size": 14, "token": "foo"}
assert response.json() == {
"file_size": 14,
"token": "foo",
"fileb_content_type": "text/plain",
}

View File

View File

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

View File

View File

@@ -0,0 +1,66 @@
from starlette.testclient import TestClient
from sub_applications.tutorial001 import app
client = TestClient(app)
openapi_schema_main = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"paths": {
"/app": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
}
},
"summary": "Read Main Get",
"operationId": "read_main_app_get",
}
}
},
}
openapi_schema_sub = {
"openapi": "3.0.2",
"info": {"title": "Fast API", "version": "0.1.0"},
"paths": {
"/subapi/sub": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
}
},
"summary": "Read Sub Get",
"operationId": "read_sub_sub_get",
}
}
},
}
def test_openapi_schema_main():
response = client.get("/openapi.json")
assert response.status_code == 200
assert response.json() == openapi_schema_main
def test_main():
response = client.get("/app")
assert response.status_code == 200
assert response.json() == {"message": "Hello World from main app"}
def test_openapi_schema_sub():
response = client.get("/subapi/openapi.json")
assert response.status_code == 200
assert response.json() == openapi_schema_sub
def test_sub():
response = client.get("/subapi/sub")
assert response.status_code == 200
assert response.json() == {"message": "Hello World from sub API"}