Codebase list fastapi / a18f576
New upstream version 0.74.1 Sandro Tosi 2 years ago
19 changed file(s) with 536 addition(s) and 107 deletion(s). Raw diff Collapse all Expand all
00 name: Build Docs
11 on:
22 push:
3 branches:
4 - master
35 pull_request:
46 types: [opened, synchronize]
57 jobs:
4949 <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>
5050 <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>
5151 <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>
52 <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>
5253 <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>
5354 <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>
5455 <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>
00 maintainers:
11 - login: tiangolo
2 answers: 1230
3 prs: 269
2 answers: 1237
3 prs: 280
44 avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=5cad72c846b7aba2e960546af490edc7375dafc4&v=4
55 url: https://github.com/tiangolo
66 experts:
77 - login: Kludex
8 count: 316
8 count: 319
99 avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=3682d9b9b93bef272f379ab623dc031c8d71432e&v=4
1010 url: https://github.com/Kludex
1111 - login: dmontagu
1313 avatarUrl: https://avatars.githubusercontent.com/u/35119617?u=58ed2a45798a4339700e2f62b2e12e6e54bf0396&v=4
1414 url: https://github.com/dmontagu
1515 - login: ycd
16 count: 219
16 count: 221
1717 avatarUrl: https://avatars.githubusercontent.com/u/62724709?u=826f228edf0bab0d19ad1d5c4ba4df1047ccffef&v=4
1818 url: https://github.com/ycd
1919 - login: Mause
3333 avatarUrl: https://avatars.githubusercontent.com/u/31127044?u=81a84af39c89b898b0fbc5a04e8834f60f23e55a&v=4
3434 url: https://github.com/ArcLightSlavik
3535 - login: raphaelauv
36 count: 67
36 count: 68
3737 avatarUrl: https://avatars.githubusercontent.com/u/10202690?u=e6f86f5c0c3026a15d6b51792fa3e532b12f1371&v=4
3838 url: https://github.com/raphaelauv
3939 - login: falkben
5656 count: 38
5757 avatarUrl: https://avatars.githubusercontent.com/u/11836741?u=8bd5ef7e62fe6a82055e33c4c0e0a7879ff8cfb6&v=4
5858 url: https://github.com/includeamin
59 - login: STeveShary
60 count: 37
61 avatarUrl: https://avatars.githubusercontent.com/u/5167622?u=de8f597c81d6336fcebc37b32dfd61a3f877160c&v=4
62 url: https://github.com/STeveShary
5963 - login: prostomarkeloff
6064 count: 33
6165 avatarUrl: https://avatars.githubusercontent.com/u/28061158?u=72309cc1f2e04e40fa38b29969cb4e9d3f722e7b&v=4
6266 url: https://github.com/prostomarkeloff
63 - login: STeveShary
64 count: 32
65 avatarUrl: https://avatars.githubusercontent.com/u/5167622?u=de8f597c81d6336fcebc37b32dfd61a3f877160c&v=4
66 url: https://github.com/STeveShary
6767 - login: krishnardt
6868 count: 31
6969 avatarUrl: https://avatars.githubusercontent.com/u/31960541?u=47f4829c77f4962ab437ffb7995951e41eeebe9b&v=4
7070 url: https://github.com/krishnardt
71 - login: adriangb
72 count: 30
73 avatarUrl: https://avatars.githubusercontent.com/u/1755071?u=81f0262df34e1460ca546fbd0c211169c2478532&v=4
74 url: https://github.com/adriangb
7175 - login: wshayes
7276 count: 29
7377 avatarUrl: https://avatars.githubusercontent.com/u/365303?u=07ca03c5ee811eb0920e633cc3c3db73dbec1aa5&v=4
7680 count: 29
7781 avatarUrl: https://avatars.githubusercontent.com/u/1144727?u=85c025e3fcc7bd79a5665c63ee87cdf8aae13374&v=4
7882 url: https://github.com/frankie567
79 - login: adriangb
80 count: 28
81 avatarUrl: https://avatars.githubusercontent.com/u/1755071?u=81f0262df34e1460ca546fbd0c211169c2478532&v=4
82 url: https://github.com/adriangb
83 - login: chbndrhnns
84 count: 25
85 avatarUrl: https://avatars.githubusercontent.com/u/7534547?v=4
86 url: https://github.com/chbndrhnns
8387 - login: ghandic
8488 count: 25
8589 avatarUrl: https://avatars.githubusercontent.com/u/23500353?u=e2e1d736f924d9be81e8bfc565b6d8836ba99773&v=4
8892 count: 25
8993 avatarUrl: https://avatars.githubusercontent.com/u/43723790?u=9bcce836bbce55835291c5b2ac93a4e311f4b3c3&v=4
9094 url: https://github.com/dbanty
91 - login: chbndrhnns
92 count: 24
93 avatarUrl: https://avatars.githubusercontent.com/u/7534547?v=4
94 url: https://github.com/chbndrhnns
95 - login: panla
96 count: 25
97 avatarUrl: https://avatars.githubusercontent.com/u/41326348?u=ba2fda6b30110411ecbf406d187907e2b420ac19&v=4
98 url: https://github.com/panla
9599 - login: SirTelemak
96100 count: 24
97101 avatarUrl: https://avatars.githubusercontent.com/u/9435877?u=719327b7d2c4c62212456d771bfa7c6b8dbb9eac&v=4
98102 url: https://github.com/SirTelemak
99 - login: panla
100 count: 23
101 avatarUrl: https://avatars.githubusercontent.com/u/41326348?u=ba2fda6b30110411ecbf406d187907e2b420ac19&v=4
102 url: https://github.com/panla
103103 - login: acnebs
104104 count: 22
105105 avatarUrl: https://avatars.githubusercontent.com/u/9054108?u=c27e50269f1ef8ea950cc6f0268c8ec5cebbe9c9&v=4
128128 count: 17
129129 avatarUrl: https://avatars.githubusercontent.com/u/28262306?u=66ee21316275ef356081c2efc4ed7a4572e690dc&v=4
130130 url: https://github.com/nkhitrov
131 - login: acidjunk
132 count: 16
133 avatarUrl: https://avatars.githubusercontent.com/u/685002?u=b5094ab4527fc84b006c0ac9ff54367bdebb2267&v=4
134 url: https://github.com/acidjunk
131135 - login: waynerv
132136 count: 16
133137 avatarUrl: https://avatars.githubusercontent.com/u/39515546?u=ec35139777597cdbbbddda29bf8b9d4396b429a9&v=4
134138 url: https://github.com/waynerv
135 - login: acidjunk
136 count: 15
137 avatarUrl: https://avatars.githubusercontent.com/u/685002?u=b5094ab4527fc84b006c0ac9ff54367bdebb2267&v=4
138 url: https://github.com/acidjunk
139139 - login: dstlny
140 count: 14
140 count: 16
141141 avatarUrl: https://avatars.githubusercontent.com/u/41964673?u=9f2174f9d61c15c6e3a4c9e3aeee66f711ce311f&v=4
142142 url: https://github.com/dstlny
143 - login: jgould22
144 count: 14
145 avatarUrl: https://avatars.githubusercontent.com/u/4335847?u=ed77f67e0bb069084639b24d812dbb2a2b1dc554&v=4
146 url: https://github.com/jgould22
147 - login: harunyasar
148 count: 14
149 avatarUrl: https://avatars.githubusercontent.com/u/1765494?u=5b1ab7c582db4b4016fa31affe977d10af108ad4&v=4
150 url: https://github.com/harunyasar
143151 - login: haizaar
144152 count: 13
145153 avatarUrl: https://avatars.githubusercontent.com/u/58201?u=4f1f9843d69433ca0d380d95146cfe119e5fdac4&v=4
146154 url: https://github.com/haizaar
155 - login: hellocoldworld
156 count: 12
157 avatarUrl: https://avatars.githubusercontent.com/u/47581948?v=4
158 url: https://github.com/hellocoldworld
147159 - login: David-Lor
148160 count: 12
149161 avatarUrl: https://avatars.githubusercontent.com/u/17401854?u=474680c02b94cba810cb9032fb7eb787d9cc9d22&v=4
172184 count: 10
173185 avatarUrl: https://avatars.githubusercontent.com/u/20441825?u=ee1e59446b98f8ec2363caeda4c17164d0d9cc7d&v=4
174186 url: https://github.com/stefanondisponibile
175 - login: hellocoldworld
176 count: 10
177 avatarUrl: https://avatars.githubusercontent.com/u/47581948?v=4
178 url: https://github.com/hellocoldworld
179187 - login: oligond
180188 count: 10
181189 avatarUrl: https://avatars.githubusercontent.com/u/2858306?u=1bb1182a5944e93624b7fb26585f22c8f7a9d76e&v=4
182190 url: https://github.com/oligond
183191 last_month_active:
184 - login: insomnes
185 count: 10
186 avatarUrl: https://avatars.githubusercontent.com/u/16958893?u=f8be7088d5076d963984a21f95f44e559192d912&v=4
187 url: https://github.com/insomnes
188 - login: raphaelauv
189 count: 6
190 avatarUrl: https://avatars.githubusercontent.com/u/10202690?u=e6f86f5c0c3026a15d6b51792fa3e532b12f1371&v=4
191 url: https://github.com/raphaelauv
192 - login: harunyasar
193 count: 10
194 avatarUrl: https://avatars.githubusercontent.com/u/1765494?u=5b1ab7c582db4b4016fa31affe977d10af108ad4&v=4
195 url: https://github.com/harunyasar
192196 - login: jgould22
193 count: 4
197 count: 10
194198 avatarUrl: https://avatars.githubusercontent.com/u/4335847?u=ed77f67e0bb069084639b24d812dbb2a2b1dc554&v=4
195199 url: https://github.com/jgould22
196 - login: harunyasar
197 count: 4
198 avatarUrl: https://avatars.githubusercontent.com/u/1765494?u=5b1ab7c582db4b4016fa31affe977d10af108ad4&v=4
199 url: https://github.com/harunyasar
200 - login: rafsaf
201 count: 9
202 avatarUrl: https://avatars.githubusercontent.com/u/51059348?u=be9f06b8ced2d2b677297decc781fa8ce4f7ddbd&v=4
203 url: https://github.com/rafsaf
204 - login: STeveShary
205 count: 5
206 avatarUrl: https://avatars.githubusercontent.com/u/5167622?u=de8f597c81d6336fcebc37b32dfd61a3f877160c&v=4
207 url: https://github.com/STeveShary
208 - login: ahnaf-zamil
209 count: 3
210 avatarUrl: https://avatars.githubusercontent.com/u/57180217?u=849128b146771ace47beca5b5ff68eb82905dd6d&v=4
211 url: https://github.com/ahnaf-zamil
212 - login: lucastosetto
213 count: 3
214 avatarUrl: https://avatars.githubusercontent.com/u/89307132?u=56326696423df7126c9e7c702ee58f294db69a2a&v=4
215 url: https://github.com/lucastosetto
216 - login: blokje
217 count: 3
218 avatarUrl: https://avatars.githubusercontent.com/u/851418?v=4
219 url: https://github.com/blokje
220 - login: MatthijsKok
221 count: 3
222 avatarUrl: https://avatars.githubusercontent.com/u/7658129?u=1243e32d57e13abc45e3f5235ed5b9197e0d2b41&v=4
223 url: https://github.com/MatthijsKok
200224 - login: Kludex
201225 count: 3
202226 avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=3682d9b9b93bef272f379ab623dc031c8d71432e&v=4
203227 url: https://github.com/Kludex
204 - login: panla
205 count: 3
206 avatarUrl: https://avatars.githubusercontent.com/u/41326348?u=ba2fda6b30110411ecbf406d187907e2b420ac19&v=4
207 url: https://github.com/panla
208228 top_contributors:
209229 - login: waynerv
210230 count: 25
218238 count: 16
219239 avatarUrl: https://avatars.githubusercontent.com/u/35119617?u=58ed2a45798a4339700e2f62b2e12e6e54bf0396&v=4
220240 url: https://github.com/dmontagu
241 - login: jaystone776
242 count: 15
243 avatarUrl: https://avatars.githubusercontent.com/u/11191137?u=299205a95e9b6817a43144a48b643346a5aac5cc&v=4
244 url: https://github.com/jaystone776
221245 - login: euri10
222246 count: 13
223247 avatarUrl: https://avatars.githubusercontent.com/u/1104190?u=321a2e953e6645a7d09b732786c7a8061e0f8a8b&v=4
224248 url: https://github.com/euri10
225 - login: jaystone776
226 count: 13
227 avatarUrl: https://avatars.githubusercontent.com/u/11191137?u=299205a95e9b6817a43144a48b643346a5aac5cc&v=4
228 url: https://github.com/jaystone776
229249 - login: mariacamilagl
230250 count: 12
231251 avatarUrl: https://avatars.githubusercontent.com/u/11489395?u=4adb6986bf3debfc2b8216ae701f2bd47d73da7d&v=4
284304 url: https://github.com/NinaHwang
285305 top_reviewers:
286306 - login: Kludex
287 count: 91
307 count: 93
288308 avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=3682d9b9b93bef272f379ab623dc031c8d71432e&v=4
289309 url: https://github.com/Kludex
290310 - login: waynerv
300320 avatarUrl: https://avatars.githubusercontent.com/u/41147016?u=55010621aece725aa702270b54fed829b6a1fe60&v=4
301321 url: https://github.com/tokusumi
302322 - login: ycd
303 count: 44
323 count: 45
304324 avatarUrl: https://avatars.githubusercontent.com/u/62724709?u=826f228edf0bab0d19ad1d5c4ba4df1047ccffef&v=4
305325 url: https://github.com/ycd
306326 - login: AdrianDeAnda
311331 count: 31
312332 avatarUrl: https://avatars.githubusercontent.com/u/31127044?u=81a84af39c89b898b0fbc5a04e8834f60f23e55a&v=4
313333 url: https://github.com/ArcLightSlavik
334 - login: cikay
335 count: 24
336 avatarUrl: https://avatars.githubusercontent.com/u/24587499?u=e772190a051ab0eaa9c8542fcff1892471638f2b&v=4
337 url: https://github.com/cikay
314338 - login: dmontagu
315339 count: 23
316340 avatarUrl: https://avatars.githubusercontent.com/u/35119617?u=58ed2a45798a4339700e2f62b2e12e6e54bf0396&v=4
317341 url: https://github.com/dmontagu
318342 - login: cassiobotaro
319 count: 22
343 count: 23
320344 avatarUrl: https://avatars.githubusercontent.com/u/3127847?u=b0a652331da17efeb85cd6e3a4969182e5004804&v=4
321345 url: https://github.com/cassiobotaro
322346 - login: komtaki
335359 count: 16
336360 avatarUrl: https://avatars.githubusercontent.com/u/21978760?v=4
337361 url: https://github.com/yanever
362 - login: lsglucas
363 count: 16
364 avatarUrl: https://avatars.githubusercontent.com/u/61513630?u=320e43fe4dc7bc6efc64e9b8f325f8075634fd20&v=4
365 url: https://github.com/lsglucas
338366 - login: SwftAlpc
339367 count: 16
340368 avatarUrl: https://avatars.githubusercontent.com/u/52768429?u=6a3aa15277406520ad37f6236e89466ed44bc5b8&v=4
355383 count: 15
356384 avatarUrl: https://avatars.githubusercontent.com/u/63476957?u=6c86e59b48e0394d4db230f37fc9ad4d7e2c27c7&v=4
357385 url: https://github.com/delhi09
358 - login: lsglucas
386 - login: rjNemo
359387 count: 14
360 avatarUrl: https://avatars.githubusercontent.com/u/61513630?u=320e43fe4dc7bc6efc64e9b8f325f8075634fd20&v=4
361 url: https://github.com/lsglucas
362 - login: rjNemo
363 count: 13
364388 avatarUrl: https://avatars.githubusercontent.com/u/56785022?u=d5c3a02567c8649e146fcfc51b6060ccaf8adef8&v=4
365389 url: https://github.com/rjNemo
366390 - login: RunningIkkyu
367391 count: 12
368392 avatarUrl: https://avatars.githubusercontent.com/u/31848542?u=706e1ee3f248245f2d68b976d149d06fd5a2010d&v=4
369393 url: https://github.com/RunningIkkyu
394 - login: yezz123
395 count: 12
396 avatarUrl: https://avatars.githubusercontent.com/u/52716203?u=636b4f79645176df4527dd45c12d5dbb5a4193cf&v=4
397 url: https://github.com/yezz123
370398 - login: sh0nk
371399 count: 12
372400 avatarUrl: https://avatars.githubusercontent.com/u/6478810?u=af15d724875cec682ed8088a86d36b2798f981c0&v=4
373401 url: https://github.com/sh0nk
374 - login: yezz123
375 count: 11
376 avatarUrl: https://avatars.githubusercontent.com/u/52716203?u=636b4f79645176df4527dd45c12d5dbb5a4193cf&v=4
377 url: https://github.com/yezz123
378402 - login: mariacamilagl
379403 count: 10
380404 avatarUrl: https://avatars.githubusercontent.com/u/11489395?u=4adb6986bf3debfc2b8216ae701f2bd47d73da7d&v=4
399423 count: 9
400424 avatarUrl: https://avatars.githubusercontent.com/u/49435654?v=4
401425 url: https://github.com/kty4119
426 - login: zy7y
427 count: 9
428 avatarUrl: https://avatars.githubusercontent.com/u/67154681?u=5d634834cc514028ea3f9115f7030b99a1f4d5a4&v=4
429 url: https://github.com/zy7y
402430 - login: bezaca
403431 count: 9
404432 avatarUrl: https://avatars.githubusercontent.com/u/69092910?u=4ac58eab99bd37d663f3d23551df96d4fbdbf760&v=4
443471 count: 7
444472 avatarUrl: https://avatars.githubusercontent.com/u/34248814?v=4
445473 url: https://github.com/krocdort
474 - login: dimaqq
475 count: 7
476 avatarUrl: https://avatars.githubusercontent.com/u/662249?v=4
477 url: https://github.com/dimaqq
446478 - login: jovicon
447479 count: 6
448480 avatarUrl: https://avatars.githubusercontent.com/u/21287303?u=b049eac3e51a4c0473c2efe66b4d28a7d8f2b572&v=4
449481 url: https://github.com/jovicon
482 - login: NinaHwang
483 count: 6
484 avatarUrl: https://avatars.githubusercontent.com/u/79563565?u=1741703bd6c8f491503354b363a86e879b4c1cab&v=4
485 url: https://github.com/NinaHwang
450486 - login: diogoduartec
451487 count: 5
452488 avatarUrl: https://avatars.githubusercontent.com/u/31852339?u=b50fc11c531e9b77922e19edfc9e7233d4d7b92e&v=4
453489 url: https://github.com/diogoduartec
454 - login: nimctl
455 count: 5
456 avatarUrl: https://avatars.githubusercontent.com/u/49960770?u=e39b11d47188744ee07b2a1c7ce1a1bdf3c80760&v=4
457 url: https://github.com/nimctl
490 - login: n25a
491 count: 5
492 avatarUrl: https://avatars.githubusercontent.com/u/49960770?u=eb3c95338741c78fff7d9d5d7ace9617e53eee4a&v=4
493 url: https://github.com/n25a
494 - login: izaguerreiro
495 count: 5
496 avatarUrl: https://avatars.githubusercontent.com/u/2241504?v=4
497 url: https://github.com/izaguerreiro
458498 - login: israteneda
459499 count: 5
460500 avatarUrl: https://avatars.githubusercontent.com/u/20668624?u=d7b2961d330aca65fbce5bdb26a0800a3d23ed2d&v=4
461501 url: https://github.com/israteneda
462 - login: juntatalor
463 count: 5
464 avatarUrl: https://avatars.githubusercontent.com/u/8134632?v=4
465 url: https://github.com/juntatalor
466 - login: SnkSynthesis
467 count: 5
468 avatarUrl: https://avatars.githubusercontent.com/u/63564282?u=0078826509dbecb2fdb543f4e881c9cd06157893&v=4
469 url: https://github.com/SnkSynthesis
470 - login: anthonycepeda
471 count: 5
472 avatarUrl: https://avatars.githubusercontent.com/u/72019805?u=892f700c79f9732211bd5221bf16eec32356a732&v=4
473 url: https://github.com/anthonycepeda
474 - login: oandersonmagalhaes
475 count: 5
476 avatarUrl: https://avatars.githubusercontent.com/u/83456692?v=4
477 url: https://github.com/oandersonmagalhaes
478 - login: qysfblog
479 count: 5
480 avatarUrl: https://avatars.githubusercontent.com/u/52229895?v=4
481 url: https://github.com/qysfblog
77 - url: https://www.dropbase.io/careers
88 title: Dropbase - seamlessly collect, clean, and centralize data.
99 img: https://fastapi.tiangolo.com/img/sponsors/dropbase.svg
10 - url: https://striveworks.us/careers?utm_source=fastapi&utm_medium=sponsor_banner&utm_campaign=feb_march#openings
11 title: https://striveworks.us/careers
12 img: https://fastapi.tiangolo.com/img/sponsors/striveworks.png
1013 silver:
1114 - url: https://www.deta.sh/?ref=fastapi
1215 title: The launchpad for all your (team's) ideas
77 - deepset-ai
88 - cryptapi
99 - DropbaseHQ
10 - Striveworks
00 # Release Notes
11
22 ## Latest Changes
3
4
5 ## 0.74.1
6
7 ### Features
8
9 * ✨ Include route in scope to allow middleware and other tools to extract its information. PR [#4603](https://github.com/tiangolo/fastapi/pull/4603) by [@tiangolo](https://github.com/tiangolo).
10
11 ## 0.74.0
12
13 ### Breaking Changes
14
15 * ✨ 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).
16
17 Dependencies with `yield` can now catch `HTTPException` and custom exceptions. For example:
18
19 ```Python
20 async def get_database():
21 with Session() as session:
22 try:
23 yield session
24 except HTTPException:
25 session.rollback()
26 raise
27 finally:
28 session.close()
29 ```
30
31 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`.
32
33 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`:
34
35 ```Python
36 async def do_something():
37 try:
38 yield something
39 finally:
40 some_cleanup()
41 ```
42
43 ...that way the `finally` block is run regardless of any exception that might happen.
44
45 ### Features
46
47 * 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.
48
49 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.
50
51 For example, this works correctly now:
52
53 ```Python
54 from contextvars import ContextVar
55 from typing import Any, Dict, Optional
56
57
58 legacy_request_state_context_var: ContextVar[Optional[Dict[str, Any]]] = ContextVar(
59 "legacy_request_state_context_var", default=None
60 )
61
62 async def set_up_request_state_dependency():
63 request_state = {"user": "deadpond"}
64 contextvar_token = legacy_request_state_context_var.set(request_state)
65 yield request_state
66 legacy_request_state_context_var.reset(contextvar_token)
67 ```
68
69 ...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.
70
71 **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.
72
73 **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).
74
75 ### Internal
76
77 * 🔧 Add Striveworks sponsor. PR [#4596](https://github.com/tiangolo/fastapi/pull/4596) by [@tiangolo](https://github.com/tiangolo).
78 * 💚 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).
79 * 👥 Update FastAPI People. PR [#4502](https://github.com/tiangolo/fastapi/pull/4502) by [@github-actions[bot]](https://github.com/apps/github-actions).
380
481 ## 0.73.0
582
9898
9999 It might be tempting to raise an `HTTPException` or similar in the exit code, after the `yield`. But **it won't work**.
100100
101 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`).
101 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`).
102102
103103 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.
104104
137137 end
138138 dep ->> operation: Run dependency, e.g. DB session
139139 opt raise
140 operation -->> handler: Raise HTTPException
140 operation -->> dep: Raise HTTPException
141 dep -->> handler: Auto forward exception
141142 handler -->> client: HTTP error response
142143 operation -->> dep: Raise other exception
144 dep -->> handler: Auto forward exception
143145 end
144146 operation ->> client: Return response to client
145147 Note over client,operation: Response is already sent, can't change it anymore
161163 After one of those responses is sent, no other response can be sent.
162164
163165 !!! tip
164 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.
165
166 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.
166 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}.
167
168 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.
167169
168170 ## Context Managers
169171
5151 <img class="sponsor-image" src="/img/sponsors/dropbase-banner.svg" />
5252 </a>
5353 </div>
54 <div class="item">
55 <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">
56 <span class="sponsor-badge">sponsor</span>
57 <img class="sponsor-image" src="/img/sponsors/striveworks-banner.png" />
58 </a>
59 </div>
5460 </div>
5561 </div>
5662 {% endblock %}
00 """FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
11
2 __version__ = "0.73.0"
2 __version__ = "0.74.1"
33
44 from starlette import status as status
55
11 from typing import Any, Callable, Coroutine, Dict, List, Optional, Sequence, Type, Union
22
33 from fastapi import routing
4 from fastapi.concurrency import AsyncExitStack
54 from fastapi.datastructures import Default, DefaultPlaceholder
65 from fastapi.encoders import DictIntStrAny, SetIntStr
76 from fastapi.exception_handlers import (
109 )
1110 from fastapi.exceptions import RequestValidationError
1211 from fastapi.logger import logger
12 from fastapi.middleware.asyncexitstack import AsyncExitStackMiddleware
1313 from fastapi.openapi.docs import (
1414 get_redoc_html,
1515 get_swagger_ui_html,
2020 from fastapi.types import DecoratedCallable
2121 from starlette.applications import Starlette
2222 from starlette.datastructures import State
23 from starlette.exceptions import HTTPException
23 from starlette.exceptions import ExceptionMiddleware, HTTPException
2424 from starlette.middleware import Middleware
25 from starlette.middleware.errors import ServerErrorMiddleware
2526 from starlette.requests import Request
2627 from starlette.responses import HTMLResponse, JSONResponse, Response
2728 from starlette.routing import BaseRoute
133134 self.openapi_schema: Optional[Dict[str, Any]] = None
134135 self.setup()
135136
137 def build_middleware_stack(self) -> ASGIApp:
138 # Duplicate/override from Starlette to add AsyncExitStackMiddleware
139 # inside of ExceptionMiddleware, inside of custom user middlewares
140 debug = self.debug
141 error_handler = None
142 exception_handlers = {}
143
144 for key, value in self.exception_handlers.items():
145 if key in (500, Exception):
146 error_handler = value
147 else:
148 exception_handlers[key] = value
149
150 middleware = (
151 [Middleware(ServerErrorMiddleware, handler=error_handler, debug=debug)]
152 + self.user_middleware
153 + [
154 Middleware(
155 ExceptionMiddleware, handlers=exception_handlers, debug=debug
156 ),
157 # Add FastAPI-specific AsyncExitStackMiddleware for dependencies with
158 # contextvars.
159 # This needs to happen after user middlewares because those create a
160 # new contextvars context copy by using a new AnyIO task group.
161 # The initial part of dependencies with yield is executed in the
162 # FastAPI code, inside all the middlewares, but the teardown part
163 # (after yield) is executed in the AsyncExitStack in this middleware,
164 # if the AsyncExitStack lived outside of the custom middlewares and
165 # contextvars were set in a dependency with yield in that internal
166 # contextvars context, the values would not be available in the
167 # outside context of the AsyncExitStack.
168 # By putting the middleware and the AsyncExitStack here, inside all
169 # user middlewares, the code before and after yield in dependencies
170 # with yield is executed in the same contextvars context, so all values
171 # set in contextvars before yield is still available after yield as
172 # would be expected.
173 # Additionally, by having this AsyncExitStack here, after the
174 # ExceptionMiddleware, now dependencies can catch handled exceptions,
175 # e.g. HTTPException, to customize the teardown code (e.g. DB session
176 # rollback).
177 Middleware(AsyncExitStackMiddleware),
178 ]
179 )
180
181 app = self.router
182 for cls, options in reversed(middleware):
183 app = cls(app=app, **options)
184 return app
185
136186 def openapi(self) -> Dict[str, Any]:
137187 if not self.openapi_schema:
138188 self.openapi_schema = get_openapi(
205255 async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
206256 if self.root_path:
207257 scope["root_path"] = self.root_path
208 if AsyncExitStack:
209 async with AsyncExitStack() as stack:
210 scope["fastapi_astack"] = stack
211 await super().__call__(scope, receive, send)
212 else:
213 await super().__call__(scope, receive, send) # pragma: no cover
258 await super().__call__(scope, receive, send)
214259
215260 def add_api_route(
216261 self,
0 from typing import Optional
1
2 from fastapi.concurrency import AsyncExitStack
3 from starlette.types import ASGIApp, Receive, Scope, Send
4
5
6 class AsyncExitStackMiddleware:
7 def __init__(self, app: ASGIApp, context_name: str = "fastapi_astack") -> None:
8 self.app = app
9 self.context_name = context_name
10
11 async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
12 if AsyncExitStack:
13 dependency_exception: Optional[Exception] = None
14 async with AsyncExitStack() as stack:
15 scope[self.context_name] = stack
16 try:
17 await self.app(scope, receive, send)
18 except Exception as e:
19 dependency_exception = e
20 raise e
21 if dependency_exception:
22 # This exception was possibly handled by the dependency but it should
23 # still bubble up so that the ServerErrorMiddleware can return a 500
24 # or the ExceptionMiddleware can catch and handle any other exceptions
25 raise dependency_exception
26 else:
27 await self.app(scope, receive, send) # pragma: no cover
1212 Optional,
1313 Sequence,
1414 Set,
15 Tuple,
1516 Type,
1617 Union,
1718 )
4344 from starlette.exceptions import HTTPException
4445 from starlette.requests import Request
4546 from starlette.responses import JSONResponse, Response
46 from starlette.routing import BaseRoute
47 from starlette.routing import BaseRoute, Match
4748 from starlette.routing import Mount as Mount # noqa
4849 from starlette.routing import (
4950 compile_path,
5253 websocket_session,
5354 )
5455 from starlette.status import WS_1008_POLICY_VIOLATION
55 from starlette.types import ASGIApp
56 from starlette.types import ASGIApp, Scope
5657 from starlette.websockets import WebSocket
5758
5859
294295 )
295296 )
296297 self.path_regex, self.path_format, self.param_convertors = compile_path(path)
298
299 def matches(self, scope: Scope) -> Tuple[Match, Scope]:
300 match, child_scope = super().matches(scope)
301 if match != Match.NONE:
302 child_scope["route"] = self
303 return match, child_scope
297304
298305
299306 class APIRoute(routing.Route):
430437 response_model_exclude_none=self.response_model_exclude_none,
431438 dependency_overrides_provider=self.dependency_overrides_provider,
432439 )
440
441 def matches(self, scope: Scope) -> Tuple[Match, Scope]:
442 match, child_scope = super().matches(scope)
443 if match != Match.NONE:
444 child_scope["route"] = self
445 return match, child_scope
433446
434447
435448 class APIRouter(routing.Router):
234234 assert "/sync_raise" not in errors
235235
236236
237 def test_async_raise():
237 def test_async_raise_raises():
238 with pytest.raises(AsyncDependencyError):
239 client.get("/async_raise")
240 assert state["/async_raise"] == "asyncgen raise finalized"
241 assert "/async_raise" in errors
242 errors.clear()
243
244
245 def test_async_raise_server_error():
246 client = TestClient(app, raise_server_exceptions=False)
238247 response = client.get("/async_raise")
239248 assert response.status_code == 500, response.text
240249 assert state["/async_raise"] == "asyncgen raise finalized"
269278 assert state["bg"] == "bg set - b: started b - a: started a"
270279
271280
272 def test_sync_raise():
281 def test_sync_raise_raises():
282 with pytest.raises(SyncDependencyError):
283 client.get("/sync_raise")
284 assert state["/sync_raise"] == "generator raise finalized"
285 assert "/sync_raise" in errors
286 errors.clear()
287
288
289 def test_sync_raise_server_error():
290 client = TestClient(app, raise_server_exceptions=False)
273291 response = client.get("/sync_raise")
274292 assert response.status_code == 500, response.text
275293 assert state["/sync_raise"] == "generator raise finalized"
305323 assert "/sync_raise" not in errors
306324
307325
308 def test_sync_async_raise():
326 def test_sync_async_raise_raises():
327 with pytest.raises(AsyncDependencyError):
328 client.get("/sync_async_raise")
329 assert state["/async_raise"] == "asyncgen raise finalized"
330 assert "/async_raise" in errors
331 errors.clear()
332
333
334 def test_sync_async_raise_server_error():
335 client = TestClient(app, raise_server_exceptions=False)
309336 response = client.get("/sync_async_raise")
310337 assert response.status_code == 500, response.text
311338 assert state["/async_raise"] == "asyncgen raise finalized"
313340 errors.clear()
314341
315342
316 def test_sync_sync_raise():
343 def test_sync_sync_raise_raises():
344 with pytest.raises(SyncDependencyError):
345 client.get("/sync_sync_raise")
346 assert state["/sync_raise"] == "generator raise finalized"
347 assert "/sync_raise" in errors
348 errors.clear()
349
350
351 def test_sync_sync_raise_server_error():
352 client = TestClient(app, raise_server_exceptions=False)
317353 response = client.get("/sync_sync_raise")
318354 assert response.status_code == 500, response.text
319355 assert state["/sync_raise"] == "generator raise finalized"
0 from contextvars import ContextVar
1 from typing import Any, Awaitable, Callable, Dict, Optional
2
3 from fastapi import Depends, FastAPI, Request, Response
4 from fastapi.testclient import TestClient
5
6 legacy_request_state_context_var: ContextVar[Optional[Dict[str, Any]]] = ContextVar(
7 "legacy_request_state_context_var", default=None
8 )
9
10 app = FastAPI()
11
12
13 async def set_up_request_state_dependency():
14 request_state = {"user": "deadpond"}
15 contextvar_token = legacy_request_state_context_var.set(request_state)
16 yield request_state
17 legacy_request_state_context_var.reset(contextvar_token)
18
19
20 @app.middleware("http")
21 async def custom_middleware(
22 request: Request, call_next: Callable[[Request], Awaitable[Response]]
23 ):
24 response = await call_next(request)
25 response.headers["custom"] = "foo"
26 return response
27
28
29 @app.get("/user", dependencies=[Depends(set_up_request_state_dependency)])
30 def get_user():
31 request_state = legacy_request_state_context_var.get()
32 assert request_state
33 return request_state["user"]
34
35
36 client = TestClient(app)
37
38
39 def test_dependency_contextvars():
40 """
41 Check that custom middlewares don't affect the contextvar context for dependencies.
42
43 The code before yield and the code after yield should be run in the same contextvar
44 context, so that request_state_context_var.reset(contextvar_token).
45
46 If they are run in a different context, that raises an error.
47 """
48 response = client.get("/user")
49 assert response.json() == "deadpond"
50 assert response.headers["custom"] == "foo"
0 import pytest
1 from fastapi import Body, Depends, FastAPI, HTTPException
2 from fastapi.testclient import TestClient
3
4 initial_fake_database = {"rick": "Rick Sanchez"}
5
6 fake_database = initial_fake_database.copy()
7
8 initial_state = {"except": False, "finally": False}
9
10 state = initial_state.copy()
11
12 app = FastAPI()
13
14
15 async def get_database():
16 temp_database = fake_database.copy()
17 try:
18 yield temp_database
19 fake_database.update(temp_database)
20 except HTTPException:
21 state["except"] = True
22 finally:
23 state["finally"] = True
24
25
26 @app.put("/invalid-user/{user_id}")
27 def put_invalid_user(
28 user_id: str, name: str = Body(...), db: dict = Depends(get_database)
29 ):
30 db[user_id] = name
31 raise HTTPException(status_code=400, detail="Invalid user")
32
33
34 @app.put("/user/{user_id}")
35 def put_user(user_id: str, name: str = Body(...), db: dict = Depends(get_database)):
36 db[user_id] = name
37 return {"message": "OK"}
38
39
40 @pytest.fixture(autouse=True)
41 def reset_state_and_db():
42 global fake_database
43 global state
44 fake_database = initial_fake_database.copy()
45 state = initial_state.copy()
46
47
48 client = TestClient(app)
49
50
51 def test_dependency_gets_exception():
52 assert state["except"] is False
53 assert state["finally"] is False
54 response = client.put("/invalid-user/rick", json="Morty")
55 assert response.status_code == 400, response.text
56 assert response.json() == {"detail": "Invalid user"}
57 assert state["except"] is True
58 assert state["finally"] is True
59 assert fake_database["rick"] == "Rick Sanchez"
60
61
62 def test_dependency_no_exception():
63 assert state["except"] is False
64 assert state["finally"] is False
65 response = client.put("/user/rick", json="Morty")
66 assert response.status_code == 200, response.text
67 assert response.json() == {"message": "OK"}
68 assert state["except"] is False
69 assert state["finally"] is True
70 assert fake_database["rick"] == "Morty"
0 import pytest
01 from fastapi import FastAPI, HTTPException
12 from fastapi.exceptions import RequestValidationError
23 from fastapi.testclient import TestClient
1112 return JSONResponse({"exception": "request-validation"})
1213
1314
15 def server_error_exception_handler(request, exception):
16 return JSONResponse(status_code=500, content={"exception": "server-error"})
17
18
1419 app = FastAPI(
1520 exception_handlers={
1621 HTTPException: http_exception_handler,
1722 RequestValidationError: request_validation_exception_handler,
23 Exception: server_error_exception_handler,
1824 }
1925 )
2026
3137 pass # pragma: no cover
3238
3339
40 @app.get("/server-error")
41 def route_with_server_error():
42 raise RuntimeError("Oops!")
43
44
3445 def test_override_http_exception():
3546 response = client.get("/http-exception")
3647 assert response.status_code == 200
4152 response = client.get("/request-validation/invalid")
4253 assert response.status_code == 200
4354 assert response.json() == {"exception": "request-validation"}
55
56
57 def test_override_server_error_exception_raises():
58 with pytest.raises(RuntimeError):
59 client.get("/server-error")
60
61
62 def test_override_server_error_exception_response():
63 client = TestClient(app, raise_server_exceptions=False)
64 response = client.get("/server-error")
65 assert response.status_code == 500
66 assert response.json() == {"exception": "server-error"}
0 import pytest
1 from fastapi import FastAPI, Request, WebSocket, WebSocketDisconnect
2 from fastapi.routing import APIRoute, APIWebSocketRoute
3 from fastapi.testclient import TestClient
4
5 app = FastAPI()
6
7
8 @app.get("/users/{user_id}")
9 async def get_user(user_id: str, request: Request):
10 route: APIRoute = request.scope["route"]
11 return {"user_id": user_id, "path": route.path}
12
13
14 @app.websocket("/items/{item_id}")
15 async def websocket_item(item_id: str, websocket: WebSocket):
16 route: APIWebSocketRoute = websocket.scope["route"]
17 await websocket.accept()
18 await websocket.send_json({"item_id": item_id, "path": route.path})
19
20
21 client = TestClient(app)
22
23
24 def test_get():
25 response = client.get("/users/rick")
26 assert response.status_code == 200, response.text
27 assert response.json() == {"user_id": "rick", "path": "/users/{user_id}"}
28
29
30 def test_invalid_method_doesnt_match():
31 response = client.post("/users/rick")
32 assert response.status_code == 405, response.text
33
34
35 def test_invalid_path_doesnt_match():
36 response = client.post("/usersx/rick")
37 assert response.status_code == 404, response.text
38
39
40 def test_websocket():
41 with client.websocket_connect("/items/portal-gun") as websocket:
42 data = websocket.receive_json()
43 assert data == {"item_id": "portal-gun", "path": "/items/{item_id}"}
44
45
46 def test_websocket_invalid_path_doesnt_match():
47 with pytest.raises(WebSocketDisconnect):
48 with client.websocket_connect("/itemsx/portal-gun") as websocket:
49 websocket.receive_json()