New upstream version 0.74.1
Sandro Tosi
2 years ago
0 | 0 | name: Build Docs |
1 | 1 | on: |
2 | 2 | push: |
3 | branches: | |
4 | - master | |
3 | 5 | pull_request: |
4 | 6 | types: [opened, synchronize] |
5 | 7 | jobs: |
49 | 49 | <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> |
50 | 50 | <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> |
51 | 51 | <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> | |
52 | 53 | <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> |
53 | 54 | <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> |
54 | 55 | <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> |
0 | 0 | maintainers: |
1 | 1 | - login: tiangolo |
2 | answers: 1230 | |
3 | prs: 269 | |
2 | answers: 1237 | |
3 | prs: 280 | |
4 | 4 | avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=5cad72c846b7aba2e960546af490edc7375dafc4&v=4 |
5 | 5 | url: https://github.com/tiangolo |
6 | 6 | experts: |
7 | 7 | - login: Kludex |
8 | count: 316 | |
8 | count: 319 | |
9 | 9 | avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=3682d9b9b93bef272f379ab623dc031c8d71432e&v=4 |
10 | 10 | url: https://github.com/Kludex |
11 | 11 | - login: dmontagu |
13 | 13 | avatarUrl: https://avatars.githubusercontent.com/u/35119617?u=58ed2a45798a4339700e2f62b2e12e6e54bf0396&v=4 |
14 | 14 | url: https://github.com/dmontagu |
15 | 15 | - login: ycd |
16 | count: 219 | |
16 | count: 221 | |
17 | 17 | avatarUrl: https://avatars.githubusercontent.com/u/62724709?u=826f228edf0bab0d19ad1d5c4ba4df1047ccffef&v=4 |
18 | 18 | url: https://github.com/ycd |
19 | 19 | - login: Mause |
33 | 33 | avatarUrl: https://avatars.githubusercontent.com/u/31127044?u=81a84af39c89b898b0fbc5a04e8834f60f23e55a&v=4 |
34 | 34 | url: https://github.com/ArcLightSlavik |
35 | 35 | - login: raphaelauv |
36 | count: 67 | |
36 | count: 68 | |
37 | 37 | avatarUrl: https://avatars.githubusercontent.com/u/10202690?u=e6f86f5c0c3026a15d6b51792fa3e532b12f1371&v=4 |
38 | 38 | url: https://github.com/raphaelauv |
39 | 39 | - login: falkben |
56 | 56 | count: 38 |
57 | 57 | avatarUrl: https://avatars.githubusercontent.com/u/11836741?u=8bd5ef7e62fe6a82055e33c4c0e0a7879ff8cfb6&v=4 |
58 | 58 | 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 | |
59 | 63 | - login: prostomarkeloff |
60 | 64 | count: 33 |
61 | 65 | avatarUrl: https://avatars.githubusercontent.com/u/28061158?u=72309cc1f2e04e40fa38b29969cb4e9d3f722e7b&v=4 |
62 | 66 | 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 | |
67 | 67 | - login: krishnardt |
68 | 68 | count: 31 |
69 | 69 | avatarUrl: https://avatars.githubusercontent.com/u/31960541?u=47f4829c77f4962ab437ffb7995951e41eeebe9b&v=4 |
70 | 70 | 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 | |
71 | 75 | - login: wshayes |
72 | 76 | count: 29 |
73 | 77 | avatarUrl: https://avatars.githubusercontent.com/u/365303?u=07ca03c5ee811eb0920e633cc3c3db73dbec1aa5&v=4 |
76 | 80 | count: 29 |
77 | 81 | avatarUrl: https://avatars.githubusercontent.com/u/1144727?u=85c025e3fcc7bd79a5665c63ee87cdf8aae13374&v=4 |
78 | 82 | 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 | |
83 | 87 | - login: ghandic |
84 | 88 | count: 25 |
85 | 89 | avatarUrl: https://avatars.githubusercontent.com/u/23500353?u=e2e1d736f924d9be81e8bfc565b6d8836ba99773&v=4 |
88 | 92 | count: 25 |
89 | 93 | avatarUrl: https://avatars.githubusercontent.com/u/43723790?u=9bcce836bbce55835291c5b2ac93a4e311f4b3c3&v=4 |
90 | 94 | 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 | |
95 | 99 | - login: SirTelemak |
96 | 100 | count: 24 |
97 | 101 | avatarUrl: https://avatars.githubusercontent.com/u/9435877?u=719327b7d2c4c62212456d771bfa7c6b8dbb9eac&v=4 |
98 | 102 | 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 | |
103 | 103 | - login: acnebs |
104 | 104 | count: 22 |
105 | 105 | avatarUrl: https://avatars.githubusercontent.com/u/9054108?u=c27e50269f1ef8ea950cc6f0268c8ec5cebbe9c9&v=4 |
128 | 128 | count: 17 |
129 | 129 | avatarUrl: https://avatars.githubusercontent.com/u/28262306?u=66ee21316275ef356081c2efc4ed7a4572e690dc&v=4 |
130 | 130 | 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 | |
131 | 135 | - login: waynerv |
132 | 136 | count: 16 |
133 | 137 | avatarUrl: https://avatars.githubusercontent.com/u/39515546?u=ec35139777597cdbbbddda29bf8b9d4396b429a9&v=4 |
134 | 138 | 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 | |
139 | 139 | - login: dstlny |
140 | count: 14 | |
140 | count: 16 | |
141 | 141 | avatarUrl: https://avatars.githubusercontent.com/u/41964673?u=9f2174f9d61c15c6e3a4c9e3aeee66f711ce311f&v=4 |
142 | 142 | 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 | |
143 | 151 | - login: haizaar |
144 | 152 | count: 13 |
145 | 153 | avatarUrl: https://avatars.githubusercontent.com/u/58201?u=4f1f9843d69433ca0d380d95146cfe119e5fdac4&v=4 |
146 | 154 | 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 | |
147 | 159 | - login: David-Lor |
148 | 160 | count: 12 |
149 | 161 | avatarUrl: https://avatars.githubusercontent.com/u/17401854?u=474680c02b94cba810cb9032fb7eb787d9cc9d22&v=4 |
172 | 184 | count: 10 |
173 | 185 | avatarUrl: https://avatars.githubusercontent.com/u/20441825?u=ee1e59446b98f8ec2363caeda4c17164d0d9cc7d&v=4 |
174 | 186 | 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 | |
179 | 187 | - login: oligond |
180 | 188 | count: 10 |
181 | 189 | avatarUrl: https://avatars.githubusercontent.com/u/2858306?u=1bb1182a5944e93624b7fb26585f22c8f7a9d76e&v=4 |
182 | 190 | url: https://github.com/oligond |
183 | 191 | 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 | |
192 | 196 | - login: jgould22 |
193 | count: 4 | |
197 | count: 10 | |
194 | 198 | avatarUrl: https://avatars.githubusercontent.com/u/4335847?u=ed77f67e0bb069084639b24d812dbb2a2b1dc554&v=4 |
195 | 199 | 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 | |
200 | 224 | - login: Kludex |
201 | 225 | count: 3 |
202 | 226 | avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=3682d9b9b93bef272f379ab623dc031c8d71432e&v=4 |
203 | 227 | 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 | |
208 | 228 | top_contributors: |
209 | 229 | - login: waynerv |
210 | 230 | count: 25 |
218 | 238 | count: 16 |
219 | 239 | avatarUrl: https://avatars.githubusercontent.com/u/35119617?u=58ed2a45798a4339700e2f62b2e12e6e54bf0396&v=4 |
220 | 240 | 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 | |
221 | 245 | - login: euri10 |
222 | 246 | count: 13 |
223 | 247 | avatarUrl: https://avatars.githubusercontent.com/u/1104190?u=321a2e953e6645a7d09b732786c7a8061e0f8a8b&v=4 |
224 | 248 | 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 | |
229 | 249 | - login: mariacamilagl |
230 | 250 | count: 12 |
231 | 251 | avatarUrl: https://avatars.githubusercontent.com/u/11489395?u=4adb6986bf3debfc2b8216ae701f2bd47d73da7d&v=4 |
284 | 304 | url: https://github.com/NinaHwang |
285 | 305 | top_reviewers: |
286 | 306 | - login: Kludex |
287 | count: 91 | |
307 | count: 93 | |
288 | 308 | avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=3682d9b9b93bef272f379ab623dc031c8d71432e&v=4 |
289 | 309 | url: https://github.com/Kludex |
290 | 310 | - login: waynerv |
300 | 320 | avatarUrl: https://avatars.githubusercontent.com/u/41147016?u=55010621aece725aa702270b54fed829b6a1fe60&v=4 |
301 | 321 | url: https://github.com/tokusumi |
302 | 322 | - login: ycd |
303 | count: 44 | |
323 | count: 45 | |
304 | 324 | avatarUrl: https://avatars.githubusercontent.com/u/62724709?u=826f228edf0bab0d19ad1d5c4ba4df1047ccffef&v=4 |
305 | 325 | url: https://github.com/ycd |
306 | 326 | - login: AdrianDeAnda |
311 | 331 | count: 31 |
312 | 332 | avatarUrl: https://avatars.githubusercontent.com/u/31127044?u=81a84af39c89b898b0fbc5a04e8834f60f23e55a&v=4 |
313 | 333 | 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 | |
314 | 338 | - login: dmontagu |
315 | 339 | count: 23 |
316 | 340 | avatarUrl: https://avatars.githubusercontent.com/u/35119617?u=58ed2a45798a4339700e2f62b2e12e6e54bf0396&v=4 |
317 | 341 | url: https://github.com/dmontagu |
318 | 342 | - login: cassiobotaro |
319 | count: 22 | |
343 | count: 23 | |
320 | 344 | avatarUrl: https://avatars.githubusercontent.com/u/3127847?u=b0a652331da17efeb85cd6e3a4969182e5004804&v=4 |
321 | 345 | url: https://github.com/cassiobotaro |
322 | 346 | - login: komtaki |
335 | 359 | count: 16 |
336 | 360 | avatarUrl: https://avatars.githubusercontent.com/u/21978760?v=4 |
337 | 361 | 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 | |
338 | 366 | - login: SwftAlpc |
339 | 367 | count: 16 |
340 | 368 | avatarUrl: https://avatars.githubusercontent.com/u/52768429?u=6a3aa15277406520ad37f6236e89466ed44bc5b8&v=4 |
355 | 383 | count: 15 |
356 | 384 | avatarUrl: https://avatars.githubusercontent.com/u/63476957?u=6c86e59b48e0394d4db230f37fc9ad4d7e2c27c7&v=4 |
357 | 385 | url: https://github.com/delhi09 |
358 | - login: lsglucas | |
386 | - login: rjNemo | |
359 | 387 | 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 | |
364 | 388 | avatarUrl: https://avatars.githubusercontent.com/u/56785022?u=d5c3a02567c8649e146fcfc51b6060ccaf8adef8&v=4 |
365 | 389 | url: https://github.com/rjNemo |
366 | 390 | - login: RunningIkkyu |
367 | 391 | count: 12 |
368 | 392 | avatarUrl: https://avatars.githubusercontent.com/u/31848542?u=706e1ee3f248245f2d68b976d149d06fd5a2010d&v=4 |
369 | 393 | 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 | |
370 | 398 | - login: sh0nk |
371 | 399 | count: 12 |
372 | 400 | avatarUrl: https://avatars.githubusercontent.com/u/6478810?u=af15d724875cec682ed8088a86d36b2798f981c0&v=4 |
373 | 401 | 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 | |
378 | 402 | - login: mariacamilagl |
379 | 403 | count: 10 |
380 | 404 | avatarUrl: https://avatars.githubusercontent.com/u/11489395?u=4adb6986bf3debfc2b8216ae701f2bd47d73da7d&v=4 |
399 | 423 | count: 9 |
400 | 424 | avatarUrl: https://avatars.githubusercontent.com/u/49435654?v=4 |
401 | 425 | 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 | |
402 | 430 | - login: bezaca |
403 | 431 | count: 9 |
404 | 432 | avatarUrl: https://avatars.githubusercontent.com/u/69092910?u=4ac58eab99bd37d663f3d23551df96d4fbdbf760&v=4 |
443 | 471 | count: 7 |
444 | 472 | avatarUrl: https://avatars.githubusercontent.com/u/34248814?v=4 |
445 | 473 | 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 | |
446 | 478 | - login: jovicon |
447 | 479 | count: 6 |
448 | 480 | avatarUrl: https://avatars.githubusercontent.com/u/21287303?u=b049eac3e51a4c0473c2efe66b4d28a7d8f2b572&v=4 |
449 | 481 | 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 | |
450 | 486 | - login: diogoduartec |
451 | 487 | count: 5 |
452 | 488 | avatarUrl: https://avatars.githubusercontent.com/u/31852339?u=b50fc11c531e9b77922e19edfc9e7233d4d7b92e&v=4 |
453 | 489 | 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 | |
458 | 498 | - login: israteneda |
459 | 499 | count: 5 |
460 | 500 | avatarUrl: https://avatars.githubusercontent.com/u/20668624?u=d7b2961d330aca65fbce5bdb26a0800a3d23ed2d&v=4 |
461 | 501 | 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 |
7 | 7 | - url: https://www.dropbase.io/careers |
8 | 8 | title: Dropbase - seamlessly collect, clean, and centralize data. |
9 | 9 | 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 | |
10 | 13 | silver: |
11 | 14 | - url: https://www.deta.sh/?ref=fastapi |
12 | 15 | title: The launchpad for all your (team's) ideas |
Binary diff not shown
Binary diff not shown
0 | 0 | # Release Notes |
1 | 1 | |
2 | 2 | ## 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). | |
3 | 80 | |
4 | 81 | ## 0.73.0 |
5 | 82 |
98 | 98 | |
99 | 99 | It might be tempting to raise an `HTTPException` or similar in the exit code, after the `yield`. But **it won't work**. |
100 | 100 | |
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`). | |
102 | 102 | |
103 | 103 | 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. |
104 | 104 | |
137 | 137 | end |
138 | 138 | dep ->> operation: Run dependency, e.g. DB session |
139 | 139 | opt raise |
140 | operation -->> handler: Raise HTTPException | |
140 | operation -->> dep: Raise HTTPException | |
141 | dep -->> handler: Auto forward exception | |
141 | 142 | handler -->> client: HTTP error response |
142 | 143 | operation -->> dep: Raise other exception |
144 | dep -->> handler: Auto forward exception | |
143 | 145 | end |
144 | 146 | operation ->> client: Return response to client |
145 | 147 | Note over client,operation: Response is already sent, can't change it anymore |
161 | 163 | After one of those responses is sent, no other response can be sent. |
162 | 164 | |
163 | 165 | !!! 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. | |
167 | 169 | |
168 | 170 | ## Context Managers |
169 | 171 |
51 | 51 | <img class="sponsor-image" src="/img/sponsors/dropbase-banner.svg" /> |
52 | 52 | </a> |
53 | 53 | </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> | |
54 | 60 | </div> |
55 | 61 | </div> |
56 | 62 | {% endblock %} |
0 | 0 | """FastAPI framework, high performance, easy to learn, fast to code, ready for production""" |
1 | 1 | |
2 | __version__ = "0.73.0" | |
2 | __version__ = "0.74.1" | |
3 | 3 | |
4 | 4 | from starlette import status as status |
5 | 5 |
1 | 1 | from typing import Any, Callable, Coroutine, Dict, List, Optional, Sequence, Type, Union |
2 | 2 | |
3 | 3 | from fastapi import routing |
4 | from fastapi.concurrency import AsyncExitStack | |
5 | 4 | from fastapi.datastructures import Default, DefaultPlaceholder |
6 | 5 | from fastapi.encoders import DictIntStrAny, SetIntStr |
7 | 6 | from fastapi.exception_handlers import ( |
10 | 9 | ) |
11 | 10 | from fastapi.exceptions import RequestValidationError |
12 | 11 | from fastapi.logger import logger |
12 | from fastapi.middleware.asyncexitstack import AsyncExitStackMiddleware | |
13 | 13 | from fastapi.openapi.docs import ( |
14 | 14 | get_redoc_html, |
15 | 15 | get_swagger_ui_html, |
20 | 20 | from fastapi.types import DecoratedCallable |
21 | 21 | from starlette.applications import Starlette |
22 | 22 | from starlette.datastructures import State |
23 | from starlette.exceptions import HTTPException | |
23 | from starlette.exceptions import ExceptionMiddleware, HTTPException | |
24 | 24 | from starlette.middleware import Middleware |
25 | from starlette.middleware.errors import ServerErrorMiddleware | |
25 | 26 | from starlette.requests import Request |
26 | 27 | from starlette.responses import HTMLResponse, JSONResponse, Response |
27 | 28 | from starlette.routing import BaseRoute |
133 | 134 | self.openapi_schema: Optional[Dict[str, Any]] = None |
134 | 135 | self.setup() |
135 | 136 | |
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 | ||
136 | 186 | def openapi(self) -> Dict[str, Any]: |
137 | 187 | if not self.openapi_schema: |
138 | 188 | self.openapi_schema = get_openapi( |
205 | 255 | async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: |
206 | 256 | if self.root_path: |
207 | 257 | 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) | |
214 | 259 | |
215 | 260 | def add_api_route( |
216 | 261 | 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 |
12 | 12 | Optional, |
13 | 13 | Sequence, |
14 | 14 | Set, |
15 | Tuple, | |
15 | 16 | Type, |
16 | 17 | Union, |
17 | 18 | ) |
43 | 44 | from starlette.exceptions import HTTPException |
44 | 45 | from starlette.requests import Request |
45 | 46 | from starlette.responses import JSONResponse, Response |
46 | from starlette.routing import BaseRoute | |
47 | from starlette.routing import BaseRoute, Match | |
47 | 48 | from starlette.routing import Mount as Mount # noqa |
48 | 49 | from starlette.routing import ( |
49 | 50 | compile_path, |
52 | 53 | websocket_session, |
53 | 54 | ) |
54 | 55 | from starlette.status import WS_1008_POLICY_VIOLATION |
55 | from starlette.types import ASGIApp | |
56 | from starlette.types import ASGIApp, Scope | |
56 | 57 | from starlette.websockets import WebSocket |
57 | 58 | |
58 | 59 | |
294 | 295 | ) |
295 | 296 | ) |
296 | 297 | 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 | |
297 | 304 | |
298 | 305 | |
299 | 306 | class APIRoute(routing.Route): |
430 | 437 | response_model_exclude_none=self.response_model_exclude_none, |
431 | 438 | dependency_overrides_provider=self.dependency_overrides_provider, |
432 | 439 | ) |
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 | |
433 | 446 | |
434 | 447 | |
435 | 448 | class APIRouter(routing.Router): |
234 | 234 | assert "/sync_raise" not in errors |
235 | 235 | |
236 | 236 | |
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) | |
238 | 247 | response = client.get("/async_raise") |
239 | 248 | assert response.status_code == 500, response.text |
240 | 249 | assert state["/async_raise"] == "asyncgen raise finalized" |
269 | 278 | assert state["bg"] == "bg set - b: started b - a: started a" |
270 | 279 | |
271 | 280 | |
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) | |
273 | 291 | response = client.get("/sync_raise") |
274 | 292 | assert response.status_code == 500, response.text |
275 | 293 | assert state["/sync_raise"] == "generator raise finalized" |
305 | 323 | assert "/sync_raise" not in errors |
306 | 324 | |
307 | 325 | |
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) | |
309 | 336 | response = client.get("/sync_async_raise") |
310 | 337 | assert response.status_code == 500, response.text |
311 | 338 | assert state["/async_raise"] == "asyncgen raise finalized" |
313 | 340 | errors.clear() |
314 | 341 | |
315 | 342 | |
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) | |
317 | 353 | response = client.get("/sync_sync_raise") |
318 | 354 | assert response.status_code == 500, response.text |
319 | 355 | 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 | |
0 | 1 | from fastapi import FastAPI, HTTPException |
1 | 2 | from fastapi.exceptions import RequestValidationError |
2 | 3 | from fastapi.testclient import TestClient |
11 | 12 | return JSONResponse({"exception": "request-validation"}) |
12 | 13 | |
13 | 14 | |
15 | def server_error_exception_handler(request, exception): | |
16 | return JSONResponse(status_code=500, content={"exception": "server-error"}) | |
17 | ||
18 | ||
14 | 19 | app = FastAPI( |
15 | 20 | exception_handlers={ |
16 | 21 | HTTPException: http_exception_handler, |
17 | 22 | RequestValidationError: request_validation_exception_handler, |
23 | Exception: server_error_exception_handler, | |
18 | 24 | } |
19 | 25 | ) |
20 | 26 | |
31 | 37 | pass # pragma: no cover |
32 | 38 | |
33 | 39 | |
40 | @app.get("/server-error") | |
41 | def route_with_server_error(): | |
42 | raise RuntimeError("Oops!") | |
43 | ||
44 | ||
34 | 45 | def test_override_http_exception(): |
35 | 46 | response = client.get("/http-exception") |
36 | 47 | assert response.status_code == 200 |
41 | 52 | response = client.get("/request-validation/invalid") |
42 | 53 | assert response.status_code == 200 |
43 | 54 | 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() |