diff --git a/.github/workflows/preview-docs.yml b/.github/workflows/preview-docs.yml
index 7d31a9c64e..2af56e2bca 100644
--- a/.github/workflows/preview-docs.yml
+++ b/.github/workflows/preview-docs.yml
@@ -16,7 +16,7 @@ jobs:
rm -rf ./site
mkdir ./site
- name: Download Artifact Docs
- uses: dawidd6/action-download-artifact@v2.24.2
+ uses: dawidd6/action-download-artifact@v2.24.3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
workflow: build-docs.yml
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 8ffb493a4f..c2fdb8e17f 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -31,7 +31,7 @@ jobs:
- name: Build distribution
run: python -m build
- name: Publish
- uses: pypa/gh-action-pypi-publish@v1.5.2
+ uses: pypa/gh-action-pypi-publish@v1.6.4
with:
password: ${{ secrets.PYPI_API_TOKEN }}
- name: Dump GitHub context
diff --git a/.github/workflows/smokeshow.yml b/.github/workflows/smokeshow.yml
index 7559c24c06..d6206d697b 100644
--- a/.github/workflows/smokeshow.yml
+++ b/.github/workflows/smokeshow.yml
@@ -20,7 +20,7 @@ jobs:
- run: pip install smokeshow
- - uses: dawidd6/action-download-artifact@v2.24.2
+ - uses: dawidd6/action-download-artifact@v2.24.3
with:
workflow: test.yml
commit: ${{ github.event.workflow_run.head_sha }}
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index ddc43c942b..1235516d33 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -75,3 +75,19 @@ jobs:
with:
name: coverage-html
path: htmlcov
+
+ # https://github.com/marketplace/actions/alls-green#why
+ check: # This job does nothing and is only used for the branch protection
+
+ if: always()
+
+ needs:
+ - coverage-combine
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Decide whether the needed jobs succeeded or failed
+ uses: re-actors/alls-green@release/v1
+ with:
+ jobs: ${{ toJSON(needs) }}
diff --git a/README.md b/README.md
index 7c4a6c4b4f..2b4de2a651 100644
--- a/README.md
+++ b/README.md
@@ -49,7 +49,6 @@ The key features are:
-
@@ -57,6 +56,7 @@ The key features are:
+
diff --git a/docs/en/data/external_links.yml b/docs/en/data/external_links.yml
index 934c5842b3..c1b1f1fa49 100644
--- a/docs/en/data/external_links.yml
+++ b/docs/en/data/external_links.yml
@@ -1,5 +1,13 @@
articles:
english:
+ - author: Raf Rasenberg
+ author_link: https://rafrasenberg.com/about/
+ link: https://rafrasenberg.com/fastapi-lambda/
+ title: 'FastAPI lambda container: serverless simplified'
+ - author: Teresa N. Fontanella De Santis
+ author_link: https://dev.to/
+ link: https://dev.to/teresafds/authorization-on-fastapi-with-casbin-41og
+ title: Authorization on FastAPI with Casbin
- author: WayScript
author_link: https://www.wayscript.com
link: https://blog.wayscript.com/fast-api-quickstart/
diff --git a/docs/en/data/github_sponsors.yml b/docs/en/data/github_sponsors.yml
index 1953df801e..3d6831db61 100644
--- a/docs/en/data/github_sponsors.yml
+++ b/docs/en/data/github_sponsors.yml
@@ -2,15 +2,9 @@ sponsors:
- - login: jina-ai
avatarUrl: https://avatars.githubusercontent.com/u/60539444?v=4
url: https://github.com/jina-ai
-- - login: Doist
- avatarUrl: https://avatars.githubusercontent.com/u/2565372?v=4
- url: https://github.com/Doist
- - login: cryptapi
+- - login: cryptapi
avatarUrl: https://avatars.githubusercontent.com/u/44925437?u=61369138589bc7fee6c417f3fbd50fbd38286cc4&v=4
url: https://github.com/cryptapi
- - login: jrobbins-LiveData
- avatarUrl: https://avatars.githubusercontent.com/u/79278744?u=bae8175fc3f09db281aca1f97a9ddc1a914a8c4f&v=4
- url: https://github.com/jrobbins-LiveData
- - login: nihpo
avatarUrl: https://avatars.githubusercontent.com/u/1841030?u=0264956d7580f7e46687a762a7baa629f84cf97c&v=4
url: https://github.com/nihpo
@@ -32,24 +26,21 @@ sponsors:
- login: investsuite
avatarUrl: https://avatars.githubusercontent.com/u/73833632?v=4
url: https://github.com/investsuite
+ - login: svix
+ avatarUrl: https://avatars.githubusercontent.com/u/80175132?v=4
+ url: https://github.com/svix
- login: VincentParedes
avatarUrl: https://avatars.githubusercontent.com/u/103889729?v=4
url: https://github.com/VincentParedes
- - login: getsentry
avatarUrl: https://avatars.githubusercontent.com/u/1396951?v=4
url: https://github.com/getsentry
-- - login: InesIvanova
- avatarUrl: https://avatars.githubusercontent.com/u/22920417?u=409882ec1df6dbd77455788bb383a8de223dbf6f&v=4
- url: https://github.com/InesIvanova
- - login: vyos
avatarUrl: https://avatars.githubusercontent.com/u/5647000?v=4
url: https://github.com/vyos
- login: SendCloud
avatarUrl: https://avatars.githubusercontent.com/u/7831959?v=4
url: https://github.com/SendCloud
- - login: matallan
- avatarUrl: https://avatars.githubusercontent.com/u/12107723?v=4
- url: https://github.com/matallan
- login: takashi-yoneya
avatarUrl: https://avatars.githubusercontent.com/u/33813153?u=2d0522bceba0b8b69adf1f2db866503bd96f944e&v=4
url: https://github.com/takashi-yoneya
@@ -65,12 +56,12 @@ sponsors:
- login: BoostryJP
avatarUrl: https://avatars.githubusercontent.com/u/57932412?v=4
url: https://github.com/BoostryJP
+- - login: InesIvanova
+ avatarUrl: https://avatars.githubusercontent.com/u/22920417?u=409882ec1df6dbd77455788bb383a8de223dbf6f&v=4
+ url: https://github.com/InesIvanova
- - login: johnadjei
avatarUrl: https://avatars.githubusercontent.com/u/767860?v=4
url: https://github.com/johnadjei
- - login: gvisniuc
- avatarUrl: https://avatars.githubusercontent.com/u/1614747?u=502dfdb2b087ddcf5460026297c98c7907bc2795&v=4
- url: https://github.com/gvisniuc
- login: HiredScore
avatarUrl: https://avatars.githubusercontent.com/u/3908850?v=4
url: https://github.com/HiredScore
@@ -80,6 +71,9 @@ sponsors:
- login: Lovage-Labs
avatarUrl: https://avatars.githubusercontent.com/u/71685552?v=4
url: https://github.com/Lovage-Labs
+- - login: xshapira
+ avatarUrl: https://avatars.githubusercontent.com/u/48856190?u=3b0927ad29addab29a43767b52e45bee5cd6da9f&v=4
+ url: https://github.com/xshapira
- - login: moellenbeck
avatarUrl: https://avatars.githubusercontent.com/u/169372?v=4
url: https://github.com/moellenbeck
@@ -95,6 +89,9 @@ sponsors:
- login: dorianturba
avatarUrl: https://avatars.githubusercontent.com/u/9381120?u=4bfc7032a824d1ed1994aa8256dfa597c8f187ad&v=4
url: https://github.com/dorianturba
+ - login: Qazzquimby
+ avatarUrl: https://avatars.githubusercontent.com/u/12368310?u=f4ed4a7167fd359cfe4502d56d7c64f9bf59bb38&v=4
+ url: https://github.com/Qazzquimby
- login: jmaralc
avatarUrl: https://avatars.githubusercontent.com/u/21101214?u=b15a9f07b7cbf6c9dcdbcb6550bbd2c52f55aa50&v=4
url: https://github.com/jmaralc
@@ -107,12 +104,15 @@ sponsors:
- login: primer-io
avatarUrl: https://avatars.githubusercontent.com/u/62146168?v=4
url: https://github.com/primer-io
-- - login: indeedeng
+- - login: guivaloz
+ avatarUrl: https://avatars.githubusercontent.com/u/1296621?u=bc4fc28f96c654aa2be7be051d03a315951e2491&v=4
+ url: https://github.com/guivaloz
+ - login: indeedeng
avatarUrl: https://avatars.githubusercontent.com/u/2905043?v=4
url: https://github.com/indeedeng
- - login: A-Edge
- avatarUrl: https://avatars.githubusercontent.com/u/59514131?v=4
- url: https://github.com/A-Edge
+ - login: fratambot
+ avatarUrl: https://avatars.githubusercontent.com/u/20300069?u=41c85ea08960c8a8f0ce967b780e242b1454690c&v=4
+ url: https://github.com/fratambot
- - login: Kludex
avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=62adc405ef418f4b6c8caa93d3eb8ab107bc4927&v=4
url: https://github.com/Kludex
@@ -152,9 +152,6 @@ sponsors:
- login: jqueguiner
avatarUrl: https://avatars.githubusercontent.com/u/690878?u=bd65cc1f228ce6455e56dfaca3ef47c33bc7c3b0&v=4
url: https://github.com/jqueguiner
- - login: alexsantos
- avatarUrl: https://avatars.githubusercontent.com/u/932219?v=4
- url: https://github.com/alexsantos
- login: tcsmith
avatarUrl: https://avatars.githubusercontent.com/u/989034?u=7d8d741552b3279e8f4d3878679823a705a46f8f&v=4
url: https://github.com/tcsmith
@@ -164,6 +161,9 @@ sponsors:
- login: mrkmcknz
avatarUrl: https://avatars.githubusercontent.com/u/1089376?u=2b9b8a8c25c33a4f6c220095638bd821cdfd13a3&v=4
url: https://github.com/mrkmcknz
+ - login: theonlynexus
+ avatarUrl: https://avatars.githubusercontent.com/u/1515004?v=4
+ url: https://github.com/theonlynexus
- login: coffeewasmyidea
avatarUrl: https://avatars.githubusercontent.com/u/1636488?u=8e32a4f200eff54dd79cd79d55d254bfce5e946d&v=4
url: https://github.com/coffeewasmyidea
@@ -185,9 +185,6 @@ sponsors:
- login: ColliotL
avatarUrl: https://avatars.githubusercontent.com/u/3412402?u=ca64b07ecbef2f9da1cc2cac3f37522aa4814902&v=4
url: https://github.com/ColliotL
- - login: grillazz
- avatarUrl: https://avatars.githubusercontent.com/u/3415861?u=453cd1725c8d7fe3e258016bc19cff861d4fcb53&v=4
- url: https://github.com/grillazz
- login: dblackrun
avatarUrl: https://avatars.githubusercontent.com/u/3528486?v=4
url: https://github.com/dblackrun
@@ -218,12 +215,6 @@ sponsors:
- login: oliverxchen
avatarUrl: https://avatars.githubusercontent.com/u/4471774?u=534191f25e32eeaadda22dfab4b0a428733d5489&v=4
url: https://github.com/oliverxchen
- - login: Rey8d01
- avatarUrl: https://avatars.githubusercontent.com/u/4836190?u=5942598a23a377602c1669522334ab5ebeaf9165&v=4
- url: https://github.com/Rey8d01
- - login: ScrimForever
- avatarUrl: https://avatars.githubusercontent.com/u/5040124?u=091ec38bfe16d6e762099e91309b59f248616a65&v=4
- url: https://github.com/ScrimForever
- login: ennui93
avatarUrl: https://avatars.githubusercontent.com/u/5300907?u=5b5452725ddb391b2caaebf34e05aba873591c3a&v=4
url: https://github.com/ennui93
@@ -257,9 +248,9 @@ sponsors:
- login: wdwinslow
avatarUrl: https://avatars.githubusercontent.com/u/11562137?u=dc01daafb354135603a263729e3d26d939c0c452&v=4
url: https://github.com/wdwinslow
- - login: bapi24
- avatarUrl: https://avatars.githubusercontent.com/u/11890901?u=45cc721d8f66ad2f62b086afc3d4761d0c16b9c6&v=4
- url: https://github.com/bapi24
+ - login: jacobkrit
+ avatarUrl: https://avatars.githubusercontent.com/u/11823915?u=4921a7ea429b7eadcad3077f119f90d15a3318b2&v=4
+ url: https://github.com/jacobkrit
- login: svats2k
avatarUrl: https://avatars.githubusercontent.com/u/12378398?u=ecf28c19f61052e664bdfeb2391f8107d137915c&v=4
url: https://github.com/svats2k
@@ -278,11 +269,8 @@ sponsors:
- login: wedwardbeck
avatarUrl: https://avatars.githubusercontent.com/u/19333237?u=1de4ae2bf8d59eb4c013f21d863cbe0f2010575f&v=4
url: https://github.com/wedwardbeck
- - login: m4knV
- avatarUrl: https://avatars.githubusercontent.com/u/19666130?u=843383978814886be93c137d10d2e20e9f13af07&v=4
- url: https://github.com/m4knV
- login: Filimoa
- avatarUrl: https://avatars.githubusercontent.com/u/21352040?u=75e02d102d2ee3e3d793e555fa5c63045913ccb0&v=4
+ avatarUrl: https://avatars.githubusercontent.com/u/21352040?u=0be845711495bbd7b756e13fcaeb8efc1ebd78ba&v=4
url: https://github.com/Filimoa
- login: shuheng-liu
avatarUrl: https://avatars.githubusercontent.com/u/22414322?u=813c45f30786c6b511b21a661def025d8f7b609e&v=4
@@ -317,9 +305,6 @@ sponsors:
- login: ProteinQure
avatarUrl: https://avatars.githubusercontent.com/u/33707203?v=4
url: https://github.com/ProteinQure
- - login: faviasono
- avatarUrl: https://avatars.githubusercontent.com/u/37707874?u=f0b75ca4248987c08ed8fb8ed682e7e74d5d7091&v=4
- url: https://github.com/faviasono
- login: ybressler
avatarUrl: https://avatars.githubusercontent.com/u/40807730?u=41e2c00f1eebe3c402635f0325e41b4e6511462c&v=4
url: https://github.com/ybressler
@@ -341,6 +326,9 @@ sponsors:
- login: dazeddd
avatarUrl: https://avatars.githubusercontent.com/u/59472056?u=7a1b668449bf8b448db13e4c575576d24d7d658b&v=4
url: https://github.com/dazeddd
+ - login: A-Edge
+ avatarUrl: https://avatars.githubusercontent.com/u/59514131?v=4
+ url: https://github.com/A-Edge
- login: yakkonaut
avatarUrl: https://avatars.githubusercontent.com/u/60633704?u=90a71fd631aa998ba4a96480788f017c9904e07b&v=4
url: https://github.com/yakkonaut
@@ -359,9 +347,6 @@ sponsors:
- login: anthonycepeda
avatarUrl: https://avatars.githubusercontent.com/u/72019805?u=4252c6b6dc5024af502a823a3ac5e7a03a69963f&v=4
url: https://github.com/anthonycepeda
- - login: fpiem
- avatarUrl: https://avatars.githubusercontent.com/u/77389987?u=f667a25cd4832b28801189013b74450e06cc232c&v=4
- url: https://github.com/fpiem
- login: DelfinaCare
avatarUrl: https://avatars.githubusercontent.com/u/83734439?v=4
url: https://github.com/DelfinaCare
@@ -404,9 +389,6 @@ sponsors:
- login: dmig
avatarUrl: https://avatars.githubusercontent.com/u/388564?v=4
url: https://github.com/dmig
- - login: rinckd
- avatarUrl: https://avatars.githubusercontent.com/u/546002?u=8ec88ab721a5636346f19dcd677a6f323058be8b&v=4
- url: https://github.com/rinckd
- login: securancy
avatarUrl: https://avatars.githubusercontent.com/u/606673?v=4
url: https://github.com/securancy
@@ -431,6 +413,9 @@ sponsors:
- login: WillHogan
avatarUrl: https://avatars.githubusercontent.com/u/1661551?u=7036c064cf29781470573865264ec8e60b6b809f&v=4
url: https://github.com/WillHogan
+ - login: my3
+ avatarUrl: https://avatars.githubusercontent.com/u/1825270?v=4
+ url: https://github.com/my3
- login: cbonoz
avatarUrl: https://avatars.githubusercontent.com/u/2351087?u=fd3e8030b2cc9fbfbb54a65e9890c548a016f58b&v=4
url: https://github.com/cbonoz
@@ -443,6 +428,9 @@ sponsors:
- login: igorcorrea
avatarUrl: https://avatars.githubusercontent.com/u/3438238?u=c57605077c31a8f7b2341fc4912507f91b4a5621&v=4
url: https://github.com/igorcorrea
+ - login: larsvik
+ avatarUrl: https://avatars.githubusercontent.com/u/3442226?v=4
+ url: https://github.com/larsvik
- login: anthonycorletti
avatarUrl: https://avatars.githubusercontent.com/u/3477132?v=4
url: https://github.com/anthonycorletti
@@ -519,7 +507,7 @@ sponsors:
avatarUrl: https://avatars.githubusercontent.com/u/18649504?u=d8a6ac40321f2bded0eba78b637751c7f86c6823&v=4
url: https://github.com/paulowiz
- login: yannicschroeer
- avatarUrl: https://avatars.githubusercontent.com/u/22749683?u=4df05a7296c207b91c5d7c7a11c29df5ab313e2b&v=4
+ avatarUrl: https://avatars.githubusercontent.com/u/22749683?u=91515328b5418a4e7289a83f0dcec3573f1a6500&v=4
url: https://github.com/yannicschroeer
- login: ghandic
avatarUrl: https://avatars.githubusercontent.com/u/23500353?u=e2e1d736f924d9be81e8bfc565b6d8836ba99773&v=4
@@ -528,7 +516,7 @@ sponsors:
avatarUrl: https://avatars.githubusercontent.com/u/24669867?u=60e7c8c09f8dafabee8fc3edcd6f9e19abbff918&v=4
url: https://github.com/fstau
- login: pers0n4
- avatarUrl: https://avatars.githubusercontent.com/u/24864600?u=444441027bc2c9f9db68e8047d65ff23d25699cf&v=4
+ avatarUrl: https://avatars.githubusercontent.com/u/24864600?u=7e5d2bf26d0a0670ea347f7db5febebc4e031ed1&v=4
url: https://github.com/pers0n4
- login: SebTota
avatarUrl: https://avatars.githubusercontent.com/u/25122511?v=4
@@ -564,7 +552,7 @@ sponsors:
avatarUrl: https://avatars.githubusercontent.com/u/38921751?u=ae14bc1e40f2dd5a9c5741fc0b0dffbd416a5fa9&v=4
url: https://github.com/ww-daniel-mora
- login: rwxd
- avatarUrl: https://avatars.githubusercontent.com/u/40308458?u=9ddf8023ca3326381ba8fb77285ae36598a15de3&v=4
+ avatarUrl: https://avatars.githubusercontent.com/u/40308458?u=b3cb7a606207141c357e517071cf91a67fb5577e&v=4
url: https://github.com/rwxd
- login: ilias-ant
avatarUrl: https://avatars.githubusercontent.com/u/42189572?u=a2d6121bac4d125d92ec207460fa3f1842d37e66&v=4
@@ -602,16 +590,16 @@ sponsors:
- login: realabja
avatarUrl: https://avatars.githubusercontent.com/u/66185192?u=001e2dd9297784f4218997981b4e6fa8357bb70b&v=4
url: https://github.com/realabja
- - login: alessio-proietti
- avatarUrl: https://avatars.githubusercontent.com/u/67370599?u=8ac73db1e18e946a7681f173abdb640516f88515&v=4
- url: https://github.com/alessio-proietti
- login: pondDevThai
avatarUrl: https://avatars.githubusercontent.com/u/71592181?u=08af9a59bccfd8f6b101de1005aa9822007d0a44&v=4
url: https://github.com/pondDevThai
-- - login: wardal
- avatarUrl: https://avatars.githubusercontent.com/u/15804042?v=4
- url: https://github.com/wardal
- - login: gabrielmbmb
+ - login: zeb0x01
+ avatarUrl: https://avatars.githubusercontent.com/u/77236545?u=c62bfcfbd463f9cf171c879cea1362a63de2c582&v=4
+ url: https://github.com/zeb0x01
+ - login: lironmiz
+ avatarUrl: https://avatars.githubusercontent.com/u/91504420?u=cb93dfec613911ac8d4e84fbb560711546711ad5&v=4
+ url: https://github.com/lironmiz
+- - login: gabrielmbmb
avatarUrl: https://avatars.githubusercontent.com/u/29572918?u=6d1e00b5d558e96718312ff910a2318f47cc3145&v=4
url: https://github.com/gabrielmbmb
- login: danburonline
@@ -620,3 +608,6 @@ sponsors:
- login: Moises6669
avatarUrl: https://avatars.githubusercontent.com/u/66188523?u=96af25b8d5be9f983cb96e9dd7c605c716caf1f5&v=4
url: https://github.com/Moises6669
+ - login: lyuboparvanov
+ avatarUrl: https://avatars.githubusercontent.com/u/106192895?u=367329c777320e01550afda9d8de670436181d86&v=4
+ url: https://github.com/lyuboparvanov
diff --git a/docs/en/data/people.yml b/docs/en/data/people.yml
index 51940a6b13..d46ec44ae4 100644
--- a/docs/en/data/people.yml
+++ b/docs/en/data/people.yml
@@ -1,12 +1,12 @@
maintainers:
- login: tiangolo
- answers: 1837
- prs: 360
+ answers: 1878
+ prs: 361
avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=740f11212a731f56798f558ceddb0bd07642afa7&v=4
url: https://github.com/tiangolo
experts:
- login: Kludex
- count: 374
+ count: 379
avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=62adc405ef418f4b6c8caa93d3eb8ab107bc4927&v=4
url: https://github.com/Kludex
- login: dmontagu
@@ -34,7 +34,7 @@ experts:
avatarUrl: https://avatars.githubusercontent.com/u/331403?v=4
url: https://github.com/phy25
- login: iudeen
- count: 87
+ count: 103
avatarUrl: https://avatars.githubusercontent.com/u/10519440?u=2843b3303282bff8b212dcd4d9d6689452e4470c&v=4
url: https://github.com/iudeen
- login: raphaelauv
@@ -45,14 +45,14 @@ experts:
count: 71
avatarUrl: https://avatars.githubusercontent.com/u/31127044?u=b0f2c37142f4b762e41ad65dc49581813422bd71&v=4
url: https://github.com/ArcLightSlavik
+- login: jgould22
+ count: 68
+ avatarUrl: https://avatars.githubusercontent.com/u/4335847?u=ed77f67e0bb069084639b24d812dbb2a2b1dc554&v=4
+ url: https://github.com/jgould22
- login: falkben
count: 59
avatarUrl: https://avatars.githubusercontent.com/u/653031?u=0c8d8f33d87f1aa1a6488d3f02105e9abc838105&v=4
url: https://github.com/falkben
-- login: jgould22
- count: 55
- avatarUrl: https://avatars.githubusercontent.com/u/4335847?u=ed77f67e0bb069084639b24d812dbb2a2b1dc554&v=4
- url: https://github.com/jgould22
- login: sm-Fifteen
count: 50
avatarUrl: https://avatars.githubusercontent.com/u/516999?u=437c0c5038558c67e887ccd863c1ba0f846c03da&v=4
@@ -66,8 +66,8 @@ experts:
avatarUrl: https://avatars.githubusercontent.com/u/27180793?u=5cf2877f50b3eb2bc55086089a78a36f07042889&v=4
url: https://github.com/Dustyposa
- login: adriangb
- count: 40
- avatarUrl: https://avatars.githubusercontent.com/u/1755071?u=75087f0cf0e9f725f3cd18a899218b6c63ae60d3&v=4
+ count: 41
+ avatarUrl: https://avatars.githubusercontent.com/u/1755071?u=1e2c2c9b39f5c9b780fb933d8995cf08ec235a47&v=4
url: https://github.com/adriangb
- login: includeamin
count: 39
@@ -82,7 +82,7 @@ experts:
avatarUrl: https://avatars.githubusercontent.com/u/7534547?v=4
url: https://github.com/chbndrhnns
- login: frankie567
- count: 33
+ count: 34
avatarUrl: https://avatars.githubusercontent.com/u/1144727?u=85c025e3fcc7bd79a5665c63ee87cdf8aae13374&v=4
url: https://github.com/frankie567
- login: prostomarkeloff
@@ -97,14 +97,14 @@ experts:
count: 31
avatarUrl: https://avatars.githubusercontent.com/u/31960541?u=47f4829c77f4962ab437ffb7995951e41eeebe9b&v=4
url: https://github.com/krishnardt
+- login: panla
+ count: 30
+ avatarUrl: https://avatars.githubusercontent.com/u/41326348?u=ba2fda6b30110411ecbf406d187907e2b420ac19&v=4
+ url: https://github.com/panla
- login: wshayes
count: 29
avatarUrl: https://avatars.githubusercontent.com/u/365303?u=07ca03c5ee811eb0920e633cc3c3db73dbec1aa5&v=4
url: https://github.com/wshayes
-- login: panla
- count: 29
- avatarUrl: https://avatars.githubusercontent.com/u/41326348?u=ba2fda6b30110411ecbf406d187907e2b420ac19&v=4
- url: https://github.com/panla
- login: ghandic
count: 25
avatarUrl: https://avatars.githubusercontent.com/u/23500353?u=e2e1d736f924d9be81e8bfc565b6d8836ba99773&v=4
@@ -169,6 +169,10 @@ experts:
count: 16
avatarUrl: https://avatars.githubusercontent.com/u/41964673?u=9f2174f9d61c15c6e3a4c9e3aeee66f711ce311f&v=4
url: https://github.com/dstlny
+- login: hellocoldworld
+ count: 15
+ avatarUrl: https://avatars.githubusercontent.com/u/47581948?u=3d2186796434c507a6cb6de35189ab0ad27c356f&v=4
+ url: https://github.com/hellocoldworld
- login: jonatasoli
count: 15
avatarUrl: https://avatars.githubusercontent.com/u/26334101?u=071c062d2861d3dd127f6b4a5258cd8ef55d4c50&v=4
@@ -177,14 +181,14 @@ experts:
count: 15
avatarUrl: https://avatars.githubusercontent.com/u/50829834?u=a48610bf1bffaa9c75d03228926e2eb08a2e24ee&v=4
url: https://github.com/mbroton
-- login: hellocoldworld
- count: 14
- avatarUrl: https://avatars.githubusercontent.com/u/47581948?u=3d2186796434c507a6cb6de35189ab0ad27c356f&v=4
- url: https://github.com/hellocoldworld
- login: haizaar
count: 13
avatarUrl: https://avatars.githubusercontent.com/u/58201?u=dd40d99a3e1935d0b768f122bfe2258d6ea53b2b&v=4
url: https://github.com/haizaar
+- login: n8sty
+ count: 13
+ avatarUrl: https://avatars.githubusercontent.com/u/2964996?v=4
+ url: https://github.com/n8sty
- login: valentin994
count: 13
avatarUrl: https://avatars.githubusercontent.com/u/42819267?u=fdeeaa9242a59b243f8603496b00994f6951d5a2&v=4
@@ -193,43 +197,31 @@ experts:
count: 12
avatarUrl: https://avatars.githubusercontent.com/u/17401854?u=474680c02b94cba810cb9032fb7eb787d9cc9d22&v=4
url: https://github.com/David-Lor
-- login: n8sty
- count: 12
- avatarUrl: https://avatars.githubusercontent.com/u/2964996?v=4
- url: https://github.com/n8sty
last_month_active:
-- login: jgould22
- count: 9
- avatarUrl: https://avatars.githubusercontent.com/u/4335847?u=ed77f67e0bb069084639b24d812dbb2a2b1dc554&v=4
- url: https://github.com/jgould22
-- login: yinziyan1206
- count: 9
- avatarUrl: https://avatars.githubusercontent.com/u/37829370?u=da44ca53aefd5c23f346fab8e9fd2e108294c179&v=4
- url: https://github.com/yinziyan1206
- login: iudeen
- count: 8
+ count: 16
avatarUrl: https://avatars.githubusercontent.com/u/10519440?u=2843b3303282bff8b212dcd4d9d6689452e4470c&v=4
url: https://github.com/iudeen
+- login: jgould22
+ count: 13
+ avatarUrl: https://avatars.githubusercontent.com/u/4335847?u=ed77f67e0bb069084639b24d812dbb2a2b1dc554&v=4
+ url: https://github.com/jgould22
+- login: NewSouthMjos
+ count: 4
+ avatarUrl: https://avatars.githubusercontent.com/u/77476573?v=4
+ url: https://github.com/NewSouthMjos
+- login: davismartens
+ count: 3
+ avatarUrl: https://avatars.githubusercontent.com/u/69799848?u=dbdfd256dd4e0a12d93efb3463225f3e39d8df6f&v=4
+ url: https://github.com/davismartens
- login: Kludex
- count: 5
+ count: 3
avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=62adc405ef418f4b6c8caa93d3eb8ab107bc4927&v=4
url: https://github.com/Kludex
-- login: JarroVGIT
- count: 5
- avatarUrl: https://avatars.githubusercontent.com/u/13659033?u=e8bea32d07a5ef72f7dde3b2079ceb714923ca05&v=4
- url: https://github.com/JarroVGIT
-- login: TheJumpyWizard
- count: 4
- avatarUrl: https://avatars.githubusercontent.com/u/90986815?u=67e9c13c9f063dd4313db7beb64eaa2f3a37f1fe&v=4
- url: https://github.com/TheJumpyWizard
-- login: mbroton
+- login: Lenclove
count: 3
- avatarUrl: https://avatars.githubusercontent.com/u/50829834?u=a48610bf1bffaa9c75d03228926e2eb08a2e24ee&v=4
- url: https://github.com/mbroton
-- login: mateoradman
- count: 3
- avatarUrl: https://avatars.githubusercontent.com/u/48420316?u=066f36b8e8e263b0d90798113b0f291d3266db7c&v=4
- url: https://github.com/mateoradman
+ avatarUrl: https://avatars.githubusercontent.com/u/32355298?u=d0065e01650c63c2b2413f42d983634b2ea85481&v=4
+ url: https://github.com/Lenclove
top_contributors:
- login: waynerv
count: 25
@@ -269,7 +261,7 @@ top_contributors:
url: https://github.com/Serrones
- login: RunningIkkyu
count: 7
- avatarUrl: https://avatars.githubusercontent.com/u/31848542?u=efb5b45b55584450507834f279ce48d4d64dea2f&v=4
+ avatarUrl: https://avatars.githubusercontent.com/u/31848542?u=494ecc298e3f26197495bb357ad0f57cfd5f7a32&v=4
url: https://github.com/RunningIkkyu
- login: hard-coders
count: 7
@@ -337,15 +329,15 @@ top_reviewers:
avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=62adc405ef418f4b6c8caa93d3eb8ab107bc4927&v=4
url: https://github.com/Kludex
- login: BilalAlpaslan
- count: 70
+ count: 72
avatarUrl: https://avatars.githubusercontent.com/u/47563997?u=63ed66e304fe8d765762c70587d61d9196e5c82d&v=4
url: https://github.com/BilalAlpaslan
- login: yezz123
- count: 59
+ count: 66
avatarUrl: https://avatars.githubusercontent.com/u/52716203?u=636b4f79645176df4527dd45c12d5dbb5a4193cf&v=4
url: https://github.com/yezz123
- login: tokusumi
- count: 50
+ count: 51
avatarUrl: https://avatars.githubusercontent.com/u/41147016?u=55010621aece725aa702270b54fed829b6a1fe60&v=4
url: https://github.com/tokusumi
- login: waynerv
@@ -360,6 +352,10 @@ top_reviewers:
count: 45
avatarUrl: https://avatars.githubusercontent.com/u/62724709?u=bba5af018423a2858d49309bed2a899bb5c34ac5&v=4
url: https://github.com/ycd
+- login: iudeen
+ count: 42
+ avatarUrl: https://avatars.githubusercontent.com/u/10519440?u=2843b3303282bff8b212dcd4d9d6689452e4470c&v=4
+ url: https://github.com/iudeen
- login: cikay
count: 41
avatarUrl: https://avatars.githubusercontent.com/u/24587499?u=e772190a051ab0eaa9c8542fcff1892471638f2b&v=4
@@ -372,10 +368,6 @@ top_reviewers:
count: 33
avatarUrl: https://avatars.githubusercontent.com/u/1024932?u=b2ea249c6b41ddf98679c8d110d0f67d4a3ebf93&v=4
url: https://github.com/AdrianDeAnda
-- login: iudeen
- count: 33
- avatarUrl: https://avatars.githubusercontent.com/u/10519440?u=2843b3303282bff8b212dcd4d9d6689452e4470c&v=4
- url: https://github.com/iudeen
- login: ArcLightSlavik
count: 31
avatarUrl: https://avatars.githubusercontent.com/u/31127044?u=b0f2c37142f4b762e41ad65dc49581813422bd71&v=4
@@ -446,7 +438,7 @@ top_reviewers:
url: https://github.com/sh0nk
- login: RunningIkkyu
count: 12
- avatarUrl: https://avatars.githubusercontent.com/u/31848542?u=efb5b45b55584450507834f279ce48d4d64dea2f&v=4
+ avatarUrl: https://avatars.githubusercontent.com/u/31848542?u=494ecc298e3f26197495bb357ad0f57cfd5f7a32&v=4
url: https://github.com/RunningIkkyu
- login: LorhanSohaky
count: 11
@@ -474,7 +466,7 @@ top_reviewers:
url: https://github.com/ComicShrimp
- login: peidrao
count: 10
- avatarUrl: https://avatars.githubusercontent.com/u/32584628?u=88c2cb42a99e0f50cdeae3606992568184783ee5&v=4
+ avatarUrl: https://avatars.githubusercontent.com/u/32584628?u=39edf7052371484cb488277638c23e1f6b584f4b&v=4
url: https://github.com/peidrao
- login: izaguerreiro
count: 9
@@ -496,6 +488,10 @@ top_reviewers:
count: 9
avatarUrl: https://avatars.githubusercontent.com/u/69092910?u=4ac58eab99bd37d663f3d23551df96d4fbdbf760&v=4
url: https://github.com/bezaca
+- login: dimaqq
+ count: 9
+ avatarUrl: https://avatars.githubusercontent.com/u/662249?v=4
+ url: https://github.com/dimaqq
- login: raphaelauv
count: 8
avatarUrl: https://avatars.githubusercontent.com/u/10202690?u=e6f86f5c0c3026a15d6b51792fa3e532b12f1371&v=4
@@ -512,10 +508,6 @@ top_reviewers:
count: 8
avatarUrl: https://avatars.githubusercontent.com/u/79563565?u=1741703bd6c8f491503354b363a86e879b4c1cab&v=4
url: https://github.com/NinaHwang
-- login: dimaqq
- count: 8
- avatarUrl: https://avatars.githubusercontent.com/u/662249?v=4
- url: https://github.com/dimaqq
- login: Xewus
count: 8
avatarUrl: https://avatars.githubusercontent.com/u/85196001?u=4bdd4a0300530a504987db27488ba79c37f2fb18&v=4
diff --git a/docs/en/data/sponsors.yml b/docs/en/data/sponsors.yml
index 749f528c51..c39dbb589f 100644
--- a/docs/en/data/sponsors.yml
+++ b/docs/en/data/sponsors.yml
@@ -8,9 +8,6 @@ gold:
- url: https://cryptapi.io/
title: "CryptAPI: Your easy to use, secure and privacy oriented payment gateway."
img: https://fastapi.tiangolo.com/img/sponsors/cryptapi.svg
- - url: https://doist.com/careers/9B437B1615-wa-senior-backend-engineer-python
- title: Help us migrate doist to FastAPI
- img: https://fastapi.tiangolo.com/img/sponsors/doist.svg
silver:
- url: https://www.deta.sh/?ref=fastapi
title: The launchpad for all your (team's) ideas
@@ -33,6 +30,9 @@ silver:
- url: https://careers.budget-insight.com/
title: Budget Insight is hiring!
img: https://fastapi.tiangolo.com/img/sponsors/budget-insight.svg
+ - url: https://www.svix.com/
+ title: Svix - Webhooks as a service
+ img: https://fastapi.tiangolo.com/img/sponsors/svix.svg
bronze:
- url: https://www.exoflare.com/open-source/?utm_source=FastAPI&utm_campaign=open_source
title: Biosecurity risk assessments made easy.
diff --git a/docs/en/docs/async.md b/docs/en/docs/async.md
index 6f34a9c9c5..3d4b1956af 100644
--- a/docs/en/docs/async.md
+++ b/docs/en/docs/async.md
@@ -283,7 +283,7 @@ For example:
### Concurrency + Parallelism: Web + Machine Learning
-With **FastAPI** you can take the advantage of concurrency that is very common for web development (the same main attractive of NodeJS).
+With **FastAPI** you can take the advantage of concurrency that is very common for web development (the same main attraction of NodeJS).
But you can also exploit the benefits of parallelism and multiprocessing (having multiple processes running in parallel) for **CPU bound** workloads like those in Machine Learning systems.
diff --git a/docs/en/docs/deployment/concepts.md b/docs/en/docs/deployment/concepts.md
index 22604ceeb2..77419f8b0d 100644
--- a/docs/en/docs/deployment/concepts.md
+++ b/docs/en/docs/deployment/concepts.md
@@ -235,7 +235,7 @@ Here are some possible combinations and strategies:
* One Uvicorn **process manager** would listen on the **IP** and **port**, and it would start **multiple Uvicorn worker processes**
* **Kubernetes** and other distributed **container systems**
* Something in the **Kubernetes** layer would listen on the **IP** and **port**. The replication would be by having **multiple containers**, each with **one Uvicorn process** running
-* **Cloud services** that handle this for your
+* **Cloud services** that handle this for you
* The cloud service will probably **handle replication for you**. It would possibly let you define **a process to run**, or a **container image** to use, in any case, it would most probably be **a single Uvicorn process**, and the cloud service would be in charge of replicating it.
!!! tip
diff --git a/docs/en/docs/img/sponsors/svix.svg b/docs/en/docs/img/sponsors/svix.svg
new file mode 100644
index 0000000000..845a860a22
--- /dev/null
+++ b/docs/en/docs/img/sponsors/svix.svg
@@ -0,0 +1,178 @@
+
+
diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md
index 1d574906bf..eab3d56f49 100644
--- a/docs/en/docs/release-notes.md
+++ b/docs/en/docs/release-notes.md
@@ -2,6 +2,84 @@
## Latest Changes
+
+## 0.89.1
+
+### Fixes
+
+* 🐛 Ignore Response classes on return annotation. PR [#5855](https://github.com/tiangolo/fastapi/pull/5855) by [@Kludex](https://github.com/Kludex). See the new docs in the PR below.
+
+### Docs
+
+* 📝 Update docs and examples for Response Model with Return Type Annotations, and update runtime error. PR [#5873](https://github.com/tiangolo/fastapi/pull/5873) by [@tiangolo](https://github.com/tiangolo). New docs at [Response Model - Return Type: Other Return Type Annotations](https://fastapi.tiangolo.com/tutorial/response-model/#other-return-type-annotations).
+* 📝 Add External Link: FastAPI lambda container: serverless simplified. PR [#5784](https://github.com/tiangolo/fastapi/pull/5784) by [@rafrasenberg](https://github.com/rafrasenberg).
+
+### Translations
+
+* 🌐 Add Turkish translation for `docs/tr/docs/tutorial/first_steps.md`. PR [#5691](https://github.com/tiangolo/fastapi/pull/5691) by [@Kadermiyanyedi](https://github.com/Kadermiyanyedi).
+
+## 0.89.0
+
+### Features
+
+* ✨ Add support for function return type annotations to declare the `response_model`. Initial PR [#1436](https://github.com/tiangolo/fastapi/pull/1436) by [@uriyyo](https://github.com/uriyyo).
+
+Now you can declare the return type / `response_model` in the function return type annotation:
+
+```python
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ price: float
+
+
+@app.get("/items/")
+async def read_items() -> list[Item]:
+ return [
+ Item(name="Portal Gun", price=42.0),
+ Item(name="Plumbus", price=32.0),
+ ]
+```
+
+FastAPI will use the return type annotation to perform:
+
+* Data validation
+* Automatic documentation
+ * It could power automatic client generators
+* **Data filtering**
+
+Before this version it was only supported via the `response_model` parameter.
+
+Read more about it in the new docs: [Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/).
+
+### Docs
+
+* 📝 Add External Link: Authorization on FastAPI with Casbin. PR [#5712](https://github.com/tiangolo/fastapi/pull/5712) by [@Xhy-5000](https://github.com/Xhy-5000).
+* ✏ Fix typo in `docs/en/docs/async.md`. PR [#5785](https://github.com/tiangolo/fastapi/pull/5785) by [@Kingdageek](https://github.com/Kingdageek).
+* ✏ Fix typo in `docs/en/docs/deployment/concepts.md`. PR [#5824](https://github.com/tiangolo/fastapi/pull/5824) by [@kelbyfaessler](https://github.com/kelbyfaessler).
+
+### Translations
+
+* 🌐 Add Russian translation for `docs/ru/docs/fastapi-people.md`. PR [#5577](https://github.com/tiangolo/fastapi/pull/5577) by [@Xewus](https://github.com/Xewus).
+* 🌐 Fix typo in Chinese translation for `docs/zh/docs/benchmarks.md`. PR [#4269](https://github.com/tiangolo/fastapi/pull/4269) by [@15027668g](https://github.com/15027668g).
+* 🌐 Add Korean translation for `docs/tutorial/cors.md`. PR [#3764](https://github.com/tiangolo/fastapi/pull/3764) by [@NinaHwang](https://github.com/NinaHwang).
+
+### Internal
+
+* ⬆ Update coverage[toml] requirement from <7.0,>=6.5.0 to >=6.5.0,<8.0. PR [#5801](https://github.com/tiangolo/fastapi/pull/5801) by [@dependabot[bot]](https://github.com/apps/dependabot).
+* ⬆ Update uvicorn[standard] requirement from <0.19.0,>=0.12.0 to >=0.12.0,<0.21.0 for development. PR [#5795](https://github.com/tiangolo/fastapi/pull/5795) by [@dependabot[bot]](https://github.com/apps/dependabot).
+* ⬆ Bump dawidd6/action-download-artifact from 2.24.2 to 2.24.3. PR [#5842](https://github.com/tiangolo/fastapi/pull/5842) by [@dependabot[bot]](https://github.com/apps/dependabot).
+* 👥 Update FastAPI People. PR [#5825](https://github.com/tiangolo/fastapi/pull/5825) by [@github-actions[bot]](https://github.com/apps/github-actions).
+* ⬆ Bump types-ujson from 5.5.0 to 5.6.0.0. PR [#5735](https://github.com/tiangolo/fastapi/pull/5735) by [@dependabot[bot]](https://github.com/apps/dependabot).
+* ⬆ Bump pypa/gh-action-pypi-publish from 1.5.2 to 1.6.4. PR [#5750](https://github.com/tiangolo/fastapi/pull/5750) by [@dependabot[bot]](https://github.com/apps/dependabot).
+* 👷 Add GitHub Action gate/check. PR [#5492](https://github.com/tiangolo/fastapi/pull/5492) by [@webknjaz](https://github.com/webknjaz).
+* 🔧 Update sponsors, add Svix. PR [#5848](https://github.com/tiangolo/fastapi/pull/5848) by [@tiangolo](https://github.com/tiangolo).
+* 🔧 Remove Doist sponsor. PR [#5847](https://github.com/tiangolo/fastapi/pull/5847) by [@tiangolo](https://github.com/tiangolo).
* ⬆ Update sqlalchemy requirement from <=1.4.41,>=1.3.18 to >=1.3.18,<1.4.43. PR [#5540](https://github.com/tiangolo/fastapi/pull/5540) by [@dependabot[bot]](https://github.com/apps/dependabot).
* ⬆ Bump nwtgck/actions-netlify from 1.2.4 to 2.0.0. PR [#5757](https://github.com/tiangolo/fastapi/pull/5757) by [@dependabot[bot]](https://github.com/apps/dependabot).
* 👷 Refactor CI artifact upload/download for docs previews. PR [#5793](https://github.com/tiangolo/fastapi/pull/5793) by [@tiangolo](https://github.com/tiangolo).
diff --git a/docs/en/docs/tutorial/response-model.md b/docs/en/docs/tutorial/response-model.md
index ab68314e85..cd7a749d4d 100644
--- a/docs/en/docs/tutorial/response-model.md
+++ b/docs/en/docs/tutorial/response-model.md
@@ -1,6 +1,51 @@
-# Response Model
+# Response Model - Return Type
-You can declare the model used for the response with the parameter `response_model` in any of the *path operations*:
+You can declare the type used for the response by annotating the *path operation function* **return type**.
+
+You can use **type annotations** the same way you would for input data in function **parameters**, you can use Pydantic models, lists, dictionaries, scalar values like integers, booleans, etc.
+
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="18 23"
+ {!> ../../../docs_src/response_model/tutorial001_01.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="18 23"
+ {!> ../../../docs_src/response_model/tutorial001_01_py39.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="16 21"
+ {!> ../../../docs_src/response_model/tutorial001_01_py310.py!}
+ ```
+
+FastAPI will use this return type to:
+
+* **Validate** the returned data.
+ * If the data is invalid (e.g. you are missing a field), it means that *your* app code is broken, not returning what it should, and it will return a server error instead of returning incorrect data. This way you and your clients can be certain that they will receive the data and the data shape expected.
+* Add a **JSON Schema** for the response, in the OpenAPI *path operation*.
+ * This will be used by the **automatic docs**.
+ * It will also be used by automatic client code generation tools.
+
+But most importantly:
+
+* It will **limit and filter** the output data to what is defined in the return type.
+ * This is particularly important for **security**, we'll see more of that below.
+
+## `response_model` Parameter
+
+There are some cases where you need or want to return some data that is not exactly what the type declares.
+
+For example, you could want to **return a dictionary** or a database object, but **declare it as a Pydantic model**. This way the Pydantic model would do all the data documentation, validation, etc. for the object that you returned (e.g. a dictionary or database object).
+
+If you added the return type annotation, tools and editors would complain with a (correct) error telling you that your function is returning a type (e.g. a dict) that is different from what you declared (e.g. a Pydantic model).
+
+In those cases, you can use the *path operation decorator* parameter `response_model` instead of the return type.
+
+You can use the `response_model` parameter in any of the *path operations*:
* `@app.get()`
* `@app.post()`
@@ -10,40 +55,41 @@ You can declare the model used for the response with the parameter `response_mod
=== "Python 3.6 and above"
- ```Python hl_lines="17"
+ ```Python hl_lines="17 22 24-27"
{!> ../../../docs_src/response_model/tutorial001.py!}
```
=== "Python 3.9 and above"
- ```Python hl_lines="17"
+ ```Python hl_lines="17 22 24-27"
{!> ../../../docs_src/response_model/tutorial001_py39.py!}
```
=== "Python 3.10 and above"
- ```Python hl_lines="15"
+ ```Python hl_lines="17 22 24-27"
{!> ../../../docs_src/response_model/tutorial001_py310.py!}
```
!!! note
Notice that `response_model` is a parameter of the "decorator" method (`get`, `post`, etc). Not of your *path operation function*, like all the parameters and body.
-It receives the same type you would declare for a Pydantic model attribute, so, it can be a Pydantic model, but it can also be, e.g. a `list` of Pydantic models, like `List[Item]`.
+`response_model` receives the same type you would declare for a Pydantic model field, so, it can be a Pydantic model, but it can also be, e.g. a `list` of Pydantic models, like `List[Item]`.
-FastAPI will use this `response_model` to:
+FastAPI will use this `response_model` to do all the data documentation, validation, etc. and also to **convert and filter the output data** to its type declaration.
-* Convert the output data to its type declaration.
-* Validate the data.
-* Add a JSON Schema for the response, in the OpenAPI *path operation*.
-* Will be used by the automatic documentation systems.
+!!! tip
+ If you have strict type checks in your editor, mypy, etc, you can declare the function return type as `Any`.
-But most importantly:
+ That way you tell the editor that you are intentionally returning anything. But FastAPI will still do the data documentation, validation, filtering, etc. with the `response_model`.
-* Will limit the output data to that of the model. We'll see how that's important below.
+### `response_model` Priority
-!!! note "Technical Details"
- The response model is declared in this parameter instead of as a function return type annotation, because the path function may not actually return that response model but rather return a `dict`, database object or some other model, and then use the `response_model` to perform the field limiting and serialization.
+If you declare both a return type and a `response_model`, the `response_model` will take priority and be used by FastAPI.
+
+This way you can add correct type annotations to your functions even when you are returning a type different than the response model, to be used by the editor and tools like mypy. And still you can have FastAPI do the data validation, documentation, etc. using the `response_model`.
+
+You can also use `response_model=None` to disable creating a response model for that *path operation*, you might need to do it if you are adding type annotations for things that are not valid Pydantic fields, you will see an example of that in one of the sections below.
## Return the same input data
@@ -71,24 +117,24 @@ And we are using this model to declare our input and the same model to declare o
=== "Python 3.6 and above"
- ```Python hl_lines="17-18"
+ ```Python hl_lines="18"
{!> ../../../docs_src/response_model/tutorial002.py!}
```
=== "Python 3.10 and above"
- ```Python hl_lines="15-16"
+ ```Python hl_lines="16"
{!> ../../../docs_src/response_model/tutorial002_py310.py!}
```
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, because the user themself is sending the password.
+In this case, it might not be a problem, because it's the same user sending the password.
But if we use the same model for another *path operation*, we could be sending our user's passwords to every client.
!!! danger
- Never store the plain password of a user or send it in a response.
+ Never store the plain password of a user or send it in a response like this, unless you know all the caveats and you know what you are doing.
## Add an output model
@@ -102,7 +148,7 @@ We can instead create an input model with the plaintext password and an output m
=== "Python 3.10 and above"
- ```Python hl_lines="7 9 14"
+ ```Python hl_lines="9 11 16"
{!> ../../../docs_src/response_model/tutorial003_py310.py!}
```
@@ -116,7 +162,7 @@ Here, even though our *path operation function* is returning the same input user
=== "Python 3.10 and above"
- ```Python hl_lines="22"
+ ```Python hl_lines="24"
{!> ../../../docs_src/response_model/tutorial003_py310.py!}
```
@@ -130,12 +176,66 @@ Here, even though our *path operation function* is returning the same input user
=== "Python 3.10 and above"
- ```Python hl_lines="20"
+ ```Python hl_lines="22"
{!> ../../../docs_src/response_model/tutorial003_py310.py!}
```
So, **FastAPI** will take care of filtering out all the data that is not declared in the output model (using Pydantic).
+### `response_model` or Return Type
+
+In this case, because the two models are different, if we annotated the function return type as `UserOut`, the editor and tools would complain that we are returning an invalid type, as those are different classes.
+
+That's why in this example we have to declare it in the `response_model` parameter.
+
+...but continue reading below to see how to overcome that.
+
+## Return Type and Data Filtering
+
+Let's continue from the previous example. We wanted to **annotate the function with one type** but return something that includes **more data**.
+
+We want FastAPI to keep **filtering** the data using the response model.
+
+In the previous example, because the classes were different, we had to use the `response_model` parameter. But that also means that we don't get the support from the editor and tools checking the function return type.
+
+But in most of the cases where we need to do something like this, we want the model just to **filter/remove** some of the data as in this example.
+
+And in those cases, we can use classes and inheritance to take advantage of function **type annotations** to get better support in the editor and tools, and still get the FastAPI **data filtering**.
+
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="9-13 15-16 20"
+ {!> ../../../docs_src/response_model/tutorial003_01.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="7-10 13-14 18"
+ {!> ../../../docs_src/response_model/tutorial003_01_py310.py!}
+ ```
+
+With this, we get tooling support, from editors and mypy as this code is correct in terms of types, but we also get the data filtering from FastAPI.
+
+How does this work? Let's check that out. 🤓
+
+### Type Annotations and Tooling
+
+First let's see how editors, mypy and other tools would see this.
+
+`BaseUser` has the base fields. Then `UserIn` inherits from `BaseUser` and adds the `password` field, so, it will include all the fields from both models.
+
+We annotate the function return type as `BaseUser`, but we are actually returning a `UserIn` instance.
+
+The editor, mypy, and other tools won't complain about this because, in typing terms, `UserIn` is a subclass of `BaseUser`, which means it's a *valid* type when what is expected is anything that is a `BaseUser`.
+
+### FastAPI Data Filtering
+
+Now, for FastAPI, it will see the return type and make sure that what you return includes **only** the fields that are declared in the type.
+
+FastAPI does several things internally with Pydantic to make sure that those same rules of class inheritance are not used for the returned data filtering, otherwise you could end up returning much more data than what you expected.
+
+This way, you can get the best of both worlds: type annotations with **tooling support** and **data filtering**.
+
## See it in the docs
When you see the automatic docs, you can check that the input model and output model will both have their own JSON Schema:
@@ -146,6 +246,74 @@ And both models will be used for the interactive API documentation:
+## Other Return Type Annotations
+
+There might be cases where you return something that is not a valid Pydantic field and you annotate it in the function, only to get the support provided by tooling (the editor, mypy, etc).
+
+### Return a Response Directly
+
+The most common case would be [returning a Response directly as explained later in the advanced docs](../advanced/response-directly.md){.internal-link target=_blank}.
+
+```Python hl_lines="8 10-11"
+{!> ../../../docs_src/response_model/tutorial003_02.py!}
+```
+
+This simple case is handled automatically by FastAPI because the return type annotation is the class (or a subclass) of `Response`.
+
+And tools will also be happy because both `RedirectResponse` and `JSONResponse` are subclasses of `Response`, so the type annotation is correct.
+
+### Annotate a Response Subclass
+
+You can also use a subclass of `Response` in the type annotation:
+
+```Python hl_lines="8-9"
+{!> ../../../docs_src/response_model/tutorial003_03.py!}
+```
+
+This will also work because `RedirectResponse` is a subclass of `Response`, and FastAPI will automatically handle this simple case.
+
+### Invalid Return Type Annotations
+
+But when you return some other arbitrary object that is not a valid Pydantic type (e.g. a database object) and you annotate it like that in the function, FastAPI will try to create a Pydantic response model from that type annotation, and will fail.
+
+The same would happen if you had something like a union between different types where one or more of them are not valid Pydantic types, for example this would fail 💥:
+
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="10"
+ {!> ../../../docs_src/response_model/tutorial003_04.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="8"
+ {!> ../../../docs_src/response_model/tutorial003_04_py310.py!}
+ ```
+
+...this fails because the type annotation is not a Pydantic type and is not just a single `Response` class or subclass, it's a union (any of the two) between a `Response` and a `dict`.
+
+### Disable Response Model
+
+Continuing from the example above, you might not want to have the default data validation, documentation, filtering, etc. that is performed by FastAPI.
+
+But you might want to still keep the return type annotation in the function to get the support from tools like editors and type checkers (e.g. mypy).
+
+In this case, you can disable the response model generation by setting `response_model=None`:
+
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/response_model/tutorial003_05.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="7"
+ {!> ../../../docs_src/response_model/tutorial003_05_py310.py!}
+ ```
+
+This will make FastAPI skip the response model generation and that way you can have any return type annotations you need without it affecting your FastAPI application. 🤓
+
## Response Model encoding parameters
Your response model could have default values, like:
diff --git a/docs/en/overrides/main.html b/docs/en/overrides/main.html
index e9b9f60eb6..b85f0c4cfc 100644
--- a/docs/en/overrides/main.html
+++ b/docs/en/overrides/main.html
@@ -34,12 +34,6 @@
-
get işlemi kullanılarak
+
+
+!!! info "`@decorator` Bilgisi"
+ Python `@something` şeklinde ifadeleri "decorator" olarak adlandırır.
+
+ Decoratoru bir fonksiyonun üzerine koyarsınız. Dekoratif bir şapka gibi (Sanırım terim buradan gelmektedir).
+
+ Bir "decorator" fonksiyonu alır ve bazı işlemler gerçekleştir.
+
+ Bizim durumumzda decarator **FastAPI'ye** fonksiyonun bir `get` işlemi ile `/` pathine geldiğini söyler.
+
+ Bu **path işlem decoratordür**
+
+Ayrıca diğer işlemleri de kullanabilirsiniz:
+
+* `@app.post()`
+* `@app.put()`
+* `@app.delete()`
+
+Ve daha egzotik olanları:
+
+* `@app.options()`
+* `@app.head()`
+* `@app.patch()`
+* `@app.trace()`
+
+!!! tip
+ Her işlemi (HTTP method) istediğiniz gibi kullanmakta özgürsünüz.
+
+ **FastAPI** herhangi bir özel anlamı zorlamaz.
+
+ Buradaki bilgiler bir gereklilik değil, bir kılavuz olarak sunulmaktadır.
+
+ Örneğin, GraphQL kullanırkan normalde tüm işlemleri yalnızca `POST` işlemini kullanarak gerçekleştirirsiniz.
+
+### Adım 4: **path işlem fonksiyonunu** tanımlayın
+
+Aşağıdakiler bizim **path işlem fonksiyonlarımızdır**:
+
+* **path**: `/`
+* **işlem**: `get`
+* **function**: "decorator"ün altındaki fonksiyondur (`@app.get("/")` altında).
+
+```Python hl_lines="7"
+{!../../../docs_src/first_steps/tutorial001.py!}
+```
+
+Bu bir Python fonksiyonudur.
+
+Bir `GET` işlemi kullanarak "`/`" URL'sine bir istek geldiğinde **FastAPI** tarafından çağrılır.
+
+Bu durumda bir `async` fonksiyonudur.
+
+---
+
+Bunu `async def` yerine normal bir fonksiyon olarakta tanımlayabilirsiniz.
+
+```Python hl_lines="7"
+{!../../../docs_src/first_steps/tutorial003.py!}
+```
+
+!!! note
+
+ Eğer farkı bilmiyorsanız, [Async: *"Acelesi var?"*](../async.md#in-a-hurry){.internal-link target=_blank} kontrol edebilirsiniz.
+
+### Adım 5: İçeriği geri döndürün
+
+
+```Python hl_lines="8"
+{!../../../docs_src/first_steps/tutorial001.py!}
+```
+
+Bir `dict`, `list` döndürebilir veya `str`, `int` gibi tekil değerler döndürebilirsiniz.
+
+Ayrıca, Pydantic modellerini de döndürebilirsiniz. (Bununla ilgili daha sonra ayrıntılı bilgi göreceksiniz.)
+
+Otomatik olarak JSON'a dönüştürülecek(ORM'ler vb. dahil) başka birçok nesne ve model vardır. En beğendiklerinizi kullanmayı deneyin, yüksek ihtimalle destekleniyordur.
+
+## Özet
+
+* `FastAPI`'yi içe aktarın.
+* Bir `app` örneği oluşturun.
+* **path işlem decorator** yazın. (`@app.get("/")` gibi)
+* **path işlem fonksiyonu** yazın. (`def root(): ...` gibi)
+* Development sunucunuzu çalıştırın. (`uvicorn main:app --reload` gibi)
diff --git a/docs/tr/mkdocs.yml b/docs/tr/mkdocs.yml
index e29d259365..5904f71f98 100644
--- a/docs/tr/mkdocs.yml
+++ b/docs/tr/mkdocs.yml
@@ -61,6 +61,8 @@ nav:
- features.md
- fastapi-people.md
- python-types.md
+- Tutorial - User Guide:
+ - tutorial/first-steps.md
markdown_extensions:
- toc:
permalink: true
diff --git a/docs/zh/docs/benchmarks.md b/docs/zh/docs/benchmarks.md
index 8991c72cd9..71e8d48382 100644
--- a/docs/zh/docs/benchmarks.md
+++ b/docs/zh/docs/benchmarks.md
@@ -1,6 +1,6 @@
# 基准测试
-第三方机构 TechEmpower 的基准测试表明在 Uvicorn 下运行的 **FastAPI** 应用程序是 可用的最快的 Python 框架之一,仅次与 Starlette 和 Uvicorn 本身 (由 FastAPI 内部使用)。(*)
+第三方机构 TechEmpower 的基准测试表明在 Uvicorn 下运行的 **FastAPI** 应用程序是 可用的最快的 Python 框架之一,仅次于 Starlette 和 Uvicorn 本身 (由 FastAPI 内部使用)。(*)
但是在查看基准得分和对比时,请注意以下几点。
diff --git a/docs/zh/docs/index.md b/docs/zh/docs/index.md
index 7901e9c2cc..4db3ef10c4 100644
--- a/docs/zh/docs/index.md
+++ b/docs/zh/docs/index.md
@@ -28,7 +28,7 @@ FastAPI 是一个用于构建 API 的现代、快速(高性能)的 web 框
关键特性:
-* **快速**:可与 **NodeJS** 和 **Go** 比肩的极高性能(归功于 Starlette 和 Pydantic)。[最快的 Python web 框架之一](#_11)。
+* **快速**:可与 **NodeJS** 和 **Go** 并肩的极高性能(归功于 Starlette 和 Pydantic)。[最快的 Python web 框架之一](#_11)。
* **高效编码**:提高功能开发速度约 200% 至 300%。*
* **更少 bug**:减少约 40% 的人为(开发者)导致错误。*
diff --git a/docs_src/response_model/tutorial001.py b/docs_src/response_model/tutorial001.py
index 0f6e03e5b2..fd1c902a52 100644
--- a/docs_src/response_model/tutorial001.py
+++ b/docs_src/response_model/tutorial001.py
@@ -1,4 +1,4 @@
-from typing import List, Union
+from typing import Any, List, Union
from fastapi import FastAPI
from pydantic import BaseModel
@@ -15,5 +15,13 @@ class Item(BaseModel):
@app.post("/items/", response_model=Item)
-async def create_item(item: Item):
+async def create_item(item: Item) -> Any:
return item
+
+
+@app.get("/items/", response_model=List[Item])
+async def read_items() -> Any:
+ return [
+ {"name": "Portal Gun", "price": 42.0},
+ {"name": "Plumbus", "price": 32.0},
+ ]
diff --git a/docs_src/response_model/tutorial001_01.py b/docs_src/response_model/tutorial001_01.py
new file mode 100644
index 0000000000..98d30d540f
--- /dev/null
+++ b/docs_src/response_model/tutorial001_01.py
@@ -0,0 +1,27 @@
+from typing import List, Union
+
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: Union[str, None] = None
+ price: float
+ tax: Union[float, None] = None
+ tags: List[str] = []
+
+
+@app.post("/items/")
+async def create_item(item: Item) -> Item:
+ return item
+
+
+@app.get("/items/")
+async def read_items() -> List[Item]:
+ return [
+ Item(name="Portal Gun", price=42.0),
+ Item(name="Plumbus", price=32.0),
+ ]
diff --git a/docs_src/response_model/tutorial001_01_py310.py b/docs_src/response_model/tutorial001_01_py310.py
new file mode 100644
index 0000000000..7951c10761
--- /dev/null
+++ b/docs_src/response_model/tutorial001_01_py310.py
@@ -0,0 +1,25 @@
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float | None = None
+ tags: list[str] = []
+
+
+@app.post("/items/")
+async def create_item(item: Item) -> Item:
+ return item
+
+
+@app.get("/items/")
+async def read_items() -> list[Item]:
+ return [
+ Item(name="Portal Gun", price=42.0),
+ Item(name="Plumbus", price=32.0),
+ ]
diff --git a/docs_src/response_model/tutorial001_01_py39.py b/docs_src/response_model/tutorial001_01_py39.py
new file mode 100644
index 0000000000..16c78aa3fd
--- /dev/null
+++ b/docs_src/response_model/tutorial001_01_py39.py
@@ -0,0 +1,27 @@
+from typing import Union
+
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: Union[str, None] = None
+ price: float
+ tax: Union[float, None] = None
+ tags: list[str] = []
+
+
+@app.post("/items/")
+async def create_item(item: Item) -> Item:
+ return item
+
+
+@app.get("/items/")
+async def read_items() -> list[Item]:
+ return [
+ Item(name="Portal Gun", price=42.0),
+ Item(name="Plumbus", price=32.0),
+ ]
diff --git a/docs_src/response_model/tutorial001_py310.py b/docs_src/response_model/tutorial001_py310.py
index 59efecde41..f8a2aa9fca 100644
--- a/docs_src/response_model/tutorial001_py310.py
+++ b/docs_src/response_model/tutorial001_py310.py
@@ -1,3 +1,5 @@
+from typing import Any
+
from fastapi import FastAPI
from pydantic import BaseModel
@@ -13,5 +15,13 @@ class Item(BaseModel):
@app.post("/items/", response_model=Item)
-async def create_item(item: Item):
+async def create_item(item: Item) -> Any:
return item
+
+
+@app.get("/items/", response_model=list[Item])
+async def read_items() -> Any:
+ return [
+ {"name": "Portal Gun", "price": 42.0},
+ {"name": "Plumbus", "price": 32.0},
+ ]
diff --git a/docs_src/response_model/tutorial001_py39.py b/docs_src/response_model/tutorial001_py39.py
index cdcca39d2a..261e252d00 100644
--- a/docs_src/response_model/tutorial001_py39.py
+++ b/docs_src/response_model/tutorial001_py39.py
@@ -1,4 +1,4 @@
-from typing import Union
+from typing import Any, Union
from fastapi import FastAPI
from pydantic import BaseModel
@@ -15,5 +15,13 @@ class Item(BaseModel):
@app.post("/items/", response_model=Item)
-async def create_item(item: Item):
+async def create_item(item: Item) -> Any:
return item
+
+
+@app.get("/items/", response_model=list[Item])
+async def read_items() -> Any:
+ return [
+ {"name": "Portal Gun", "price": 42.0},
+ {"name": "Plumbus", "price": 32.0},
+ ]
diff --git a/docs_src/response_model/tutorial002.py b/docs_src/response_model/tutorial002.py
index c68e8b1385..a58668f9ef 100644
--- a/docs_src/response_model/tutorial002.py
+++ b/docs_src/response_model/tutorial002.py
@@ -14,6 +14,6 @@ class UserIn(BaseModel):
# Don't do this in production!
-@app.post("/user/", response_model=UserIn)
-async def create_user(user: UserIn):
+@app.post("/user/")
+async def create_user(user: UserIn) -> UserIn:
return user
diff --git a/docs_src/response_model/tutorial002_py310.py b/docs_src/response_model/tutorial002_py310.py
index 29ab9c9d25..0a91a5967f 100644
--- a/docs_src/response_model/tutorial002_py310.py
+++ b/docs_src/response_model/tutorial002_py310.py
@@ -12,6 +12,6 @@ class UserIn(BaseModel):
# Don't do this in production!
-@app.post("/user/", response_model=UserIn)
-async def create_user(user: UserIn):
+@app.post("/user/")
+async def create_user(user: UserIn) -> UserIn:
return user
diff --git a/docs_src/response_model/tutorial003.py b/docs_src/response_model/tutorial003.py
index 37e493dcbe..c42dbc7077 100644
--- a/docs_src/response_model/tutorial003.py
+++ b/docs_src/response_model/tutorial003.py
@@ -1,4 +1,4 @@
-from typing import Union
+from typing import Any, Union
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
@@ -20,5 +20,5 @@ class UserOut(BaseModel):
@app.post("/user/", response_model=UserOut)
-async def create_user(user: UserIn):
+async def create_user(user: UserIn) -> Any:
return user
diff --git a/docs_src/response_model/tutorial003_01.py b/docs_src/response_model/tutorial003_01.py
new file mode 100644
index 0000000000..52694b5510
--- /dev/null
+++ b/docs_src/response_model/tutorial003_01.py
@@ -0,0 +1,21 @@
+from typing import Union
+
+from fastapi import FastAPI
+from pydantic import BaseModel, EmailStr
+
+app = FastAPI()
+
+
+class BaseUser(BaseModel):
+ username: str
+ email: EmailStr
+ full_name: Union[str, None] = None
+
+
+class UserIn(BaseUser):
+ password: str
+
+
+@app.post("/user/")
+async def create_user(user: UserIn) -> BaseUser:
+ return user
diff --git a/docs_src/response_model/tutorial003_01_py310.py b/docs_src/response_model/tutorial003_01_py310.py
new file mode 100644
index 0000000000..6ffddfd0af
--- /dev/null
+++ b/docs_src/response_model/tutorial003_01_py310.py
@@ -0,0 +1,19 @@
+from fastapi import FastAPI
+from pydantic import BaseModel, EmailStr
+
+app = FastAPI()
+
+
+class BaseUser(BaseModel):
+ username: str
+ email: EmailStr
+ full_name: str | None = None
+
+
+class UserIn(BaseUser):
+ password: str
+
+
+@app.post("/user/")
+async def create_user(user: UserIn) -> BaseUser:
+ return user
diff --git a/docs_src/response_model/tutorial003_02.py b/docs_src/response_model/tutorial003_02.py
new file mode 100644
index 0000000000..df6a09646d
--- /dev/null
+++ b/docs_src/response_model/tutorial003_02.py
@@ -0,0 +1,11 @@
+from fastapi import FastAPI, Response
+from fastapi.responses import JSONResponse, RedirectResponse
+
+app = FastAPI()
+
+
+@app.get("/portal")
+async def get_portal(teleport: bool = False) -> Response:
+ if teleport:
+ return RedirectResponse(url="https://www.youtube.com/watch?v=dQw4w9WgXcQ")
+ return JSONResponse(content={"message": "Here's your interdimensional portal."})
diff --git a/docs_src/response_model/tutorial003_03.py b/docs_src/response_model/tutorial003_03.py
new file mode 100644
index 0000000000..0d4bd8de57
--- /dev/null
+++ b/docs_src/response_model/tutorial003_03.py
@@ -0,0 +1,9 @@
+from fastapi import FastAPI
+from fastapi.responses import RedirectResponse
+
+app = FastAPI()
+
+
+@app.get("/teleport")
+async def get_teleport() -> RedirectResponse:
+ return RedirectResponse(url="https://www.youtube.com/watch?v=dQw4w9WgXcQ")
diff --git a/docs_src/response_model/tutorial003_04.py b/docs_src/response_model/tutorial003_04.py
new file mode 100644
index 0000000000..b13a926929
--- /dev/null
+++ b/docs_src/response_model/tutorial003_04.py
@@ -0,0 +1,13 @@
+from typing import Union
+
+from fastapi import FastAPI, Response
+from fastapi.responses import RedirectResponse
+
+app = FastAPI()
+
+
+@app.get("/portal")
+async def get_portal(teleport: bool = False) -> Union[Response, dict]:
+ if teleport:
+ return RedirectResponse(url="https://www.youtube.com/watch?v=dQw4w9WgXcQ")
+ return {"message": "Here's your interdimensional portal."}
diff --git a/docs_src/response_model/tutorial003_04_py310.py b/docs_src/response_model/tutorial003_04_py310.py
new file mode 100644
index 0000000000..cee49b83e0
--- /dev/null
+++ b/docs_src/response_model/tutorial003_04_py310.py
@@ -0,0 +1,11 @@
+from fastapi import FastAPI, Response
+from fastapi.responses import RedirectResponse
+
+app = FastAPI()
+
+
+@app.get("/portal")
+async def get_portal(teleport: bool = False) -> Response | dict:
+ if teleport:
+ return RedirectResponse(url="https://www.youtube.com/watch?v=dQw4w9WgXcQ")
+ return {"message": "Here's your interdimensional portal."}
diff --git a/docs_src/response_model/tutorial003_05.py b/docs_src/response_model/tutorial003_05.py
new file mode 100644
index 0000000000..0962061a60
--- /dev/null
+++ b/docs_src/response_model/tutorial003_05.py
@@ -0,0 +1,13 @@
+from typing import Union
+
+from fastapi import FastAPI, Response
+from fastapi.responses import RedirectResponse
+
+app = FastAPI()
+
+
+@app.get("/portal", response_model=None)
+async def get_portal(teleport: bool = False) -> Union[Response, dict]:
+ if teleport:
+ return RedirectResponse(url="https://www.youtube.com/watch?v=dQw4w9WgXcQ")
+ return {"message": "Here's your interdimensional portal."}
diff --git a/docs_src/response_model/tutorial003_05_py310.py b/docs_src/response_model/tutorial003_05_py310.py
new file mode 100644
index 0000000000..f1c0f8e126
--- /dev/null
+++ b/docs_src/response_model/tutorial003_05_py310.py
@@ -0,0 +1,11 @@
+from fastapi import FastAPI, Response
+from fastapi.responses import RedirectResponse
+
+app = FastAPI()
+
+
+@app.get("/portal", response_model=None)
+async def get_portal(teleport: bool = False) -> Response | dict:
+ if teleport:
+ return RedirectResponse(url="https://www.youtube.com/watch?v=dQw4w9WgXcQ")
+ return {"message": "Here's your interdimensional portal."}
diff --git a/docs_src/response_model/tutorial003_py310.py b/docs_src/response_model/tutorial003_py310.py
index fc9693e3cd..3703bf888a 100644
--- a/docs_src/response_model/tutorial003_py310.py
+++ b/docs_src/response_model/tutorial003_py310.py
@@ -1,3 +1,5 @@
+from typing import Any
+
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
@@ -18,5 +20,5 @@ class UserOut(BaseModel):
@app.post("/user/", response_model=UserOut)
-async def create_user(user: UserIn):
+async def create_user(user: UserIn) -> Any:
return user
diff --git a/fastapi/__init__.py b/fastapi/__init__.py
index 037d9804b5..07ed78ffa6 100644
--- a/fastapi/__init__.py
+++ b/fastapi/__init__.py
@@ -1,6 +1,6 @@
"""FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
-__version__ = "0.88.0"
+__version__ = "0.89.1"
from starlette import status as status
diff --git a/fastapi/applications.py b/fastapi/applications.py
index 61d4582d27..36dc2605d5 100644
--- a/fastapi/applications.py
+++ b/fastapi/applications.py
@@ -274,7 +274,7 @@ class FastAPI(Starlette):
path: str,
endpoint: Callable[..., Coroutine[Any, Any, Response]],
*,
- response_model: Any = None,
+ response_model: Any = Default(None),
status_code: Optional[int] = None,
tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[Depends]] = None,
@@ -332,7 +332,7 @@ class FastAPI(Starlette):
self,
path: str,
*,
- response_model: Any = None,
+ response_model: Any = Default(None),
status_code: Optional[int] = None,
tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[Depends]] = None,
@@ -435,7 +435,7 @@ class FastAPI(Starlette):
self,
path: str,
*,
- response_model: Any = None,
+ response_model: Any = Default(None),
status_code: Optional[int] = None,
tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[Depends]] = None,
@@ -490,7 +490,7 @@ class FastAPI(Starlette):
self,
path: str,
*,
- response_model: Any = None,
+ response_model: Any = Default(None),
status_code: Optional[int] = None,
tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[Depends]] = None,
@@ -545,7 +545,7 @@ class FastAPI(Starlette):
self,
path: str,
*,
- response_model: Any = None,
+ response_model: Any = Default(None),
status_code: Optional[int] = None,
tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[Depends]] = None,
@@ -600,7 +600,7 @@ class FastAPI(Starlette):
self,
path: str,
*,
- response_model: Any = None,
+ response_model: Any = Default(None),
status_code: Optional[int] = None,
tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[Depends]] = None,
@@ -655,7 +655,7 @@ class FastAPI(Starlette):
self,
path: str,
*,
- response_model: Any = None,
+ response_model: Any = Default(None),
status_code: Optional[int] = None,
tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[Depends]] = None,
@@ -710,7 +710,7 @@ class FastAPI(Starlette):
self,
path: str,
*,
- response_model: Any = None,
+ response_model: Any = Default(None),
status_code: Optional[int] = None,
tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[Depends]] = None,
@@ -765,7 +765,7 @@ class FastAPI(Starlette):
self,
path: str,
*,
- response_model: Any = None,
+ response_model: Any = Default(None),
status_code: Optional[int] = None,
tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[Depends]] = None,
@@ -820,7 +820,7 @@ class FastAPI(Starlette):
self,
path: str,
*,
- response_model: Any = None,
+ response_model: Any = Default(None),
status_code: Optional[int] = None,
tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[Depends]] = None,
diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py
index 26db5a3afc..098100b8b2 100644
--- a/fastapi/dependencies/utils.py
+++ b/fastapi/dependencies/utils.py
@@ -253,7 +253,7 @@ def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature:
name=param.name,
kind=param.kind,
default=param.default,
- annotation=get_typed_annotation(param, globalns),
+ annotation=get_typed_annotation(param.annotation, globalns),
)
for param in signature.parameters.values()
]
@@ -261,14 +261,24 @@ def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature:
return typed_signature
-def get_typed_annotation(param: inspect.Parameter, globalns: Dict[str, Any]) -> Any:
- annotation = param.annotation
+def get_typed_annotation(annotation: Any, globalns: Dict[str, Any]) -> Any:
if isinstance(annotation, str):
annotation = ForwardRef(annotation)
annotation = evaluate_forwardref(annotation, globalns, globalns)
return annotation
+def get_typed_return_annotation(call: Callable[..., Any]) -> Any:
+ signature = inspect.signature(call)
+ annotation = signature.return_annotation
+
+ if annotation is inspect.Signature.empty:
+ return None
+
+ globalns = getattr(call, "__globals__", {})
+ return get_typed_annotation(annotation, globalns)
+
+
def get_dependant(
*,
path: str,
diff --git a/fastapi/routing.py b/fastapi/routing.py
index 9a7d88efc8..f131fa903e 100644
--- a/fastapi/routing.py
+++ b/fastapi/routing.py
@@ -26,6 +26,7 @@ from fastapi.dependencies.utils import (
get_body_field,
get_dependant,
get_parameterless_sub_dependant,
+ get_typed_return_annotation,
solve_dependencies,
)
from fastapi.encoders import DictIntStrAny, SetIntStr, jsonable_encoder
@@ -41,6 +42,7 @@ from fastapi.utils import (
from pydantic import BaseModel
from pydantic.error_wrappers import ErrorWrapper, ValidationError
from pydantic.fields import ModelField, Undefined
+from pydantic.utils import lenient_issubclass
from starlette import routing
from starlette.concurrency import run_in_threadpool
from starlette.exceptions import HTTPException
@@ -323,7 +325,7 @@ class APIRoute(routing.Route):
path: str,
endpoint: Callable[..., Any],
*,
- response_model: Any = None,
+ response_model: Any = Default(None),
status_code: Optional[int] = None,
tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
@@ -354,6 +356,12 @@ class APIRoute(routing.Route):
) -> None:
self.path = path
self.endpoint = endpoint
+ if isinstance(response_model, DefaultPlaceholder):
+ return_annotation = get_typed_return_annotation(endpoint)
+ if lenient_issubclass(return_annotation, Response):
+ response_model = None
+ else:
+ response_model = return_annotation
self.response_model = response_model
self.summary = summary
self.response_description = response_description
@@ -519,7 +527,7 @@ class APIRouter(routing.Router):
path: str,
endpoint: Callable[..., Any],
*,
- response_model: Any = None,
+ response_model: Any = Default(None),
status_code: Optional[int] = None,
tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
@@ -600,7 +608,7 @@ class APIRouter(routing.Router):
self,
path: str,
*,
- response_model: Any = None,
+ response_model: Any = Default(None),
status_code: Optional[int] = None,
tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
@@ -795,7 +803,7 @@ class APIRouter(routing.Router):
self,
path: str,
*,
- response_model: Any = None,
+ response_model: Any = Default(None),
status_code: Optional[int] = None,
tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
@@ -851,7 +859,7 @@ class APIRouter(routing.Router):
self,
path: str,
*,
- response_model: Any = None,
+ response_model: Any = Default(None),
status_code: Optional[int] = None,
tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
@@ -907,7 +915,7 @@ class APIRouter(routing.Router):
self,
path: str,
*,
- response_model: Any = None,
+ response_model: Any = Default(None),
status_code: Optional[int] = None,
tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
@@ -963,7 +971,7 @@ class APIRouter(routing.Router):
self,
path: str,
*,
- response_model: Any = None,
+ response_model: Any = Default(None),
status_code: Optional[int] = None,
tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
@@ -1019,7 +1027,7 @@ class APIRouter(routing.Router):
self,
path: str,
*,
- response_model: Any = None,
+ response_model: Any = Default(None),
status_code: Optional[int] = None,
tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
@@ -1075,7 +1083,7 @@ class APIRouter(routing.Router):
self,
path: str,
*,
- response_model: Any = None,
+ response_model: Any = Default(None),
status_code: Optional[int] = None,
tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
@@ -1131,7 +1139,7 @@ class APIRouter(routing.Router):
self,
path: str,
*,
- response_model: Any = None,
+ response_model: Any = Default(None),
status_code: Optional[int] = None,
tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
@@ -1187,7 +1195,7 @@ class APIRouter(routing.Router):
self,
path: str,
*,
- response_model: Any = None,
+ response_model: Any = Default(None),
status_code: Optional[int] = None,
tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
diff --git a/fastapi/utils.py b/fastapi/utils.py
index b15f6a2cfb..391c47d813 100644
--- a/fastapi/utils.py
+++ b/fastapi/utils.py
@@ -88,7 +88,13 @@ def create_response_field(
return response_field(field_info=field_info)
except RuntimeError:
raise fastapi.exceptions.FastAPIError(
- f"Invalid args for response field! Hint: check that {type_} is a valid pydantic field type"
+ "Invalid args for response field! Hint: "
+ f"check that {type_} is a valid Pydantic field type. "
+ "If you are using a return type annotation that is not a valid Pydantic "
+ "field (e.g. Union[Response, dict, None]) you can disable generating the "
+ "response model from the type annotation with the path operation decorator "
+ "parameter response_model=None. Read more: "
+ "https://fastapi.tiangolo.com/tutorial/response-model/"
) from None
diff --git a/pyproject.toml b/pyproject.toml
index 55856cf360..7fb8078f95 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -51,7 +51,7 @@ Documentation = "https://fastapi.tiangolo.com/"
[project.optional-dependencies]
test = [
"pytest >=7.1.3,<8.0.0",
- "coverage[toml] >= 6.5.0,<7.0",
+ "coverage[toml] >= 6.5.0,< 8.0",
"mypy ==0.982",
"ruff ==0.0.138",
"black == 22.10.0",
@@ -73,7 +73,7 @@ test = [
"passlib[bcrypt] >=1.7.2,<2.0.0",
# types
- "types-ujson ==5.5.0",
+ "types-ujson ==5.6.0.0",
"types-orjson ==3.6.2",
]
doc = [
@@ -88,7 +88,7 @@ doc = [
]
dev = [
"ruff ==0.0.138",
- "uvicorn[standard] >=0.12.0,<0.19.0",
+ "uvicorn[standard] >=0.12.0,<0.21.0",
"pre-commit >=2.17.0,<3.0.0",
]
all = [
@@ -155,6 +155,10 @@ source = [
"fastapi"
]
context = '${CONTEXT}'
+omit = [
+ "docs_src/response_model/tutorial003_04.py",
+ "docs_src/response_model/tutorial003_04_py310.py",
+]
[tool.ruff]
select = [
diff --git a/tests/test_reponse_set_reponse_code_empty.py b/tests/test_reponse_set_reponse_code_empty.py
index 094d54a84b..50ec753a00 100644
--- a/tests/test_reponse_set_reponse_code_empty.py
+++ b/tests/test_reponse_set_reponse_code_empty.py
@@ -9,6 +9,7 @@ app = FastAPI()
@app.delete(
"/{id}",
status_code=204,
+ response_model=None,
)
async def delete_deployment(
id: int,
diff --git a/tests/test_response_model_as_return_annotation.py b/tests/test_response_model_as_return_annotation.py
new file mode 100644
index 0000000000..e453641497
--- /dev/null
+++ b/tests/test_response_model_as_return_annotation.py
@@ -0,0 +1,1111 @@
+from typing import List, Union
+
+import pytest
+from fastapi import FastAPI
+from fastapi.exceptions import FastAPIError
+from fastapi.responses import JSONResponse, Response
+from fastapi.testclient import TestClient
+from pydantic import BaseModel, ValidationError
+
+
+class BaseUser(BaseModel):
+ name: str
+
+
+class User(BaseUser):
+ surname: str
+
+
+class DBUser(User):
+ password_hash: str
+
+
+class Item(BaseModel):
+ name: str
+ price: float
+
+
+app = FastAPI()
+
+
+@app.get("/no_response_model-no_annotation-return_model")
+def no_response_model_no_annotation_return_model():
+ return User(name="John", surname="Doe")
+
+
+@app.get("/no_response_model-no_annotation-return_dict")
+def no_response_model_no_annotation_return_dict():
+ return {"name": "John", "surname": "Doe"}
+
+
+@app.get("/response_model-no_annotation-return_same_model", response_model=User)
+def response_model_no_annotation_return_same_model():
+ return User(name="John", surname="Doe")
+
+
+@app.get("/response_model-no_annotation-return_exact_dict", response_model=User)
+def response_model_no_annotation_return_exact_dict():
+ return {"name": "John", "surname": "Doe"}
+
+
+@app.get("/response_model-no_annotation-return_invalid_dict", response_model=User)
+def response_model_no_annotation_return_invalid_dict():
+ return {"name": "John"}
+
+
+@app.get("/response_model-no_annotation-return_invalid_model", response_model=User)
+def response_model_no_annotation_return_invalid_model():
+ return Item(name="Foo", price=42.0)
+
+
+@app.get(
+ "/response_model-no_annotation-return_dict_with_extra_data", response_model=User
+)
+def response_model_no_annotation_return_dict_with_extra_data():
+ return {"name": "John", "surname": "Doe", "password_hash": "secret"}
+
+
+@app.get(
+ "/response_model-no_annotation-return_submodel_with_extra_data", response_model=User
+)
+def response_model_no_annotation_return_submodel_with_extra_data():
+ return DBUser(name="John", surname="Doe", password_hash="secret")
+
+
+@app.get("/no_response_model-annotation-return_same_model")
+def no_response_model_annotation_return_same_model() -> User:
+ return User(name="John", surname="Doe")
+
+
+@app.get("/no_response_model-annotation-return_exact_dict")
+def no_response_model_annotation_return_exact_dict() -> User:
+ return {"name": "John", "surname": "Doe"}
+
+
+@app.get("/no_response_model-annotation-return_invalid_dict")
+def no_response_model_annotation_return_invalid_dict() -> User:
+ return {"name": "John"}
+
+
+@app.get("/no_response_model-annotation-return_invalid_model")
+def no_response_model_annotation_return_invalid_model() -> User:
+ return Item(name="Foo", price=42.0)
+
+
+@app.get("/no_response_model-annotation-return_dict_with_extra_data")
+def no_response_model_annotation_return_dict_with_extra_data() -> User:
+ return {"name": "John", "surname": "Doe", "password_hash": "secret"}
+
+
+@app.get("/no_response_model-annotation-return_submodel_with_extra_data")
+def no_response_model_annotation_return_submodel_with_extra_data() -> User:
+ return DBUser(name="John", surname="Doe", password_hash="secret")
+
+
+@app.get("/response_model_none-annotation-return_same_model", response_model=None)
+def response_model_none_annotation_return_same_model() -> User:
+ return User(name="John", surname="Doe")
+
+
+@app.get("/response_model_none-annotation-return_exact_dict", response_model=None)
+def response_model_none_annotation_return_exact_dict() -> User:
+ return {"name": "John", "surname": "Doe"}
+
+
+@app.get("/response_model_none-annotation-return_invalid_dict", response_model=None)
+def response_model_none_annotation_return_invalid_dict() -> User:
+ return {"name": "John"}
+
+
+@app.get("/response_model_none-annotation-return_invalid_model", response_model=None)
+def response_model_none_annotation_return_invalid_model() -> User:
+ return Item(name="Foo", price=42.0)
+
+
+@app.get(
+ "/response_model_none-annotation-return_dict_with_extra_data", response_model=None
+)
+def response_model_none_annotation_return_dict_with_extra_data() -> User:
+ return {"name": "John", "surname": "Doe", "password_hash": "secret"}
+
+
+@app.get(
+ "/response_model_none-annotation-return_submodel_with_extra_data",
+ response_model=None,
+)
+def response_model_none_annotation_return_submodel_with_extra_data() -> User:
+ return DBUser(name="John", surname="Doe", password_hash="secret")
+
+
+@app.get(
+ "/response_model_model1-annotation_model2-return_same_model", response_model=User
+)
+def response_model_model1_annotation_model2_return_same_model() -> Item:
+ return User(name="John", surname="Doe")
+
+
+@app.get(
+ "/response_model_model1-annotation_model2-return_exact_dict", response_model=User
+)
+def response_model_model1_annotation_model2_return_exact_dict() -> Item:
+ return {"name": "John", "surname": "Doe"}
+
+
+@app.get(
+ "/response_model_model1-annotation_model2-return_invalid_dict", response_model=User
+)
+def response_model_model1_annotation_model2_return_invalid_dict() -> Item:
+ return {"name": "John"}
+
+
+@app.get(
+ "/response_model_model1-annotation_model2-return_invalid_model", response_model=User
+)
+def response_model_model1_annotation_model2_return_invalid_model() -> Item:
+ return Item(name="Foo", price=42.0)
+
+
+@app.get(
+ "/response_model_model1-annotation_model2-return_dict_with_extra_data",
+ response_model=User,
+)
+def response_model_model1_annotation_model2_return_dict_with_extra_data() -> Item:
+ return {"name": "John", "surname": "Doe", "password_hash": "secret"}
+
+
+@app.get(
+ "/response_model_model1-annotation_model2-return_submodel_with_extra_data",
+ response_model=User,
+)
+def response_model_model1_annotation_model2_return_submodel_with_extra_data() -> Item:
+ return DBUser(name="John", surname="Doe", password_hash="secret")
+
+
+@app.get(
+ "/response_model_filtering_model-annotation_submodel-return_submodel",
+ response_model=User,
+)
+def response_model_filtering_model_annotation_submodel_return_submodel() -> DBUser:
+ return DBUser(name="John", surname="Doe", password_hash="secret")
+
+
+@app.get("/response_model_list_of_model-no_annotation", response_model=List[User])
+def response_model_list_of_model_no_annotation():
+ return [
+ DBUser(name="John", surname="Doe", password_hash="secret"),
+ DBUser(name="Jane", surname="Does", password_hash="secret2"),
+ ]
+
+
+@app.get("/no_response_model-annotation_list_of_model")
+def no_response_model_annotation_list_of_model() -> List[User]:
+ return [
+ DBUser(name="John", surname="Doe", password_hash="secret"),
+ DBUser(name="Jane", surname="Does", password_hash="secret2"),
+ ]
+
+
+@app.get("/no_response_model-annotation_forward_ref_list_of_model")
+def no_response_model_annotation_forward_ref_list_of_model() -> "List[User]":
+ return [
+ DBUser(name="John", surname="Doe", password_hash="secret"),
+ DBUser(name="Jane", surname="Does", password_hash="secret2"),
+ ]
+
+
+@app.get(
+ "/response_model_union-no_annotation-return_model1",
+ response_model=Union[User, Item],
+)
+def response_model_union_no_annotation_return_model1():
+ return DBUser(name="John", surname="Doe", password_hash="secret")
+
+
+@app.get(
+ "/response_model_union-no_annotation-return_model2",
+ response_model=Union[User, Item],
+)
+def response_model_union_no_annotation_return_model2():
+ return Item(name="Foo", price=42.0)
+
+
+@app.get("/no_response_model-annotation_union-return_model1")
+def no_response_model_annotation_union_return_model1() -> Union[User, Item]:
+ return DBUser(name="John", surname="Doe", password_hash="secret")
+
+
+@app.get("/no_response_model-annotation_union-return_model2")
+def no_response_model_annotation_union_return_model2() -> Union[User, Item]:
+ return Item(name="Foo", price=42.0)
+
+
+@app.get("/no_response_model-annotation_response_class")
+def no_response_model_annotation_response_class() -> Response:
+ return Response(content="Foo")
+
+
+@app.get("/no_response_model-annotation_json_response_class")
+def no_response_model_annotation_json_response_class() -> JSONResponse:
+ return JSONResponse(content={"foo": "bar"})
+
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/no_response_model-no_annotation-return_model": {
+ "get": {
+ "summary": "No Response Model No Annotation Return Model",
+ "operationId": "no_response_model_no_annotation_return_model_no_response_model_no_annotation_return_model_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ }
+ },
+ }
+ },
+ "/no_response_model-no_annotation-return_dict": {
+ "get": {
+ "summary": "No Response Model No Annotation Return Dict",
+ "operationId": "no_response_model_no_annotation_return_dict_no_response_model_no_annotation_return_dict_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ }
+ },
+ }
+ },
+ "/response_model-no_annotation-return_same_model": {
+ "get": {
+ "summary": "Response Model No Annotation Return Same Model",
+ "operationId": "response_model_no_annotation_return_same_model_response_model_no_annotation_return_same_model_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/User"}
+ }
+ },
+ }
+ },
+ }
+ },
+ "/response_model-no_annotation-return_exact_dict": {
+ "get": {
+ "summary": "Response Model No Annotation Return Exact Dict",
+ "operationId": "response_model_no_annotation_return_exact_dict_response_model_no_annotation_return_exact_dict_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/User"}
+ }
+ },
+ }
+ },
+ }
+ },
+ "/response_model-no_annotation-return_invalid_dict": {
+ "get": {
+ "summary": "Response Model No Annotation Return Invalid Dict",
+ "operationId": "response_model_no_annotation_return_invalid_dict_response_model_no_annotation_return_invalid_dict_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/User"}
+ }
+ },
+ }
+ },
+ }
+ },
+ "/response_model-no_annotation-return_invalid_model": {
+ "get": {
+ "summary": "Response Model No Annotation Return Invalid Model",
+ "operationId": "response_model_no_annotation_return_invalid_model_response_model_no_annotation_return_invalid_model_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/User"}
+ }
+ },
+ }
+ },
+ }
+ },
+ "/response_model-no_annotation-return_dict_with_extra_data": {
+ "get": {
+ "summary": "Response Model No Annotation Return Dict With Extra Data",
+ "operationId": "response_model_no_annotation_return_dict_with_extra_data_response_model_no_annotation_return_dict_with_extra_data_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/User"}
+ }
+ },
+ }
+ },
+ }
+ },
+ "/response_model-no_annotation-return_submodel_with_extra_data": {
+ "get": {
+ "summary": "Response Model No Annotation Return Submodel With Extra Data",
+ "operationId": "response_model_no_annotation_return_submodel_with_extra_data_response_model_no_annotation_return_submodel_with_extra_data_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/User"}
+ }
+ },
+ }
+ },
+ }
+ },
+ "/no_response_model-annotation-return_same_model": {
+ "get": {
+ "summary": "No Response Model Annotation Return Same Model",
+ "operationId": "no_response_model_annotation_return_same_model_no_response_model_annotation_return_same_model_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/User"}
+ }
+ },
+ }
+ },
+ }
+ },
+ "/no_response_model-annotation-return_exact_dict": {
+ "get": {
+ "summary": "No Response Model Annotation Return Exact Dict",
+ "operationId": "no_response_model_annotation_return_exact_dict_no_response_model_annotation_return_exact_dict_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/User"}
+ }
+ },
+ }
+ },
+ }
+ },
+ "/no_response_model-annotation-return_invalid_dict": {
+ "get": {
+ "summary": "No Response Model Annotation Return Invalid Dict",
+ "operationId": "no_response_model_annotation_return_invalid_dict_no_response_model_annotation_return_invalid_dict_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/User"}
+ }
+ },
+ }
+ },
+ }
+ },
+ "/no_response_model-annotation-return_invalid_model": {
+ "get": {
+ "summary": "No Response Model Annotation Return Invalid Model",
+ "operationId": "no_response_model_annotation_return_invalid_model_no_response_model_annotation_return_invalid_model_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/User"}
+ }
+ },
+ }
+ },
+ }
+ },
+ "/no_response_model-annotation-return_dict_with_extra_data": {
+ "get": {
+ "summary": "No Response Model Annotation Return Dict With Extra Data",
+ "operationId": "no_response_model_annotation_return_dict_with_extra_data_no_response_model_annotation_return_dict_with_extra_data_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/User"}
+ }
+ },
+ }
+ },
+ }
+ },
+ "/no_response_model-annotation-return_submodel_with_extra_data": {
+ "get": {
+ "summary": "No Response Model Annotation Return Submodel With Extra Data",
+ "operationId": "no_response_model_annotation_return_submodel_with_extra_data_no_response_model_annotation_return_submodel_with_extra_data_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/User"}
+ }
+ },
+ }
+ },
+ }
+ },
+ "/response_model_none-annotation-return_same_model": {
+ "get": {
+ "summary": "Response Model None Annotation Return Same Model",
+ "operationId": "response_model_none_annotation_return_same_model_response_model_none_annotation_return_same_model_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ }
+ },
+ }
+ },
+ "/response_model_none-annotation-return_exact_dict": {
+ "get": {
+ "summary": "Response Model None Annotation Return Exact Dict",
+ "operationId": "response_model_none_annotation_return_exact_dict_response_model_none_annotation_return_exact_dict_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ }
+ },
+ }
+ },
+ "/response_model_none-annotation-return_invalid_dict": {
+ "get": {
+ "summary": "Response Model None Annotation Return Invalid Dict",
+ "operationId": "response_model_none_annotation_return_invalid_dict_response_model_none_annotation_return_invalid_dict_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ }
+ },
+ }
+ },
+ "/response_model_none-annotation-return_invalid_model": {
+ "get": {
+ "summary": "Response Model None Annotation Return Invalid Model",
+ "operationId": "response_model_none_annotation_return_invalid_model_response_model_none_annotation_return_invalid_model_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ }
+ },
+ }
+ },
+ "/response_model_none-annotation-return_dict_with_extra_data": {
+ "get": {
+ "summary": "Response Model None Annotation Return Dict With Extra Data",
+ "operationId": "response_model_none_annotation_return_dict_with_extra_data_response_model_none_annotation_return_dict_with_extra_data_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ }
+ },
+ }
+ },
+ "/response_model_none-annotation-return_submodel_with_extra_data": {
+ "get": {
+ "summary": "Response Model None Annotation Return Submodel With Extra Data",
+ "operationId": "response_model_none_annotation_return_submodel_with_extra_data_response_model_none_annotation_return_submodel_with_extra_data_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ }
+ },
+ }
+ },
+ "/response_model_model1-annotation_model2-return_same_model": {
+ "get": {
+ "summary": "Response Model Model1 Annotation Model2 Return Same Model",
+ "operationId": "response_model_model1_annotation_model2_return_same_model_response_model_model1_annotation_model2_return_same_model_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/User"}
+ }
+ },
+ }
+ },
+ }
+ },
+ "/response_model_model1-annotation_model2-return_exact_dict": {
+ "get": {
+ "summary": "Response Model Model1 Annotation Model2 Return Exact Dict",
+ "operationId": "response_model_model1_annotation_model2_return_exact_dict_response_model_model1_annotation_model2_return_exact_dict_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/User"}
+ }
+ },
+ }
+ },
+ }
+ },
+ "/response_model_model1-annotation_model2-return_invalid_dict": {
+ "get": {
+ "summary": "Response Model Model1 Annotation Model2 Return Invalid Dict",
+ "operationId": "response_model_model1_annotation_model2_return_invalid_dict_response_model_model1_annotation_model2_return_invalid_dict_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/User"}
+ }
+ },
+ }
+ },
+ }
+ },
+ "/response_model_model1-annotation_model2-return_invalid_model": {
+ "get": {
+ "summary": "Response Model Model1 Annotation Model2 Return Invalid Model",
+ "operationId": "response_model_model1_annotation_model2_return_invalid_model_response_model_model1_annotation_model2_return_invalid_model_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/User"}
+ }
+ },
+ }
+ },
+ }
+ },
+ "/response_model_model1-annotation_model2-return_dict_with_extra_data": {
+ "get": {
+ "summary": "Response Model Model1 Annotation Model2 Return Dict With Extra Data",
+ "operationId": "response_model_model1_annotation_model2_return_dict_with_extra_data_response_model_model1_annotation_model2_return_dict_with_extra_data_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/User"}
+ }
+ },
+ }
+ },
+ }
+ },
+ "/response_model_model1-annotation_model2-return_submodel_with_extra_data": {
+ "get": {
+ "summary": "Response Model Model1 Annotation Model2 Return Submodel With Extra Data",
+ "operationId": "response_model_model1_annotation_model2_return_submodel_with_extra_data_response_model_model1_annotation_model2_return_submodel_with_extra_data_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/User"}
+ }
+ },
+ }
+ },
+ }
+ },
+ "/response_model_filtering_model-annotation_submodel-return_submodel": {
+ "get": {
+ "summary": "Response Model Filtering Model Annotation Submodel Return Submodel",
+ "operationId": "response_model_filtering_model_annotation_submodel_return_submodel_response_model_filtering_model_annotation_submodel_return_submodel_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/User"}
+ }
+ },
+ }
+ },
+ }
+ },
+ "/response_model_list_of_model-no_annotation": {
+ "get": {
+ "summary": "Response Model List Of Model No Annotation",
+ "operationId": "response_model_list_of_model_no_annotation_response_model_list_of_model_no_annotation_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response Response Model List Of Model No Annotation Response Model List Of Model No Annotation Get",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/User"},
+ }
+ }
+ },
+ }
+ },
+ }
+ },
+ "/no_response_model-annotation_list_of_model": {
+ "get": {
+ "summary": "No Response Model Annotation List Of Model",
+ "operationId": "no_response_model_annotation_list_of_model_no_response_model_annotation_list_of_model_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response No Response Model Annotation List Of Model No Response Model Annotation List Of Model Get",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/User"},
+ }
+ }
+ },
+ }
+ },
+ }
+ },
+ "/no_response_model-annotation_forward_ref_list_of_model": {
+ "get": {
+ "summary": "No Response Model Annotation Forward Ref List Of Model",
+ "operationId": "no_response_model_annotation_forward_ref_list_of_model_no_response_model_annotation_forward_ref_list_of_model_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response No Response Model Annotation Forward Ref List Of Model No Response Model Annotation Forward Ref List Of Model Get",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/User"},
+ }
+ }
+ },
+ }
+ },
+ }
+ },
+ "/response_model_union-no_annotation-return_model1": {
+ "get": {
+ "summary": "Response Model Union No Annotation Return Model1",
+ "operationId": "response_model_union_no_annotation_return_model1_response_model_union_no_annotation_return_model1_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response Response Model Union No Annotation Return Model1 Response Model Union No Annotation Return Model1 Get",
+ "anyOf": [
+ {"$ref": "#/components/schemas/User"},
+ {"$ref": "#/components/schemas/Item"},
+ ],
+ }
+ }
+ },
+ }
+ },
+ }
+ },
+ "/response_model_union-no_annotation-return_model2": {
+ "get": {
+ "summary": "Response Model Union No Annotation Return Model2",
+ "operationId": "response_model_union_no_annotation_return_model2_response_model_union_no_annotation_return_model2_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response Response Model Union No Annotation Return Model2 Response Model Union No Annotation Return Model2 Get",
+ "anyOf": [
+ {"$ref": "#/components/schemas/User"},
+ {"$ref": "#/components/schemas/Item"},
+ ],
+ }
+ }
+ },
+ }
+ },
+ }
+ },
+ "/no_response_model-annotation_union-return_model1": {
+ "get": {
+ "summary": "No Response Model Annotation Union Return Model1",
+ "operationId": "no_response_model_annotation_union_return_model1_no_response_model_annotation_union_return_model1_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response No Response Model Annotation Union Return Model1 No Response Model Annotation Union Return Model1 Get",
+ "anyOf": [
+ {"$ref": "#/components/schemas/User"},
+ {"$ref": "#/components/schemas/Item"},
+ ],
+ }
+ }
+ },
+ }
+ },
+ }
+ },
+ "/no_response_model-annotation_union-return_model2": {
+ "get": {
+ "summary": "No Response Model Annotation Union Return Model2",
+ "operationId": "no_response_model_annotation_union_return_model2_no_response_model_annotation_union_return_model2_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response No Response Model Annotation Union Return Model2 No Response Model Annotation Union Return Model2 Get",
+ "anyOf": [
+ {"$ref": "#/components/schemas/User"},
+ {"$ref": "#/components/schemas/Item"},
+ ],
+ }
+ }
+ },
+ }
+ },
+ }
+ },
+ "/no_response_model-annotation_response_class": {
+ "get": {
+ "summary": "No Response Model Annotation Response Class",
+ "operationId": "no_response_model_annotation_response_class_no_response_model_annotation_response_class_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ }
+ },
+ }
+ },
+ "/no_response_model-annotation_json_response_class": {
+ "get": {
+ "summary": "No Response Model Annotation Json Response Class",
+ "operationId": "no_response_model_annotation_json_response_class_no_response_model_annotation_json_response_class_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ }
+ },
+ }
+ },
+ },
+ "components": {
+ "schemas": {
+ "Item": {
+ "title": "Item",
+ "required": ["name", "price"],
+ "type": "object",
+ "properties": {
+ "name": {"title": "Name", "type": "string"},
+ "price": {"title": "Price", "type": "number"},
+ },
+ },
+ "User": {
+ "title": "User",
+ "required": ["name", "surname"],
+ "type": "object",
+ "properties": {
+ "name": {"title": "Name", "type": "string"},
+ "surname": {"title": "Surname", "type": "string"},
+ },
+ },
+ }
+ },
+}
+
+
+client = TestClient(app)
+
+
+def test_openapi_schema():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+def test_no_response_model_no_annotation_return_model():
+ response = client.get("/no_response_model-no_annotation-return_model")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"name": "John", "surname": "Doe"}
+
+
+def test_no_response_model_no_annotation_return_dict():
+ response = client.get("/no_response_model-no_annotation-return_dict")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"name": "John", "surname": "Doe"}
+
+
+def test_response_model_no_annotation_return_same_model():
+ response = client.get("/response_model-no_annotation-return_same_model")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"name": "John", "surname": "Doe"}
+
+
+def test_response_model_no_annotation_return_exact_dict():
+ response = client.get("/response_model-no_annotation-return_exact_dict")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"name": "John", "surname": "Doe"}
+
+
+def test_response_model_no_annotation_return_invalid_dict():
+ with pytest.raises(ValidationError):
+ client.get("/response_model-no_annotation-return_invalid_dict")
+
+
+def test_response_model_no_annotation_return_invalid_model():
+ with pytest.raises(ValidationError):
+ client.get("/response_model-no_annotation-return_invalid_model")
+
+
+def test_response_model_no_annotation_return_dict_with_extra_data():
+ response = client.get("/response_model-no_annotation-return_dict_with_extra_data")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"name": "John", "surname": "Doe"}
+
+
+def test_response_model_no_annotation_return_submodel_with_extra_data():
+ response = client.get(
+ "/response_model-no_annotation-return_submodel_with_extra_data"
+ )
+ assert response.status_code == 200, response.text
+ assert response.json() == {"name": "John", "surname": "Doe"}
+
+
+def test_no_response_model_annotation_return_same_model():
+ response = client.get("/no_response_model-annotation-return_same_model")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"name": "John", "surname": "Doe"}
+
+
+def test_no_response_model_annotation_return_exact_dict():
+ response = client.get("/no_response_model-annotation-return_exact_dict")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"name": "John", "surname": "Doe"}
+
+
+def test_no_response_model_annotation_return_invalid_dict():
+ with pytest.raises(ValidationError):
+ client.get("/no_response_model-annotation-return_invalid_dict")
+
+
+def test_no_response_model_annotation_return_invalid_model():
+ with pytest.raises(ValidationError):
+ client.get("/no_response_model-annotation-return_invalid_model")
+
+
+def test_no_response_model_annotation_return_dict_with_extra_data():
+ response = client.get("/no_response_model-annotation-return_dict_with_extra_data")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"name": "John", "surname": "Doe"}
+
+
+def test_no_response_model_annotation_return_submodel_with_extra_data():
+ response = client.get(
+ "/no_response_model-annotation-return_submodel_with_extra_data"
+ )
+ assert response.status_code == 200, response.text
+ assert response.json() == {"name": "John", "surname": "Doe"}
+
+
+def test_response_model_none_annotation_return_same_model():
+ response = client.get("/response_model_none-annotation-return_same_model")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"name": "John", "surname": "Doe"}
+
+
+def test_response_model_none_annotation_return_exact_dict():
+ response = client.get("/response_model_none-annotation-return_exact_dict")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"name": "John", "surname": "Doe"}
+
+
+def test_response_model_none_annotation_return_invalid_dict():
+ response = client.get("/response_model_none-annotation-return_invalid_dict")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"name": "John"}
+
+
+def test_response_model_none_annotation_return_invalid_model():
+ response = client.get("/response_model_none-annotation-return_invalid_model")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"name": "Foo", "price": 42.0}
+
+
+def test_response_model_none_annotation_return_dict_with_extra_data():
+ response = client.get("/response_model_none-annotation-return_dict_with_extra_data")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "name": "John",
+ "surname": "Doe",
+ "password_hash": "secret",
+ }
+
+
+def test_response_model_none_annotation_return_submodel_with_extra_data():
+ response = client.get(
+ "/response_model_none-annotation-return_submodel_with_extra_data"
+ )
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "name": "John",
+ "surname": "Doe",
+ "password_hash": "secret",
+ }
+
+
+def test_response_model_model1_annotation_model2_return_same_model():
+ response = client.get("/response_model_model1-annotation_model2-return_same_model")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"name": "John", "surname": "Doe"}
+
+
+def test_response_model_model1_annotation_model2_return_exact_dict():
+ response = client.get("/response_model_model1-annotation_model2-return_exact_dict")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"name": "John", "surname": "Doe"}
+
+
+def test_response_model_model1_annotation_model2_return_invalid_dict():
+ with pytest.raises(ValidationError):
+ client.get("/response_model_model1-annotation_model2-return_invalid_dict")
+
+
+def test_response_model_model1_annotation_model2_return_invalid_model():
+ with pytest.raises(ValidationError):
+ client.get("/response_model_model1-annotation_model2-return_invalid_model")
+
+
+def test_response_model_model1_annotation_model2_return_dict_with_extra_data():
+ response = client.get(
+ "/response_model_model1-annotation_model2-return_dict_with_extra_data"
+ )
+ assert response.status_code == 200, response.text
+ assert response.json() == {"name": "John", "surname": "Doe"}
+
+
+def test_response_model_model1_annotation_model2_return_submodel_with_extra_data():
+ response = client.get(
+ "/response_model_model1-annotation_model2-return_submodel_with_extra_data"
+ )
+ assert response.status_code == 200, response.text
+ assert response.json() == {"name": "John", "surname": "Doe"}
+
+
+def test_response_model_filtering_model_annotation_submodel_return_submodel():
+ response = client.get(
+ "/response_model_filtering_model-annotation_submodel-return_submodel"
+ )
+ assert response.status_code == 200, response.text
+ assert response.json() == {"name": "John", "surname": "Doe"}
+
+
+def test_response_model_list_of_model_no_annotation():
+ response = client.get("/response_model_list_of_model-no_annotation")
+ assert response.status_code == 200, response.text
+ assert response.json() == [
+ {"name": "John", "surname": "Doe"},
+ {"name": "Jane", "surname": "Does"},
+ ]
+
+
+def test_no_response_model_annotation_list_of_model():
+ response = client.get("/no_response_model-annotation_list_of_model")
+ assert response.status_code == 200, response.text
+ assert response.json() == [
+ {"name": "John", "surname": "Doe"},
+ {"name": "Jane", "surname": "Does"},
+ ]
+
+
+def test_no_response_model_annotation_forward_ref_list_of_model():
+ response = client.get("/no_response_model-annotation_forward_ref_list_of_model")
+ assert response.status_code == 200, response.text
+ assert response.json() == [
+ {"name": "John", "surname": "Doe"},
+ {"name": "Jane", "surname": "Does"},
+ ]
+
+
+def test_response_model_union_no_annotation_return_model1():
+ response = client.get("/response_model_union-no_annotation-return_model1")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"name": "John", "surname": "Doe"}
+
+
+def test_response_model_union_no_annotation_return_model2():
+ response = client.get("/response_model_union-no_annotation-return_model2")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"name": "Foo", "price": 42.0}
+
+
+def test_no_response_model_annotation_union_return_model1():
+ response = client.get("/no_response_model-annotation_union-return_model1")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"name": "John", "surname": "Doe"}
+
+
+def test_no_response_model_annotation_union_return_model2():
+ response = client.get("/no_response_model-annotation_union-return_model2")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"name": "Foo", "price": 42.0}
+
+
+def test_no_response_model_annotation_return_class():
+ response = client.get("/no_response_model-annotation_response_class")
+ assert response.status_code == 200, response.text
+ assert response.text == "Foo"
+
+
+def test_no_response_model_annotation_json_response_class():
+ response = client.get("/no_response_model-annotation_json_response_class")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"foo": "bar"}
+
+
+def test_invalid_response_model_field():
+ app = FastAPI()
+ with pytest.raises(FastAPIError) as e:
+
+ @app.get("/")
+ def read_root() -> Union[Response, None]:
+ return Response(content="Foo") # pragma: no cover
+
+ assert "valid Pydantic field type" in e.value.args[0]
+ assert "parameter response_model=None" in e.value.args[0]
diff --git a/tests/test_tutorial/test_response_model/test_tutorial003_01.py b/tests/test_tutorial/test_response_model/test_tutorial003_01.py
new file mode 100644
index 0000000000..39a4734eda
--- /dev/null
+++ b/tests/test_tutorial/test_response_model/test_tutorial003_01.py
@@ -0,0 +1,120 @@
+from fastapi.testclient import TestClient
+
+from docs_src.response_model.tutorial003_01 import app
+
+client = TestClient(app)
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/user/": {
+ "post": {
+ "summary": "Create User",
+ "operationId": "create_user_user__post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/UserIn"}
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/BaseUser"}
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "BaseUser": {
+ "title": "BaseUser",
+ "required": ["username", "email"],
+ "type": "object",
+ "properties": {
+ "username": {"title": "Username", "type": "string"},
+ "email": {"title": "Email", "type": "string", "format": "email"},
+ "full_name": {"title": "Full Name", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ "UserIn": {
+ "title": "UserIn",
+ "required": ["username", "email", "password"],
+ "type": "object",
+ "properties": {
+ "username": {"title": "Username", "type": "string"},
+ "email": {"title": "Email", "type": "string", "format": "email"},
+ "full_name": {"title": "Full Name", "type": "string"},
+ "password": {"title": "Password", "type": "string"},
+ },
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ }
+ },
+}
+
+
+def test_openapi_schema():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+def test_post_user():
+ response = client.post(
+ "/user/",
+ json={
+ "username": "foo",
+ "password": "fighter",
+ "email": "foo@example.com",
+ "full_name": "Grave Dohl",
+ },
+ )
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "username": "foo",
+ "email": "foo@example.com",
+ "full_name": "Grave Dohl",
+ }
diff --git a/tests/test_tutorial/test_response_model/test_tutorial003_01_py310.py b/tests/test_tutorial/test_response_model/test_tutorial003_01_py310.py
new file mode 100644
index 0000000000..3a04db6bc6
--- /dev/null
+++ b/tests/test_tutorial/test_response_model/test_tutorial003_01_py310.py
@@ -0,0 +1,129 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/user/": {
+ "post": {
+ "summary": "Create User",
+ "operationId": "create_user_user__post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/UserIn"}
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/BaseUser"}
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "BaseUser": {
+ "title": "BaseUser",
+ "required": ["username", "email"],
+ "type": "object",
+ "properties": {
+ "username": {"title": "Username", "type": "string"},
+ "email": {"title": "Email", "type": "string", "format": "email"},
+ "full_name": {"title": "Full Name", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ "UserIn": {
+ "title": "UserIn",
+ "required": ["username", "email", "password"],
+ "type": "object",
+ "properties": {
+ "username": {"title": "Username", "type": "string"},
+ "email": {"title": "Email", "type": "string", "format": "email"},
+ "full_name": {"title": "Full Name", "type": "string"},
+ "password": {"title": "Password", "type": "string"},
+ },
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ }
+ },
+}
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.response_model.tutorial003_01_py310 import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_py310
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+@needs_py310
+def test_post_user(client: TestClient):
+ response = client.post(
+ "/user/",
+ json={
+ "username": "foo",
+ "password": "fighter",
+ "email": "foo@example.com",
+ "full_name": "Grave Dohl",
+ },
+ )
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "username": "foo",
+ "email": "foo@example.com",
+ "full_name": "Grave Dohl",
+ }
diff --git a/tests/test_tutorial/test_response_model/test_tutorial003_02.py b/tests/test_tutorial/test_response_model/test_tutorial003_02.py
new file mode 100644
index 0000000000..d933f871cf
--- /dev/null
+++ b/tests/test_tutorial/test_response_model/test_tutorial003_02.py
@@ -0,0 +1,93 @@
+from fastapi.testclient import TestClient
+
+from docs_src.response_model.tutorial003_02 import app
+
+client = TestClient(app)
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/portal": {
+ "get": {
+ "summary": "Get Portal",
+ "operationId": "get_portal_portal_get",
+ "parameters": [
+ {
+ "required": False,
+ "schema": {
+ "title": "Teleport",
+ "type": "boolean",
+ "default": False,
+ },
+ "name": "teleport",
+ "in": "query",
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ }
+ },
+}
+
+
+def test_openapi_schema():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+def test_get_portal():
+ response = client.get("/portal")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"message": "Here's your interdimensional portal."}
+
+
+def test_get_redirect():
+ response = client.get("/portal", params={"teleport": True}, follow_redirects=False)
+ assert response.status_code == 307, response.text
+ assert response.headers["location"] == "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
diff --git a/tests/test_tutorial/test_response_model/test_tutorial003_03.py b/tests/test_tutorial/test_response_model/test_tutorial003_03.py
new file mode 100644
index 0000000000..398eb47658
--- /dev/null
+++ b/tests/test_tutorial/test_response_model/test_tutorial003_03.py
@@ -0,0 +1,36 @@
+from fastapi.testclient import TestClient
+
+from docs_src.response_model.tutorial003_03 import app
+
+client = TestClient(app)
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/teleport": {
+ "get": {
+ "summary": "Get Teleport",
+ "operationId": "get_teleport_teleport_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ }
+ },
+ }
+ }
+ },
+}
+
+
+def test_openapi_schema():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+def test_get_portal():
+ response = client.get("/teleport", follow_redirects=False)
+ assert response.status_code == 307, response.text
+ assert response.headers["location"] == "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
diff --git a/tests/test_tutorial/test_response_model/test_tutorial003_04.py b/tests/test_tutorial/test_response_model/test_tutorial003_04.py
new file mode 100644
index 0000000000..4aa80145a5
--- /dev/null
+++ b/tests/test_tutorial/test_response_model/test_tutorial003_04.py
@@ -0,0 +1,9 @@
+import pytest
+from fastapi.exceptions import FastAPIError
+
+
+def test_invalid_response_model():
+ with pytest.raises(FastAPIError):
+ from docs_src.response_model.tutorial003_04 import app
+
+ assert app # pragma: no cover
diff --git a/tests/test_tutorial/test_response_model/test_tutorial003_04_py310.py b/tests/test_tutorial/test_response_model/test_tutorial003_04_py310.py
new file mode 100644
index 0000000000..b876facc8b
--- /dev/null
+++ b/tests/test_tutorial/test_response_model/test_tutorial003_04_py310.py
@@ -0,0 +1,12 @@
+import pytest
+from fastapi.exceptions import FastAPIError
+
+from ...utils import needs_py310
+
+
+@needs_py310
+def test_invalid_response_model():
+ with pytest.raises(FastAPIError):
+ from docs_src.response_model.tutorial003_04_py310 import app
+
+ assert app # pragma: no cover
diff --git a/tests/test_tutorial/test_response_model/test_tutorial003_05.py b/tests/test_tutorial/test_response_model/test_tutorial003_05.py
new file mode 100644
index 0000000000..27896d490e
--- /dev/null
+++ b/tests/test_tutorial/test_response_model/test_tutorial003_05.py
@@ -0,0 +1,93 @@
+from fastapi.testclient import TestClient
+
+from docs_src.response_model.tutorial003_05 import app
+
+client = TestClient(app)
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/portal": {
+ "get": {
+ "summary": "Get Portal",
+ "operationId": "get_portal_portal_get",
+ "parameters": [
+ {
+ "required": False,
+ "schema": {
+ "title": "Teleport",
+ "type": "boolean",
+ "default": False,
+ },
+ "name": "teleport",
+ "in": "query",
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ }
+ },
+}
+
+
+def test_openapi_schema():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+def test_get_portal():
+ response = client.get("/portal")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"message": "Here's your interdimensional portal."}
+
+
+def test_get_redirect():
+ response = client.get("/portal", params={"teleport": True}, follow_redirects=False)
+ assert response.status_code == 307, response.text
+ assert response.headers["location"] == "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
diff --git a/tests/test_tutorial/test_response_model/test_tutorial003_05_py310.py b/tests/test_tutorial/test_response_model/test_tutorial003_05_py310.py
new file mode 100644
index 0000000000..bf36c906b2
--- /dev/null
+++ b/tests/test_tutorial/test_response_model/test_tutorial003_05_py310.py
@@ -0,0 +1,103 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/portal": {
+ "get": {
+ "summary": "Get Portal",
+ "operationId": "get_portal_portal_get",
+ "parameters": [
+ {
+ "required": False,
+ "schema": {
+ "title": "Teleport",
+ "type": "boolean",
+ "default": False,
+ },
+ "name": "teleport",
+ "in": "query",
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ }
+ },
+}
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.response_model.tutorial003_05_py310 import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_py310
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+@needs_py310
+def test_get_portal(client: TestClient):
+ response = client.get("/portal")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"message": "Here's your interdimensional portal."}
+
+
+@needs_py310
+def test_get_redirect(client: TestClient):
+ response = client.get("/portal", params={"teleport": True}, follow_redirects=False)
+ assert response.status_code == 307, response.text
+ assert response.headers["location"] == "https://www.youtube.com/watch?v=dQw4w9WgXcQ"