mirror of
https://github.com/fastapi/fastapi.git
synced 2025-12-27 08:10:57 -05:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1ce16c2f40 | ||
|
|
03d4d4c38e | ||
|
|
2d2f1dee5d | ||
|
|
59e36481dc | ||
|
|
4fcb00328c | ||
|
|
9d56a3cb59 | ||
|
|
78b07cb809 | ||
|
|
6034f80687 | ||
|
|
b93f8a709a | ||
|
|
618c99d774 |
2
.github/workflows/build-docs.yml
vendored
2
.github/workflows/build-docs.yml
vendored
@@ -1,6 +1,8 @@
|
||||
name: Build Docs
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
types: [opened, synchronize]
|
||||
jobs:
|
||||
|
||||
@@ -50,6 +50,7 @@ The key features are:
|
||||
<a href="https://bit.ly/2QSouzH" target="_blank" title="Jina: build neural search-as-a-service for any kind of data in just minutes."><img src="https://fastapi.tiangolo.com/img/sponsors/jina.svg"></a>
|
||||
<a href="https://cryptapi.io/" target="_blank" title="CryptAPI: Your easy to use, secure and privacy oriented payment gateway."><img src="https://fastapi.tiangolo.com/img/sponsors/cryptapi.svg"></a>
|
||||
<a href="https://www.dropbase.io/careers" target="_blank" title="Dropbase - seamlessly collect, clean, and centralize data."><img src="https://fastapi.tiangolo.com/img/sponsors/dropbase.svg"></a>
|
||||
<a href="https://striveworks.us/careers?utm_source=fastapi&utm_medium=sponsor_banner&utm_campaign=feb_march#openings" target="_blank" title="https://striveworks.us/careers"><img src="https://fastapi.tiangolo.com/img/sponsors/striveworks.png"></a>
|
||||
<a href="https://www.deta.sh/?ref=fastapi" target="_blank" title="The launchpad for all your (team's) ideas"><img src="https://fastapi.tiangolo.com/img/sponsors/deta.svg"></a>
|
||||
<a href="https://www.investsuite.com/jobs" target="_blank" title="Wealthtech jobs with FastAPI"><img src="https://fastapi.tiangolo.com/img/sponsors/investsuite.svg"></a>
|
||||
<a href="https://www.vim.so/?utm_source=FastAPI" target="_blank" title="We help you master vim with interactive exercises"><img src="https://fastapi.tiangolo.com/img/sponsors/vimso.png"></a>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
maintainers:
|
||||
- login: tiangolo
|
||||
answers: 1230
|
||||
prs: 269
|
||||
answers: 1237
|
||||
prs: 280
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=5cad72c846b7aba2e960546af490edc7375dafc4&v=4
|
||||
url: https://github.com/tiangolo
|
||||
experts:
|
||||
- login: Kludex
|
||||
count: 316
|
||||
count: 319
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=3682d9b9b93bef272f379ab623dc031c8d71432e&v=4
|
||||
url: https://github.com/Kludex
|
||||
- login: dmontagu
|
||||
@@ -14,7 +14,7 @@ experts:
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/35119617?u=58ed2a45798a4339700e2f62b2e12e6e54bf0396&v=4
|
||||
url: https://github.com/dmontagu
|
||||
- login: ycd
|
||||
count: 219
|
||||
count: 221
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/62724709?u=826f228edf0bab0d19ad1d5c4ba4df1047ccffef&v=4
|
||||
url: https://github.com/ycd
|
||||
- login: Mause
|
||||
@@ -34,7 +34,7 @@ experts:
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/31127044?u=81a84af39c89b898b0fbc5a04e8834f60f23e55a&v=4
|
||||
url: https://github.com/ArcLightSlavik
|
||||
- login: raphaelauv
|
||||
count: 67
|
||||
count: 68
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/10202690?u=e6f86f5c0c3026a15d6b51792fa3e532b12f1371&v=4
|
||||
url: https://github.com/raphaelauv
|
||||
- login: falkben
|
||||
@@ -57,18 +57,22 @@ experts:
|
||||
count: 38
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/11836741?u=8bd5ef7e62fe6a82055e33c4c0e0a7879ff8cfb6&v=4
|
||||
url: https://github.com/includeamin
|
||||
- login: STeveShary
|
||||
count: 37
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/5167622?u=de8f597c81d6336fcebc37b32dfd61a3f877160c&v=4
|
||||
url: https://github.com/STeveShary
|
||||
- login: prostomarkeloff
|
||||
count: 33
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/28061158?u=72309cc1f2e04e40fa38b29969cb4e9d3f722e7b&v=4
|
||||
url: https://github.com/prostomarkeloff
|
||||
- login: STeveShary
|
||||
count: 32
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/5167622?u=de8f597c81d6336fcebc37b32dfd61a3f877160c&v=4
|
||||
url: https://github.com/STeveShary
|
||||
- login: krishnardt
|
||||
count: 31
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/31960541?u=47f4829c77f4962ab437ffb7995951e41eeebe9b&v=4
|
||||
url: https://github.com/krishnardt
|
||||
- login: adriangb
|
||||
count: 30
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/1755071?u=81f0262df34e1460ca546fbd0c211169c2478532&v=4
|
||||
url: https://github.com/adriangb
|
||||
- login: wshayes
|
||||
count: 29
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/365303?u=07ca03c5ee811eb0920e633cc3c3db73dbec1aa5&v=4
|
||||
@@ -77,10 +81,10 @@ experts:
|
||||
count: 29
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/1144727?u=85c025e3fcc7bd79a5665c63ee87cdf8aae13374&v=4
|
||||
url: https://github.com/frankie567
|
||||
- login: adriangb
|
||||
count: 28
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/1755071?u=81f0262df34e1460ca546fbd0c211169c2478532&v=4
|
||||
url: https://github.com/adriangb
|
||||
- login: chbndrhnns
|
||||
count: 25
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/7534547?v=4
|
||||
url: https://github.com/chbndrhnns
|
||||
- login: ghandic
|
||||
count: 25
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/23500353?u=e2e1d736f924d9be81e8bfc565b6d8836ba99773&v=4
|
||||
@@ -89,18 +93,14 @@ experts:
|
||||
count: 25
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/43723790?u=9bcce836bbce55835291c5b2ac93a4e311f4b3c3&v=4
|
||||
url: https://github.com/dbanty
|
||||
- login: chbndrhnns
|
||||
count: 24
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/7534547?v=4
|
||||
url: https://github.com/chbndrhnns
|
||||
- login: panla
|
||||
count: 25
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/41326348?u=ba2fda6b30110411ecbf406d187907e2b420ac19&v=4
|
||||
url: https://github.com/panla
|
||||
- login: SirTelemak
|
||||
count: 24
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/9435877?u=719327b7d2c4c62212456d771bfa7c6b8dbb9eac&v=4
|
||||
url: https://github.com/SirTelemak
|
||||
- login: panla
|
||||
count: 23
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/41326348?u=ba2fda6b30110411ecbf406d187907e2b420ac19&v=4
|
||||
url: https://github.com/panla
|
||||
- login: acnebs
|
||||
count: 22
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/9054108?u=c27e50269f1ef8ea950cc6f0268c8ec5cebbe9c9&v=4
|
||||
@@ -129,22 +129,34 @@ experts:
|
||||
count: 17
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/28262306?u=66ee21316275ef356081c2efc4ed7a4572e690dc&v=4
|
||||
url: https://github.com/nkhitrov
|
||||
- login: acidjunk
|
||||
count: 16
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/685002?u=b5094ab4527fc84b006c0ac9ff54367bdebb2267&v=4
|
||||
url: https://github.com/acidjunk
|
||||
- login: waynerv
|
||||
count: 16
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/39515546?u=ec35139777597cdbbbddda29bf8b9d4396b429a9&v=4
|
||||
url: https://github.com/waynerv
|
||||
- login: acidjunk
|
||||
count: 15
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/685002?u=b5094ab4527fc84b006c0ac9ff54367bdebb2267&v=4
|
||||
url: https://github.com/acidjunk
|
||||
- login: dstlny
|
||||
count: 14
|
||||
count: 16
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/41964673?u=9f2174f9d61c15c6e3a4c9e3aeee66f711ce311f&v=4
|
||||
url: https://github.com/dstlny
|
||||
- login: jgould22
|
||||
count: 14
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/4335847?u=ed77f67e0bb069084639b24d812dbb2a2b1dc554&v=4
|
||||
url: https://github.com/jgould22
|
||||
- login: harunyasar
|
||||
count: 14
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/1765494?u=5b1ab7c582db4b4016fa31affe977d10af108ad4&v=4
|
||||
url: https://github.com/harunyasar
|
||||
- login: haizaar
|
||||
count: 13
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/58201?u=4f1f9843d69433ca0d380d95146cfe119e5fdac4&v=4
|
||||
url: https://github.com/haizaar
|
||||
- login: hellocoldworld
|
||||
count: 12
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/47581948?v=4
|
||||
url: https://github.com/hellocoldworld
|
||||
- login: David-Lor
|
||||
count: 12
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/17401854?u=474680c02b94cba810cb9032fb7eb787d9cc9d22&v=4
|
||||
@@ -173,39 +185,47 @@ experts:
|
||||
count: 10
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/20441825?u=ee1e59446b98f8ec2363caeda4c17164d0d9cc7d&v=4
|
||||
url: https://github.com/stefanondisponibile
|
||||
- login: hellocoldworld
|
||||
count: 10
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/47581948?v=4
|
||||
url: https://github.com/hellocoldworld
|
||||
- login: oligond
|
||||
count: 10
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/2858306?u=1bb1182a5944e93624b7fb26585f22c8f7a9d76e&v=4
|
||||
url: https://github.com/oligond
|
||||
last_month_active:
|
||||
- login: insomnes
|
||||
count: 10
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/16958893?u=f8be7088d5076d963984a21f95f44e559192d912&v=4
|
||||
url: https://github.com/insomnes
|
||||
- login: raphaelauv
|
||||
count: 6
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/10202690?u=e6f86f5c0c3026a15d6b51792fa3e532b12f1371&v=4
|
||||
url: https://github.com/raphaelauv
|
||||
- login: jgould22
|
||||
count: 4
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/4335847?u=ed77f67e0bb069084639b24d812dbb2a2b1dc554&v=4
|
||||
url: https://github.com/jgould22
|
||||
- login: harunyasar
|
||||
count: 4
|
||||
count: 10
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/1765494?u=5b1ab7c582db4b4016fa31affe977d10af108ad4&v=4
|
||||
url: https://github.com/harunyasar
|
||||
- login: jgould22
|
||||
count: 10
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/4335847?u=ed77f67e0bb069084639b24d812dbb2a2b1dc554&v=4
|
||||
url: https://github.com/jgould22
|
||||
- login: rafsaf
|
||||
count: 9
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/51059348?u=be9f06b8ced2d2b677297decc781fa8ce4f7ddbd&v=4
|
||||
url: https://github.com/rafsaf
|
||||
- login: STeveShary
|
||||
count: 5
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/5167622?u=de8f597c81d6336fcebc37b32dfd61a3f877160c&v=4
|
||||
url: https://github.com/STeveShary
|
||||
- login: ahnaf-zamil
|
||||
count: 3
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/57180217?u=849128b146771ace47beca5b5ff68eb82905dd6d&v=4
|
||||
url: https://github.com/ahnaf-zamil
|
||||
- login: lucastosetto
|
||||
count: 3
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/89307132?u=56326696423df7126c9e7c702ee58f294db69a2a&v=4
|
||||
url: https://github.com/lucastosetto
|
||||
- login: blokje
|
||||
count: 3
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/851418?v=4
|
||||
url: https://github.com/blokje
|
||||
- login: MatthijsKok
|
||||
count: 3
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/7658129?u=1243e32d57e13abc45e3f5235ed5b9197e0d2b41&v=4
|
||||
url: https://github.com/MatthijsKok
|
||||
- login: Kludex
|
||||
count: 3
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=3682d9b9b93bef272f379ab623dc031c8d71432e&v=4
|
||||
url: https://github.com/Kludex
|
||||
- login: panla
|
||||
count: 3
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/41326348?u=ba2fda6b30110411ecbf406d187907e2b420ac19&v=4
|
||||
url: https://github.com/panla
|
||||
top_contributors:
|
||||
- login: waynerv
|
||||
count: 25
|
||||
@@ -219,14 +239,14 @@ top_contributors:
|
||||
count: 16
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/35119617?u=58ed2a45798a4339700e2f62b2e12e6e54bf0396&v=4
|
||||
url: https://github.com/dmontagu
|
||||
- login: jaystone776
|
||||
count: 15
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/11191137?u=299205a95e9b6817a43144a48b643346a5aac5cc&v=4
|
||||
url: https://github.com/jaystone776
|
||||
- login: euri10
|
||||
count: 13
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/1104190?u=321a2e953e6645a7d09b732786c7a8061e0f8a8b&v=4
|
||||
url: https://github.com/euri10
|
||||
- login: jaystone776
|
||||
count: 13
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/11191137?u=299205a95e9b6817a43144a48b643346a5aac5cc&v=4
|
||||
url: https://github.com/jaystone776
|
||||
- login: mariacamilagl
|
||||
count: 12
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/11489395?u=4adb6986bf3debfc2b8216ae701f2bd47d73da7d&v=4
|
||||
@@ -285,7 +305,7 @@ top_contributors:
|
||||
url: https://github.com/NinaHwang
|
||||
top_reviewers:
|
||||
- login: Kludex
|
||||
count: 91
|
||||
count: 93
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=3682d9b9b93bef272f379ab623dc031c8d71432e&v=4
|
||||
url: https://github.com/Kludex
|
||||
- login: waynerv
|
||||
@@ -301,7 +321,7 @@ top_reviewers:
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/41147016?u=55010621aece725aa702270b54fed829b6a1fe60&v=4
|
||||
url: https://github.com/tokusumi
|
||||
- login: ycd
|
||||
count: 44
|
||||
count: 45
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/62724709?u=826f228edf0bab0d19ad1d5c4ba4df1047ccffef&v=4
|
||||
url: https://github.com/ycd
|
||||
- login: AdrianDeAnda
|
||||
@@ -312,12 +332,16 @@ top_reviewers:
|
||||
count: 31
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/31127044?u=81a84af39c89b898b0fbc5a04e8834f60f23e55a&v=4
|
||||
url: https://github.com/ArcLightSlavik
|
||||
- login: cikay
|
||||
count: 24
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/24587499?u=e772190a051ab0eaa9c8542fcff1892471638f2b&v=4
|
||||
url: https://github.com/cikay
|
||||
- login: dmontagu
|
||||
count: 23
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/35119617?u=58ed2a45798a4339700e2f62b2e12e6e54bf0396&v=4
|
||||
url: https://github.com/dmontagu
|
||||
- login: cassiobotaro
|
||||
count: 22
|
||||
count: 23
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/3127847?u=b0a652331da17efeb85cd6e3a4969182e5004804&v=4
|
||||
url: https://github.com/cassiobotaro
|
||||
- login: komtaki
|
||||
@@ -336,6 +360,10 @@ top_reviewers:
|
||||
count: 16
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/21978760?v=4
|
||||
url: https://github.com/yanever
|
||||
- login: lsglucas
|
||||
count: 16
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/61513630?u=320e43fe4dc7bc6efc64e9b8f325f8075634fd20&v=4
|
||||
url: https://github.com/lsglucas
|
||||
- login: SwftAlpc
|
||||
count: 16
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/52768429?u=6a3aa15277406520ad37f6236e89466ed44bc5b8&v=4
|
||||
@@ -356,26 +384,22 @@ top_reviewers:
|
||||
count: 15
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/63476957?u=6c86e59b48e0394d4db230f37fc9ad4d7e2c27c7&v=4
|
||||
url: https://github.com/delhi09
|
||||
- login: lsglucas
|
||||
count: 14
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/61513630?u=320e43fe4dc7bc6efc64e9b8f325f8075634fd20&v=4
|
||||
url: https://github.com/lsglucas
|
||||
- login: rjNemo
|
||||
count: 13
|
||||
count: 14
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/56785022?u=d5c3a02567c8649e146fcfc51b6060ccaf8adef8&v=4
|
||||
url: https://github.com/rjNemo
|
||||
- login: RunningIkkyu
|
||||
count: 12
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/31848542?u=706e1ee3f248245f2d68b976d149d06fd5a2010d&v=4
|
||||
url: https://github.com/RunningIkkyu
|
||||
- login: yezz123
|
||||
count: 12
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/52716203?u=636b4f79645176df4527dd45c12d5dbb5a4193cf&v=4
|
||||
url: https://github.com/yezz123
|
||||
- login: sh0nk
|
||||
count: 12
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/6478810?u=af15d724875cec682ed8088a86d36b2798f981c0&v=4
|
||||
url: https://github.com/sh0nk
|
||||
- login: yezz123
|
||||
count: 11
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/52716203?u=636b4f79645176df4527dd45c12d5dbb5a4193cf&v=4
|
||||
url: https://github.com/yezz123
|
||||
- login: mariacamilagl
|
||||
count: 10
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/11489395?u=4adb6986bf3debfc2b8216ae701f2bd47d73da7d&v=4
|
||||
@@ -400,6 +424,10 @@ top_reviewers:
|
||||
count: 9
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/49435654?v=4
|
||||
url: https://github.com/kty4119
|
||||
- login: zy7y
|
||||
count: 9
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/67154681?u=5d634834cc514028ea3f9115f7030b99a1f4d5a4&v=4
|
||||
url: https://github.com/zy7y
|
||||
- login: bezaca
|
||||
count: 9
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/69092910?u=4ac58eab99bd37d663f3d23551df96d4fbdbf760&v=4
|
||||
@@ -444,39 +472,31 @@ top_reviewers:
|
||||
count: 7
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/34248814?v=4
|
||||
url: https://github.com/krocdort
|
||||
- login: dimaqq
|
||||
count: 7
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/662249?v=4
|
||||
url: https://github.com/dimaqq
|
||||
- login: jovicon
|
||||
count: 6
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/21287303?u=b049eac3e51a4c0473c2efe66b4d28a7d8f2b572&v=4
|
||||
url: https://github.com/jovicon
|
||||
- login: NinaHwang
|
||||
count: 6
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/79563565?u=1741703bd6c8f491503354b363a86e879b4c1cab&v=4
|
||||
url: https://github.com/NinaHwang
|
||||
- login: diogoduartec
|
||||
count: 5
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/31852339?u=b50fc11c531e9b77922e19edfc9e7233d4d7b92e&v=4
|
||||
url: https://github.com/diogoduartec
|
||||
- login: nimctl
|
||||
- login: n25a
|
||||
count: 5
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/49960770?u=e39b11d47188744ee07b2a1c7ce1a1bdf3c80760&v=4
|
||||
url: https://github.com/nimctl
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/49960770?u=eb3c95338741c78fff7d9d5d7ace9617e53eee4a&v=4
|
||||
url: https://github.com/n25a
|
||||
- login: izaguerreiro
|
||||
count: 5
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/2241504?v=4
|
||||
url: https://github.com/izaguerreiro
|
||||
- login: israteneda
|
||||
count: 5
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/20668624?u=d7b2961d330aca65fbce5bdb26a0800a3d23ed2d&v=4
|
||||
url: https://github.com/israteneda
|
||||
- login: juntatalor
|
||||
count: 5
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/8134632?v=4
|
||||
url: https://github.com/juntatalor
|
||||
- login: SnkSynthesis
|
||||
count: 5
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/63564282?u=0078826509dbecb2fdb543f4e881c9cd06157893&v=4
|
||||
url: https://github.com/SnkSynthesis
|
||||
- login: anthonycepeda
|
||||
count: 5
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/72019805?u=892f700c79f9732211bd5221bf16eec32356a732&v=4
|
||||
url: https://github.com/anthonycepeda
|
||||
- login: oandersonmagalhaes
|
||||
count: 5
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/83456692?v=4
|
||||
url: https://github.com/oandersonmagalhaes
|
||||
- login: qysfblog
|
||||
count: 5
|
||||
avatarUrl: https://avatars.githubusercontent.com/u/52229895?v=4
|
||||
url: https://github.com/qysfblog
|
||||
|
||||
@@ -8,6 +8,9 @@ gold:
|
||||
- url: https://www.dropbase.io/careers
|
||||
title: Dropbase - seamlessly collect, clean, and centralize data.
|
||||
img: https://fastapi.tiangolo.com/img/sponsors/dropbase.svg
|
||||
- url: https://striveworks.us/careers?utm_source=fastapi&utm_medium=sponsor_banner&utm_campaign=feb_march#openings
|
||||
title: https://striveworks.us/careers
|
||||
img: https://fastapi.tiangolo.com/img/sponsors/striveworks.png
|
||||
silver:
|
||||
- url: https://www.deta.sh/?ref=fastapi
|
||||
title: The launchpad for all your (team's) ideas
|
||||
|
||||
@@ -8,3 +8,4 @@ logins:
|
||||
- deepset-ai
|
||||
- cryptapi
|
||||
- DropbaseHQ
|
||||
- Striveworks
|
||||
|
||||
BIN
docs/en/docs/img/sponsors/striveworks-banner.png
Normal file
BIN
docs/en/docs/img/sponsors/striveworks-banner.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.5 KiB |
BIN
docs/en/docs/img/sponsors/striveworks.png
Normal file
BIN
docs/en/docs/img/sponsors/striveworks.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
@@ -2,6 +2,77 @@
|
||||
|
||||
## Latest Changes
|
||||
|
||||
|
||||
## 0.74.0
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
* ✨ Update internal `AsyncExitStack` to fix context for dependencies with `yield`. PR [#4575](https://github.com/tiangolo/fastapi/pull/4575) by [@tiangolo](https://github.com/tiangolo).
|
||||
|
||||
Dependencies with `yield` can now catch `HTTPException` and custom exceptions. For example:
|
||||
|
||||
```Python
|
||||
async def get_database():
|
||||
with Session() as session:
|
||||
try:
|
||||
yield session
|
||||
except HTTPException:
|
||||
session.rollback()
|
||||
raise
|
||||
finally:
|
||||
session.close()
|
||||
```
|
||||
|
||||
After the dependency with `yield` handles the exception (or not) the exception is raised again. So that any exception handlers can catch it, or ultimately the default internal `ServerErrorMiddleware`.
|
||||
|
||||
If you depended on exceptions not being received by dependencies with `yield`, and receiving an exception breaks the code after `yield`, you can use a block with `try` and `finally`:
|
||||
|
||||
```Python
|
||||
async def do_something():
|
||||
try:
|
||||
yield something
|
||||
finally:
|
||||
some_cleanup()
|
||||
```
|
||||
|
||||
...that way the `finally` block is run regardless of any exception that might happen.
|
||||
|
||||
### Features
|
||||
|
||||
* The same PR [#4575](https://github.com/tiangolo/fastapi/pull/4575) from above also fixes the `contextvars` context for the code before and after `yield`. This was the main objective of that PR.
|
||||
|
||||
This means that now, if you set a value in a context variable before `yield`, the value would still be available after `yield` (as you would intuitively expect). And it also means that you can reset the context variable with a token afterwards.
|
||||
|
||||
For example, this works correctly now:
|
||||
|
||||
```Python
|
||||
from contextvars import ContextVar
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
|
||||
legacy_request_state_context_var: ContextVar[Optional[Dict[str, Any]]] = ContextVar(
|
||||
"legacy_request_state_context_var", default=None
|
||||
)
|
||||
|
||||
async def set_up_request_state_dependency():
|
||||
request_state = {"user": "deadpond"}
|
||||
contextvar_token = legacy_request_state_context_var.set(request_state)
|
||||
yield request_state
|
||||
legacy_request_state_context_var.reset(contextvar_token)
|
||||
```
|
||||
|
||||
...before this change it would raise an error when resetting the context variable, because the `contextvars` context was different, because of the way it was implemented.
|
||||
|
||||
**Note**: You probably don't need `contextvars`, and you should probably avoid using them. But they are powerful and useful in some advanced scenarios, for example, migrating from code that used Flask's `g` semi-global variable.
|
||||
|
||||
**Technical Details**: If you want to know more of the technical details you can check out the PR description [#4575](https://github.com/tiangolo/fastapi/pull/4575).
|
||||
|
||||
### Internal
|
||||
|
||||
* 🔧 Add Striveworks sponsor. PR [#4596](https://github.com/tiangolo/fastapi/pull/4596) by [@tiangolo](https://github.com/tiangolo).
|
||||
* 💚 Only build docs on push when on master to avoid duplicate runs from PRs. PR [#4564](https://github.com/tiangolo/fastapi/pull/4564) by [@tiangolo](https://github.com/tiangolo).
|
||||
* 👥 Update FastAPI People. PR [#4502](https://github.com/tiangolo/fastapi/pull/4502) by [@github-actions[bot]](https://github.com/apps/github-actions).
|
||||
|
||||
## 0.73.0
|
||||
|
||||
### Features
|
||||
|
||||
@@ -99,7 +99,7 @@ You saw that you can use dependencies with `yield` and have `try` blocks that ca
|
||||
|
||||
It might be tempting to raise an `HTTPException` or similar in the exit code, after the `yield`. But **it won't work**.
|
||||
|
||||
The exit code in dependencies with `yield` is executed *after* [Exception Handlers](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}. There's nothing catching exceptions thrown by your dependencies in the exit code (after the `yield`).
|
||||
The exit code in dependencies with `yield` is executed *after* the response is sent, so [Exception Handlers](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank} will have already run. There's nothing catching exceptions thrown by your dependencies in the exit code (after the `yield`).
|
||||
|
||||
So, if you raise an `HTTPException` after the `yield`, the default (or any custom) exception handler that catches `HTTPException`s and returns an HTTP 400 response won't be there to catch that exception anymore.
|
||||
|
||||
@@ -138,9 +138,11 @@ participant tasks as Background tasks
|
||||
end
|
||||
dep ->> operation: Run dependency, e.g. DB session
|
||||
opt raise
|
||||
operation -->> handler: Raise HTTPException
|
||||
operation -->> dep: Raise HTTPException
|
||||
dep -->> handler: Auto forward exception
|
||||
handler -->> client: HTTP error response
|
||||
operation -->> dep: Raise other exception
|
||||
dep -->> handler: Auto forward exception
|
||||
end
|
||||
operation ->> client: Return response to client
|
||||
Note over client,operation: Response is already sent, can't change it anymore
|
||||
@@ -162,9 +164,9 @@ participant tasks as Background tasks
|
||||
After one of those responses is sent, no other response can be sent.
|
||||
|
||||
!!! tip
|
||||
This diagram shows `HTTPException`, but you could also raise any other exception for which you create a [Custom Exception Handler](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}. And that exception would be handled by that custom exception handler instead of the dependency exit code.
|
||||
This diagram shows `HTTPException`, but you could also raise any other exception for which you create a [Custom Exception Handler](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}.
|
||||
|
||||
But if you raise an exception that is not handled by the exception handlers, it will be handled by the exit code of the dependency.
|
||||
If you raise any exception, it will be passed to the dependencies with yield, including `HTTPException`, and then **again** to the exception handlers. If there's no exception handler for that exception, it will then be handled by the default internal `ServerErrorMiddleware`, returning a 500 HTTP status code, to let the client know that there was an error in the server.
|
||||
|
||||
## Context Managers
|
||||
|
||||
|
||||
@@ -52,6 +52,12 @@
|
||||
<img class="sponsor-image" src="/img/sponsors/dropbase-banner.svg" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a title="https://striveworks.us/careers" style="display: block; position: relative;" href="https://striveworks.us/careers?utm_source=fastapi&utm_medium=small_banner&utm_campaign=feb_march#openings" target="_blank">
|
||||
<span class="sponsor-badge">sponsor</span>
|
||||
<img class="sponsor-image" src="/img/sponsors/striveworks-banner.png" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
|
||||
|
||||
__version__ = "0.73.0"
|
||||
__version__ = "0.74.0"
|
||||
|
||||
from starlette import status as status
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ from enum import Enum
|
||||
from typing import Any, Callable, Coroutine, Dict, List, Optional, Sequence, Type, Union
|
||||
|
||||
from fastapi import routing
|
||||
from fastapi.concurrency import AsyncExitStack
|
||||
from fastapi.datastructures import Default, DefaultPlaceholder
|
||||
from fastapi.encoders import DictIntStrAny, SetIntStr
|
||||
from fastapi.exception_handlers import (
|
||||
@@ -11,6 +10,7 @@ from fastapi.exception_handlers import (
|
||||
)
|
||||
from fastapi.exceptions import RequestValidationError
|
||||
from fastapi.logger import logger
|
||||
from fastapi.middleware.asyncexitstack import AsyncExitStackMiddleware
|
||||
from fastapi.openapi.docs import (
|
||||
get_redoc_html,
|
||||
get_swagger_ui_html,
|
||||
@@ -21,8 +21,9 @@ from fastapi.params import Depends
|
||||
from fastapi.types import DecoratedCallable
|
||||
from starlette.applications import Starlette
|
||||
from starlette.datastructures import State
|
||||
from starlette.exceptions import HTTPException
|
||||
from starlette.exceptions import ExceptionMiddleware, HTTPException
|
||||
from starlette.middleware import Middleware
|
||||
from starlette.middleware.errors import ServerErrorMiddleware
|
||||
from starlette.requests import Request
|
||||
from starlette.responses import HTMLResponse, JSONResponse, Response
|
||||
from starlette.routing import BaseRoute
|
||||
@@ -134,6 +135,55 @@ class FastAPI(Starlette):
|
||||
self.openapi_schema: Optional[Dict[str, Any]] = None
|
||||
self.setup()
|
||||
|
||||
def build_middleware_stack(self) -> ASGIApp:
|
||||
# Duplicate/override from Starlette to add AsyncExitStackMiddleware
|
||||
# inside of ExceptionMiddleware, inside of custom user middlewares
|
||||
debug = self.debug
|
||||
error_handler = None
|
||||
exception_handlers = {}
|
||||
|
||||
for key, value in self.exception_handlers.items():
|
||||
if key in (500, Exception):
|
||||
error_handler = value
|
||||
else:
|
||||
exception_handlers[key] = value
|
||||
|
||||
middleware = (
|
||||
[Middleware(ServerErrorMiddleware, handler=error_handler, debug=debug)]
|
||||
+ self.user_middleware
|
||||
+ [
|
||||
Middleware(
|
||||
ExceptionMiddleware, handlers=exception_handlers, debug=debug
|
||||
),
|
||||
# Add FastAPI-specific AsyncExitStackMiddleware for dependencies with
|
||||
# contextvars.
|
||||
# This needs to happen after user middlewares because those create a
|
||||
# new contextvars context copy by using a new AnyIO task group.
|
||||
# The initial part of dependencies with yield is executed in the
|
||||
# FastAPI code, inside all the middlewares, but the teardown part
|
||||
# (after yield) is executed in the AsyncExitStack in this middleware,
|
||||
# if the AsyncExitStack lived outside of the custom middlewares and
|
||||
# contextvars were set in a dependency with yield in that internal
|
||||
# contextvars context, the values would not be available in the
|
||||
# outside context of the AsyncExitStack.
|
||||
# By putting the middleware and the AsyncExitStack here, inside all
|
||||
# user middlewares, the code before and after yield in dependencies
|
||||
# with yield is executed in the same contextvars context, so all values
|
||||
# set in contextvars before yield is still available after yield as
|
||||
# would be expected.
|
||||
# Additionally, by having this AsyncExitStack here, after the
|
||||
# ExceptionMiddleware, now dependencies can catch handled exceptions,
|
||||
# e.g. HTTPException, to customize the teardown code (e.g. DB session
|
||||
# rollback).
|
||||
Middleware(AsyncExitStackMiddleware),
|
||||
]
|
||||
)
|
||||
|
||||
app = self.router
|
||||
for cls, options in reversed(middleware):
|
||||
app = cls(app=app, **options)
|
||||
return app
|
||||
|
||||
def openapi(self) -> Dict[str, Any]:
|
||||
if not self.openapi_schema:
|
||||
self.openapi_schema = get_openapi(
|
||||
@@ -206,12 +256,7 @@ class FastAPI(Starlette):
|
||||
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
|
||||
if self.root_path:
|
||||
scope["root_path"] = self.root_path
|
||||
if AsyncExitStack:
|
||||
async with AsyncExitStack() as stack:
|
||||
scope["fastapi_astack"] = stack
|
||||
await super().__call__(scope, receive, send)
|
||||
else:
|
||||
await super().__call__(scope, receive, send) # pragma: no cover
|
||||
await super().__call__(scope, receive, send)
|
||||
|
||||
def add_api_route(
|
||||
self,
|
||||
|
||||
28
fastapi/middleware/asyncexitstack.py
Normal file
28
fastapi/middleware/asyncexitstack.py
Normal file
@@ -0,0 +1,28 @@
|
||||
from typing import Optional
|
||||
|
||||
from fastapi.concurrency import AsyncExitStack
|
||||
from starlette.types import ASGIApp, Receive, Scope, Send
|
||||
|
||||
|
||||
class AsyncExitStackMiddleware:
|
||||
def __init__(self, app: ASGIApp, context_name: str = "fastapi_astack") -> None:
|
||||
self.app = app
|
||||
self.context_name = context_name
|
||||
|
||||
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
|
||||
if AsyncExitStack:
|
||||
dependency_exception: Optional[Exception] = None
|
||||
async with AsyncExitStack() as stack:
|
||||
scope[self.context_name] = stack
|
||||
try:
|
||||
await self.app(scope, receive, send)
|
||||
except Exception as e:
|
||||
dependency_exception = e
|
||||
raise e
|
||||
if dependency_exception:
|
||||
# This exception was possibly handled by the dependency but it should
|
||||
# still bubble up so that the ServerErrorMiddleware can return a 500
|
||||
# or the ExceptionMiddleware can catch and handle any other exceptions
|
||||
raise dependency_exception
|
||||
else:
|
||||
await self.app(scope, receive, send) # pragma: no cover
|
||||
@@ -235,7 +235,16 @@ def test_sync_raise_other():
|
||||
assert "/sync_raise" not in errors
|
||||
|
||||
|
||||
def test_async_raise():
|
||||
def test_async_raise_raises():
|
||||
with pytest.raises(AsyncDependencyError):
|
||||
client.get("/async_raise")
|
||||
assert state["/async_raise"] == "asyncgen raise finalized"
|
||||
assert "/async_raise" in errors
|
||||
errors.clear()
|
||||
|
||||
|
||||
def test_async_raise_server_error():
|
||||
client = TestClient(app, raise_server_exceptions=False)
|
||||
response = client.get("/async_raise")
|
||||
assert response.status_code == 500, response.text
|
||||
assert state["/async_raise"] == "asyncgen raise finalized"
|
||||
@@ -270,7 +279,16 @@ def test_background_tasks():
|
||||
assert state["bg"] == "bg set - b: started b - a: started a"
|
||||
|
||||
|
||||
def test_sync_raise():
|
||||
def test_sync_raise_raises():
|
||||
with pytest.raises(SyncDependencyError):
|
||||
client.get("/sync_raise")
|
||||
assert state["/sync_raise"] == "generator raise finalized"
|
||||
assert "/sync_raise" in errors
|
||||
errors.clear()
|
||||
|
||||
|
||||
def test_sync_raise_server_error():
|
||||
client = TestClient(app, raise_server_exceptions=False)
|
||||
response = client.get("/sync_raise")
|
||||
assert response.status_code == 500, response.text
|
||||
assert state["/sync_raise"] == "generator raise finalized"
|
||||
@@ -306,7 +324,16 @@ def test_sync_sync_raise_other():
|
||||
assert "/sync_raise" not in errors
|
||||
|
||||
|
||||
def test_sync_async_raise():
|
||||
def test_sync_async_raise_raises():
|
||||
with pytest.raises(AsyncDependencyError):
|
||||
client.get("/sync_async_raise")
|
||||
assert state["/async_raise"] == "asyncgen raise finalized"
|
||||
assert "/async_raise" in errors
|
||||
errors.clear()
|
||||
|
||||
|
||||
def test_sync_async_raise_server_error():
|
||||
client = TestClient(app, raise_server_exceptions=False)
|
||||
response = client.get("/sync_async_raise")
|
||||
assert response.status_code == 500, response.text
|
||||
assert state["/async_raise"] == "asyncgen raise finalized"
|
||||
@@ -314,7 +341,16 @@ def test_sync_async_raise():
|
||||
errors.clear()
|
||||
|
||||
|
||||
def test_sync_sync_raise():
|
||||
def test_sync_sync_raise_raises():
|
||||
with pytest.raises(SyncDependencyError):
|
||||
client.get("/sync_sync_raise")
|
||||
assert state["/sync_raise"] == "generator raise finalized"
|
||||
assert "/sync_raise" in errors
|
||||
errors.clear()
|
||||
|
||||
|
||||
def test_sync_sync_raise_server_error():
|
||||
client = TestClient(app, raise_server_exceptions=False)
|
||||
response = client.get("/sync_sync_raise")
|
||||
assert response.status_code == 500, response.text
|
||||
assert state["/sync_raise"] == "generator raise finalized"
|
||||
|
||||
51
tests/test_dependency_contextvars.py
Normal file
51
tests/test_dependency_contextvars.py
Normal file
@@ -0,0 +1,51 @@
|
||||
from contextvars import ContextVar
|
||||
from typing import Any, Awaitable, Callable, Dict, Optional
|
||||
|
||||
from fastapi import Depends, FastAPI, Request, Response
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
legacy_request_state_context_var: ContextVar[Optional[Dict[str, Any]]] = ContextVar(
|
||||
"legacy_request_state_context_var", default=None
|
||||
)
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
async def set_up_request_state_dependency():
|
||||
request_state = {"user": "deadpond"}
|
||||
contextvar_token = legacy_request_state_context_var.set(request_state)
|
||||
yield request_state
|
||||
legacy_request_state_context_var.reset(contextvar_token)
|
||||
|
||||
|
||||
@app.middleware("http")
|
||||
async def custom_middleware(
|
||||
request: Request, call_next: Callable[[Request], Awaitable[Response]]
|
||||
):
|
||||
response = await call_next(request)
|
||||
response.headers["custom"] = "foo"
|
||||
return response
|
||||
|
||||
|
||||
@app.get("/user", dependencies=[Depends(set_up_request_state_dependency)])
|
||||
def get_user():
|
||||
request_state = legacy_request_state_context_var.get()
|
||||
assert request_state
|
||||
return request_state["user"]
|
||||
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
def test_dependency_contextvars():
|
||||
"""
|
||||
Check that custom middlewares don't affect the contextvar context for dependencies.
|
||||
|
||||
The code before yield and the code after yield should be run in the same contextvar
|
||||
context, so that request_state_context_var.reset(contextvar_token).
|
||||
|
||||
If they are run in a different context, that raises an error.
|
||||
"""
|
||||
response = client.get("/user")
|
||||
assert response.json() == "deadpond"
|
||||
assert response.headers["custom"] == "foo"
|
||||
71
tests/test_dependency_normal_exceptions.py
Normal file
71
tests/test_dependency_normal_exceptions.py
Normal file
@@ -0,0 +1,71 @@
|
||||
import pytest
|
||||
from fastapi import Body, Depends, FastAPI, HTTPException
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
initial_fake_database = {"rick": "Rick Sanchez"}
|
||||
|
||||
fake_database = initial_fake_database.copy()
|
||||
|
||||
initial_state = {"except": False, "finally": False}
|
||||
|
||||
state = initial_state.copy()
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
async def get_database():
|
||||
temp_database = fake_database.copy()
|
||||
try:
|
||||
yield temp_database
|
||||
fake_database.update(temp_database)
|
||||
except HTTPException:
|
||||
state["except"] = True
|
||||
finally:
|
||||
state["finally"] = True
|
||||
|
||||
|
||||
@app.put("/invalid-user/{user_id}")
|
||||
def put_invalid_user(
|
||||
user_id: str, name: str = Body(...), db: dict = Depends(get_database)
|
||||
):
|
||||
db[user_id] = name
|
||||
raise HTTPException(status_code=400, detail="Invalid user")
|
||||
|
||||
|
||||
@app.put("/user/{user_id}")
|
||||
def put_user(user_id: str, name: str = Body(...), db: dict = Depends(get_database)):
|
||||
db[user_id] = name
|
||||
return {"message": "OK"}
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def reset_state_and_db():
|
||||
global fake_database
|
||||
global state
|
||||
fake_database = initial_fake_database.copy()
|
||||
state = initial_state.copy()
|
||||
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
def test_dependency_gets_exception():
|
||||
assert state["except"] is False
|
||||
assert state["finally"] is False
|
||||
response = client.put("/invalid-user/rick", json="Morty")
|
||||
assert response.status_code == 400, response.text
|
||||
assert response.json() == {"detail": "Invalid user"}
|
||||
assert state["except"] is True
|
||||
assert state["finally"] is True
|
||||
assert fake_database["rick"] == "Rick Sanchez"
|
||||
|
||||
|
||||
def test_dependency_no_exception():
|
||||
assert state["except"] is False
|
||||
assert state["finally"] is False
|
||||
response = client.put("/user/rick", json="Morty")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {"message": "OK"}
|
||||
assert state["except"] is False
|
||||
assert state["finally"] is True
|
||||
assert fake_database["rick"] == "Morty"
|
||||
@@ -1,3 +1,4 @@
|
||||
import pytest
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from fastapi.exceptions import RequestValidationError
|
||||
from fastapi.testclient import TestClient
|
||||
@@ -12,10 +13,15 @@ def request_validation_exception_handler(request, exception):
|
||||
return JSONResponse({"exception": "request-validation"})
|
||||
|
||||
|
||||
def server_error_exception_handler(request, exception):
|
||||
return JSONResponse(status_code=500, content={"exception": "server-error"})
|
||||
|
||||
|
||||
app = FastAPI(
|
||||
exception_handlers={
|
||||
HTTPException: http_exception_handler,
|
||||
RequestValidationError: request_validation_exception_handler,
|
||||
Exception: server_error_exception_handler,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -32,6 +38,11 @@ def route_with_request_validation_exception(param: int):
|
||||
pass # pragma: no cover
|
||||
|
||||
|
||||
@app.get("/server-error")
|
||||
def route_with_server_error():
|
||||
raise RuntimeError("Oops!")
|
||||
|
||||
|
||||
def test_override_http_exception():
|
||||
response = client.get("/http-exception")
|
||||
assert response.status_code == 200
|
||||
@@ -42,3 +53,15 @@ def test_override_request_validation_exception():
|
||||
response = client.get("/request-validation/invalid")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"exception": "request-validation"}
|
||||
|
||||
|
||||
def test_override_server_error_exception_raises():
|
||||
with pytest.raises(RuntimeError):
|
||||
client.get("/server-error")
|
||||
|
||||
|
||||
def test_override_server_error_exception_response():
|
||||
client = TestClient(app, raise_server_exceptions=False)
|
||||
response = client.get("/server-error")
|
||||
assert response.status_code == 500
|
||||
assert response.json() == {"exception": "server-error"}
|
||||
|
||||
Reference in New Issue
Block a user