Compare commits

...

16 Commits

Author SHA1 Message Date
Sebastián Ramírez
ca5f60ee72 🔖 Release version 0.135.1 2026-03-01 19:17:26 +01:00
github-actions[bot]
87f75aa62c 📝 Update release notes
[skip ci]
2026-03-01 18:16:24 +00:00
Sebastián Ramírez
8a9258b169 🐛 Fix, avoid yield from a TaskGroup, only as an async context manager, closed in the request async exit stack (#15038) 2026-03-01 19:16:03 +01:00
github-actions[bot]
6038507823 📝 Update release notes
[skip ci]
2026-03-01 17:22:02 +00:00
Sebastián Ramírez
c796ba4f46 👥 Update FastAPI People - Experts (#15037)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-01 18:21:37 +01:00
github-actions[bot]
b24aa03b88 📝 Update release notes
[skip ci]
2026-03-01 12:47:40 +00:00
Aditya Giri
2c6104752a ✏️ Fix typo in docs/en/docs/_llm-test.md (#15007) 2026-03-01 13:45:56 +01:00
github-actions[bot]
e3bbeef8a2 📝 Update release notes
[skip ci]
2026-03-01 12:45:43 +00:00
github-actions[bot]
d726c8cb2b 📝 Update release notes
[skip ci]
2026-03-01 12:45:41 +00:00
Sebastián Ramírez
cf514e6d38 👥 Update FastAPI People - Contributors and Translators (#15029)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-01 13:45:21 +01:00
Sebastián Ramírez
1084d42c3e 👥 Update FastAPI GitHub topic repositories (#15036)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-01 13:45:14 +01:00
github-actions[bot]
f5c7b20933 📝 Update release notes
[skip ci]
2026-03-01 10:06:23 +00:00
Sebastián Ramírez
924a535a4f 📝 Update Skill, optimize context, trim and refactor into references (#15031) 2026-03-01 10:05:57 +00:00
Sebastián Ramírez
12ea7be0be 🔖 Release version 0.135.0 2026-03-01 10:27:00 +01:00
github-actions[bot]
4cd76acafc 📝 Update release notes
[skip ci]
2026-03-01 09:22:17 +00:00
Sebastián Ramírez
2238155844 Add support for Server Sent Events (#15030) 2026-03-01 10:21:52 +01:00
34 changed files with 2329 additions and 570 deletions

View File

@@ -1,13 +1,18 @@
tiangolo:
login: tiangolo
count: 871
count: 922
avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4
url: https://github.com/tiangolo
dependabot:
login: dependabot
count: 133
count: 142
avatarUrl: https://avatars.githubusercontent.com/in/29110?v=4
url: https://github.com/apps/dependabot
YuriiMotov:
login: YuriiMotov
count: 57
avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=bc48be95c429989224786106b027f3c5e40cc354&v=4
url: https://github.com/YuriiMotov
alejsdev:
login: alejsdev
count: 53
@@ -18,11 +23,6 @@ pre-commit-ci:
count: 50
avatarUrl: https://avatars.githubusercontent.com/in/68672?v=4
url: https://github.com/apps/pre-commit-ci
YuriiMotov:
login: YuriiMotov
count: 38
avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=bc48be95c429989224786106b027f3c5e40cc354&v=4
url: https://github.com/YuriiMotov
github-actions:
login: github-actions
count: 26
@@ -33,16 +33,16 @@ Kludex:
count: 25
avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=df8a3f06ba8f55ae1967a3e2d5ed882903a4e330&v=4
url: https://github.com/Kludex
svlandeg:
login: svlandeg
count: 18
avatarUrl: https://avatars.githubusercontent.com/u/8796347?u=556c97650c27021911b0b9447ec55e75987b0e8a&v=4
url: https://github.com/svlandeg
dmontagu:
login: dmontagu
count: 17
avatarUrl: https://avatars.githubusercontent.com/u/35119617?u=540f30c937a6450812628b9592a1dfe91bbe148e&v=4
url: https://github.com/dmontagu
svlandeg:
login: svlandeg
count: 17
avatarUrl: https://avatars.githubusercontent.com/u/8796347?u=556c97650c27021911b0b9447ec55e75987b0e8a&v=4
url: https://github.com/svlandeg
nilslindemann:
login: nilslindemann
count: 15
@@ -148,6 +148,11 @@ AlexWendland:
count: 4
avatarUrl: https://avatars.githubusercontent.com/u/3949212?u=c4c0c615e0ea33d00bfe16b779cf6ebc0f58071c&v=4
url: https://github.com/AlexWendland
valentinDruzhinin:
login: valentinDruzhinin
count: 4
avatarUrl: https://avatars.githubusercontent.com/u/12831905?u=aae1ebc675c91e8fa582df4fcc4fc4128106344d&v=4
url: https://github.com/valentinDruzhinin
divums:
login: divums
count: 3
@@ -283,11 +288,6 @@ hamidrasti:
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/43915620?v=4
url: https://github.com/hamidrasti
valentinDruzhinin:
login: valentinDruzhinin
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/12831905?u=aae1ebc675c91e8fa582df4fcc4fc4128106344d&v=4
url: https://github.com/valentinDruzhinin
kkinder:
login: kkinder
count: 2
@@ -521,7 +521,7 @@ s111d:
estebanx64:
login: estebanx64
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/10840422?u=812422ae5d6a4bc5ff331c901fc54f9ab3cecf5c&v=4
avatarUrl: https://avatars.githubusercontent.com/u/10840422?u=2ca073ee47a625e495a9573bd374ddcd7be5ec91&v=4
url: https://github.com/estebanx64
ndimares:
login: ndimares
@@ -573,3 +573,8 @@ Taoup:
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/22348542?v=4
url: https://github.com/Taoup
jonathan-fulton:
login: jonathan-fulton
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/4665111?u=bda1c12e5137bd7771a6aa24d9515b87c11da150&v=4
url: https://github.com/jonathan-fulton

View File

@@ -1,15 +1,15 @@
maintainers:
- login: tiangolo
answers: 1923
answers: 1925
avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4
url: https://github.com/tiangolo
experts:
- login: tiangolo
count: 1923
count: 1925
avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4
url: https://github.com/tiangolo
- login: YuriiMotov
count: 1107
count: 1120
avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=bc48be95c429989224786106b027f3c5e40cc354&v=4
url: https://github.com/YuriiMotov
- login: github-actions
@@ -53,7 +53,7 @@ experts:
avatarUrl: https://avatars.githubusercontent.com/u/331403?v=4
url: https://github.com/phy25
- login: JavierSanchezCastro
count: 106
count: 107
avatarUrl: https://avatars.githubusercontent.com/u/72013291?u=ae5679e6bd971d9d98cd5e76e8683f83642ba950&v=4
url: https://github.com/JavierSanchezCastro
- login: luzzodev
@@ -246,7 +246,7 @@ experts:
url: https://github.com/mattmess1221
last_month_experts:
- login: YuriiMotov
count: 20
count: 31
avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=bc48be95c429989224786106b027f3c5e40cc354&v=4
url: https://github.com/YuriiMotov
- login: Toygarmetu
@@ -254,16 +254,20 @@ last_month_experts:
avatarUrl: https://avatars.githubusercontent.com/u/92878791?u=538530cb6d5554e71f9c28709d794db9a74d23d9&v=4
url: https://github.com/Toygarmetu
- login: JavierSanchezCastro
count: 4
count: 5
avatarUrl: https://avatars.githubusercontent.com/u/72013291?u=ae5679e6bd971d9d98cd5e76e8683f83642ba950&v=4
url: https://github.com/JavierSanchezCastro
- login: tiangolo
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4
url: https://github.com/tiangolo
- login: valentinDruzhinin
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/12831905?u=aae1ebc675c91e8fa582df4fcc4fc4128106344d&v=4
url: https://github.com/valentinDruzhinin
three_months_experts:
- login: YuriiMotov
count: 77
count: 91
avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=bc48be95c429989224786106b027f3c5e40cc354&v=4
url: https://github.com/YuriiMotov
- login: tiangolo
@@ -286,14 +290,14 @@ three_months_experts:
count: 4
avatarUrl: https://avatars.githubusercontent.com/u/12831905?u=aae1ebc675c91e8fa582df4fcc4fc4128106344d&v=4
url: https://github.com/valentinDruzhinin
- login: RichieB2B
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/1461970?u=edaa57d1077705244ea5c9244f4783d94ff11f12&v=4
url: https://github.com/RichieB2B
- login: sachinh35
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/21972708?u=8560b97b8b41e175f476270b56de8a493b84f302&v=4
url: https://github.com/sachinh35
- login: RichieB2B
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/1461970?u=edaa57d1077705244ea5c9244f4783d94ff11f12&v=4
url: https://github.com/RichieB2B
- login: EmmanuelNiyonshuti
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/142030687?u=ab131d5ad4670280a978f489babe71c9bf9c1097&v=4
@@ -326,13 +330,9 @@ three_months_experts:
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/142113?u=bf10f10080026346b092633c380977b61cee0d9c&v=4
url: https://github.com/florentx
- login: JunjieAraoXiong
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/167785867?u=b69afe090c8bf5fd73f2d23fc3a887b28f68f192&v=4
url: https://github.com/JunjieAraoXiong
six_months_experts:
- login: YuriiMotov
count: 150
count: 163
avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=bc48be95c429989224786106b027f3c5e40cc354&v=4
url: https://github.com/YuriiMotov
- login: tiangolo
@@ -348,7 +348,7 @@ six_months_experts:
avatarUrl: https://avatars.githubusercontent.com/u/155247530?u=645169bc81856b7f1bd20090ecb0171a56dcbeb4&v=4
url: https://github.com/engripaye
- login: JavierSanchezCastro
count: 12
count: 13
avatarUrl: https://avatars.githubusercontent.com/u/72013291?u=ae5679e6bd971d9d98cd5e76e8683f83642ba950&v=4
url: https://github.com/JavierSanchezCastro
- login: Toygarmetu
@@ -403,14 +403,14 @@ six_months_experts:
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/1482917?u=666f39197a88cfa38b8bd78d39ef04d95c948b6b&v=4
url: https://github.com/pankeshpatel
- login: huynguyengl99
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/49433085?u=7b626115686c5d97a2a32a03119f5300e425cc9f&v=4
url: https://github.com/huynguyengl99
- login: EmmanuelNiyonshuti
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/142030687?u=ab131d5ad4670280a978f489babe71c9bf9c1097&v=4
url: https://github.com/EmmanuelNiyonshuti
- login: huynguyengl99
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/49433085?u=7b626115686c5d97a2a32a03119f5300e425cc9f&v=4
url: https://github.com/huynguyengl99
- login: davidbrochart
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/4711805?u=d39696d995a9e02ec3613ffb2f62b20b14f92f26&v=4
@@ -469,29 +469,29 @@ six_months_experts:
url: https://github.com/profatsky
one_year_experts:
- login: YuriiMotov
count: 906
count: 918
avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=bc48be95c429989224786106b027f3c5e40cc354&v=4
url: https://github.com/YuriiMotov
- login: luzzodev
count: 62
count: 60
avatarUrl: https://avatars.githubusercontent.com/u/27291415?u=5607ae1ce75c5f54f09500ca854227f7bfd2033b&v=4
url: https://github.com/luzzodev
- login: tiangolo
count: 30
count: 31
avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4
url: https://github.com/tiangolo
- login: valentinDruzhinin
count: 30
avatarUrl: https://avatars.githubusercontent.com/u/12831905?u=aae1ebc675c91e8fa582df4fcc4fc4128106344d&v=4
url: https://github.com/valentinDruzhinin
- login: alv2017
count: 19
avatarUrl: https://avatars.githubusercontent.com/u/31544722?v=4
url: https://github.com/alv2017
- login: JavierSanchezCastro
count: 18
count: 19
avatarUrl: https://avatars.githubusercontent.com/u/72013291?u=ae5679e6bd971d9d98cd5e76e8683f83642ba950&v=4
url: https://github.com/JavierSanchezCastro
- login: alv2017
count: 17
avatarUrl: https://avatars.githubusercontent.com/u/31544722?v=4
url: https://github.com/alv2017
- login: engripaye
count: 14
avatarUrl: https://avatars.githubusercontent.com/u/155247530?u=645169bc81856b7f1bd20090ecb0171a56dcbeb4&v=4
@@ -508,6 +508,10 @@ one_year_experts:
count: 8
avatarUrl: https://avatars.githubusercontent.com/u/92878791?u=538530cb6d5554e71f9c28709d794db9a74d23d9&v=4
url: https://github.com/Toygarmetu
- login: raceychan
count: 6
avatarUrl: https://avatars.githubusercontent.com/u/75417963?u=060c62870ec5a791765e63ac20d8885d11143786&v=4
url: https://github.com/raceychan
- login: yinziyan1206
count: 6
avatarUrl: https://avatars.githubusercontent.com/u/37829370?u=da44ca53aefd5c23f346fab8e9fd2e108294c179&v=4
@@ -516,10 +520,6 @@ one_year_experts:
count: 6
avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=df8a3f06ba8f55ae1967a3e2d5ed882903a4e330&v=4
url: https://github.com/Kludex
- login: raceychan
count: 6
avatarUrl: https://avatars.githubusercontent.com/u/75417963?u=060c62870ec5a791765e63ac20d8885d11143786&v=4
url: https://github.com/raceychan
- login: ceb10n
count: 5
avatarUrl: https://avatars.githubusercontent.com/u/235213?u=edcce471814a1eba9f0cdaa4cd0de18921a940a6&v=4
@@ -588,10 +588,18 @@ one_year_experts:
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/210023470?u=c25d66addf36a747bd9fab773c4a6e7b238f45d4&v=4
url: https://github.com/Jelle-tenB
- login: jgould22
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/4335847?u=ed77f67e0bb069084639b24d812dbb2a2b1dc554&v=4
url: https://github.com/jgould22
- login: Garrett-R
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/6614695?u=c128fd775002882f6e391bda5a89d1bdc5bdf45f&v=4
url: https://github.com/Garrett-R
- login: TaigoFr
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/17792131?u=372b27056ec82f1ae03d8b3f37ef55b04a7cfdd1&v=4
url: https://github.com/TaigoFr
- login: EmmanuelNiyonshuti
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/142030687?u=ab131d5ad4670280a978f489babe71c9bf9c1097&v=4
url: https://github.com/EmmanuelNiyonshuti
- login: stan-dot
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/56644812?u=a7dd773084f1c17c5f05019cc25a984e24873691&v=4
@@ -604,10 +612,6 @@ one_year_experts:
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/49433085?u=7b626115686c5d97a2a32a03119f5300e425cc9f&v=4
url: https://github.com/huynguyengl99
- login: EmmanuelNiyonshuti
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/142030687?u=ab131d5ad4670280a978f489babe71c9bf9c1097&v=4
url: https://github.com/EmmanuelNiyonshuti
- login: Ale-Cas
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/64859146?u=d52a6ecf8d83d2927e2ae270bdfcc83495dba8c9&v=4
@@ -704,7 +708,3 @@ one_year_experts:
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/24671280?u=7ea0d9bfe46cf762594d62fd2f3c6d3813c3584c&v=4
url: https://github.com/XieJiSS
- login: profatsky
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/92920843?u=81e54bb0b613c171f7cd0ab3cbb58873782c9c9c&v=4
url: https://github.com/profatsky

View File

@@ -1,191 +1,191 @@
- name: full-stack-fastapi-template
html_url: https://github.com/fastapi/full-stack-fastapi-template
stars: 41312
stars: 41789
owner_login: fastapi
owner_html_url: https://github.com/fastapi
- name: Hello-Python
html_url: https://github.com/mouredev/Hello-Python
stars: 34206
stars: 34587
owner_login: mouredev
owner_html_url: https://github.com/mouredev
- name: serve
html_url: https://github.com/jina-ai/serve
stars: 21832
stars: 21835
owner_login: jina-ai
owner_html_url: https://github.com/jina-ai
- name: HivisionIDPhotos
html_url: https://github.com/Zeyi-Lin/HivisionIDPhotos
stars: 20661
stars: 20755
owner_login: Zeyi-Lin
owner_html_url: https://github.com/Zeyi-Lin
- name: sqlmodel
html_url: https://github.com/fastapi/sqlmodel
stars: 17567
stars: 17687
owner_login: fastapi
owner_html_url: https://github.com/fastapi
- name: fastapi-best-practices
html_url: https://github.com/zhanymkanov/fastapi-best-practices
stars: 16291
stars: 16611
owner_login: zhanymkanov
owner_html_url: https://github.com/zhanymkanov
- name: Douyin_TikTok_Download_API
html_url: https://github.com/Evil0ctal/Douyin_TikTok_Download_API
stars: 16132
stars: 16474
owner_login: Evil0ctal
owner_html_url: https://github.com/Evil0ctal
- name: SurfSense
html_url: https://github.com/MODSetter/SurfSense
stars: 12723
stars: 13069
owner_login: MODSetter
owner_html_url: https://github.com/MODSetter
- name: machine-learning-zoomcamp
html_url: https://github.com/DataTalksClub/machine-learning-zoomcamp
stars: 12575
stars: 12674
owner_login: DataTalksClub
owner_html_url: https://github.com/DataTalksClub
- name: fastapi_mcp
html_url: https://github.com/tadata-org/fastapi_mcp
stars: 11478
stars: 11604
owner_login: tadata-org
owner_html_url: https://github.com/tadata-org
- name: awesome-fastapi
html_url: https://github.com/mjhea0/awesome-fastapi
stars: 11018
stars: 11119
owner_login: mjhea0
owner_html_url: https://github.com/mjhea0
- name: XHS-Downloader
html_url: https://github.com/JoeanAmier/XHS-Downloader
stars: 9938
stars: 10206
owner_login: JoeanAmier
owner_html_url: https://github.com/JoeanAmier
- name: polar
html_url: https://github.com/polarsource/polar
stars: 9348
stars: 9500
owner_login: polarsource
owner_html_url: https://github.com/polarsource
- name: FastUI
html_url: https://github.com/pydantic/FastUI
stars: 8949
stars: 8956
owner_login: pydantic
owner_html_url: https://github.com/pydantic
- name: FileCodeBox
html_url: https://github.com/vastsa/FileCodeBox
stars: 8060
stars: 8128
owner_login: vastsa
owner_html_url: https://github.com/vastsa
- name: nonebot2
html_url: https://github.com/nonebot/nonebot2
stars: 7311
stars: 7384
owner_login: nonebot
owner_html_url: https://github.com/nonebot
- name: hatchet
html_url: https://github.com/hatchet-dev/hatchet
stars: 6479
stars: 6659
owner_login: hatchet-dev
owner_html_url: https://github.com/hatchet-dev
- name: fastapi-users
html_url: https://github.com/fastapi-users/fastapi-users
stars: 5970
stars: 6024
owner_login: fastapi-users
owner_html_url: https://github.com/fastapi-users
- name: serge
html_url: https://github.com/serge-chat/serge
stars: 5751
stars: 5746
owner_login: serge-chat
owner_html_url: https://github.com/serge-chat
- name: strawberry
html_url: https://github.com/strawberry-graphql/strawberry
stars: 4598
stars: 4616
owner_login: strawberry-graphql
owner_html_url: https://github.com/strawberry-graphql
- name: devpush
html_url: https://github.com/hunvreus/devpush
stars: 4407
stars: 4515
owner_login: hunvreus
owner_html_url: https://github.com/hunvreus
- name: Kokoro-FastAPI
html_url: https://github.com/remsky/Kokoro-FastAPI
stars: 4359
stars: 4494
owner_login: remsky
owner_html_url: https://github.com/remsky
- name: Yuxi-Know
html_url: https://github.com/xerrors/Yuxi-Know
stars: 4404
owner_login: xerrors
owner_html_url: https://github.com/xerrors
- name: poem
html_url: https://github.com/poem-web/poem
stars: 4337
stars: 4359
owner_login: poem-web
owner_html_url: https://github.com/poem-web
- name: chatgpt-web-share
html_url: https://github.com/chatpire/chatgpt-web-share
stars: 4279
stars: 4274
owner_login: chatpire
owner_html_url: https://github.com/chatpire
- name: dynaconf
html_url: https://github.com/dynaconf/dynaconf
stars: 4244
stars: 4266
owner_login: dynaconf
owner_html_url: https://github.com/dynaconf
- name: Yuxi-Know
html_url: https://github.com/xerrors/Yuxi-Know
stars: 4154
owner_login: xerrors
owner_html_url: https://github.com/xerrors
- name: atrilabs-engine
html_url: https://github.com/Atri-Labs/atrilabs-engine
stars: 4086
stars: 4085
owner_login: Atri-Labs
owner_html_url: https://github.com/Atri-Labs
- name: logfire
html_url: https://github.com/pydantic/logfire
stars: 3975
stars: 4050
owner_login: pydantic
owner_html_url: https://github.com/pydantic
- name: LitServe
html_url: https://github.com/Lightning-AI/LitServe
stars: 3797
owner_login: Lightning-AI
owner_html_url: https://github.com/Lightning-AI
- name: huma
html_url: https://github.com/danielgtaylor/huma
stars: 3785
stars: 3848
owner_login: danielgtaylor
owner_html_url: https://github.com/danielgtaylor
- name: LitServe
html_url: https://github.com/Lightning-AI/LitServe
stars: 3803
owner_login: Lightning-AI
owner_html_url: https://github.com/Lightning-AI
- name: datamodel-code-generator
html_url: https://github.com/koxudaxi/datamodel-code-generator
stars: 3731
stars: 3785
owner_login: koxudaxi
owner_html_url: https://github.com/koxudaxi
- name: fastapi-admin
html_url: https://github.com/fastapi-admin/fastapi-admin
stars: 3697
stars: 3717
owner_login: fastapi-admin
owner_html_url: https://github.com/fastapi-admin
- name: farfalle
html_url: https://github.com/rashadphz/farfalle
stars: 3506
stars: 3515
owner_login: rashadphz
owner_html_url: https://github.com/rashadphz
- name: tracecat
html_url: https://github.com/TracecatHQ/tracecat
stars: 3458
stars: 3498
owner_login: TracecatHQ
owner_html_url: https://github.com/TracecatHQ
- name: mcp-context-forge
html_url: https://github.com/IBM/mcp-context-forge
stars: 3216
stars: 3347
owner_login: IBM
owner_html_url: https://github.com/IBM
- name: opyrator
html_url: https://github.com/ml-tooling/opyrator
stars: 3134
stars: 3139
owner_login: ml-tooling
owner_html_url: https://github.com/ml-tooling
- name: docarray
html_url: https://github.com/docarray/docarray
stars: 3111
stars: 3116
owner_login: docarray
owner_html_url: https://github.com/docarray
- name: fastapi-realworld-example-app
html_url: https://github.com/nsidnev/fastapi-realworld-example-app
stars: 3072
stars: 3079
owner_login: nsidnev
owner_html_url: https://github.com/nsidnev
- name: uvicorn-gunicorn-fastapi-docker
@@ -195,64 +195,64 @@
owner_html_url: https://github.com/tiangolo
- name: FastAPI-template
html_url: https://github.com/s3rius/FastAPI-template
stars: 2728
stars: 2749
owner_login: s3rius
owner_html_url: https://github.com/s3rius
- name: best-of-web-python
html_url: https://github.com/ml-tooling/best-of-web-python
stars: 2686
stars: 2695
owner_login: ml-tooling
owner_html_url: https://github.com/ml-tooling
- name: YC-Killer
html_url: https://github.com/sahibzada-allahyar/YC-Killer
stars: 2648
owner_login: sahibzada-allahyar
owner_html_url: https://github.com/sahibzada-allahyar
- name: sqladmin
html_url: https://github.com/aminalaee/sqladmin
stars: 2637
stars: 2674
owner_login: aminalaee
owner_html_url: https://github.com/aminalaee
- name: YC-Killer
html_url: https://github.com/sahibzada-allahyar/YC-Killer
stars: 2665
owner_login: sahibzada-allahyar
owner_html_url: https://github.com/sahibzada-allahyar
- name: fastapi-react
html_url: https://github.com/Buuntu/fastapi-react
stars: 2573
stars: 2585
owner_login: Buuntu
owner_html_url: https://github.com/Buuntu
- name: RasaGPT
html_url: https://github.com/paulpierre/RasaGPT
stars: 2460
stars: 2462
owner_login: paulpierre
owner_html_url: https://github.com/paulpierre
- name: supabase-py
html_url: https://github.com/supabase/supabase-py
stars: 2428
stars: 2452
owner_login: supabase
owner_html_url: https://github.com/supabase
- name: 30-Days-of-Python
html_url: https://github.com/codingforentrepreneurs/30-Days-of-Python
stars: 2347
stars: 2435
owner_login: codingforentrepreneurs
owner_html_url: https://github.com/codingforentrepreneurs
- name: NoteDiscovery
html_url: https://github.com/gamosoft/NoteDiscovery
stars: 2354
owner_login: gamosoft
owner_html_url: https://github.com/gamosoft
- name: nextpy
html_url: https://github.com/dot-agent/nextpy
stars: 2337
stars: 2335
owner_login: dot-agent
owner_html_url: https://github.com/dot-agent
- name: fastapi-utils
html_url: https://github.com/fastapiutils/fastapi-utils
stars: 2299
stars: 2306
owner_login: fastapiutils
owner_html_url: https://github.com/fastapiutils
- name: langserve
html_url: https://github.com/langchain-ai/langserve
stars: 2255
stars: 2276
owner_login: langchain-ai
owner_html_url: https://github.com/langchain-ai
- name: NoteDiscovery
html_url: https://github.com/gamosoft/NoteDiscovery
stars: 2182
owner_login: gamosoft
owner_html_url: https://github.com/gamosoft
- name: solara
html_url: https://github.com/widgetti/solara
stars: 2154
@@ -260,236 +260,236 @@
owner_html_url: https://github.com/widgetti
- name: mangum
html_url: https://github.com/Kludex/mangum
stars: 2071
stars: 2084
owner_login: Kludex
owner_html_url: https://github.com/Kludex
- name: fastapi_best_architecture
html_url: https://github.com/fastapi-practices/fastapi_best_architecture
stars: 2036
stars: 2083
owner_login: fastapi-practices
owner_html_url: https://github.com/fastapi-practices
- name: vue-fastapi-admin
html_url: https://github.com/mizhexiaoxiao/vue-fastapi-admin
stars: 1983
stars: 2012
owner_login: mizhexiaoxiao
owner_html_url: https://github.com/mizhexiaoxiao
- name: agentkit
html_url: https://github.com/BCG-X-Official/agentkit
stars: 1941
owner_login: BCG-X-Official
owner_html_url: https://github.com/BCG-X-Official
- name: fastapi-langgraph-agent-production-ready-template
html_url: https://github.com/wassim249/fastapi-langgraph-agent-production-ready-template
stars: 1920
stars: 2006
owner_login: wassim249
owner_html_url: https://github.com/wassim249
- name: agentkit
html_url: https://github.com/BCG-X-Official/agentkit
stars: 1946
owner_login: BCG-X-Official
owner_html_url: https://github.com/BCG-X-Official
- name: slowapi
html_url: https://github.com/laurentS/slowapi
stars: 1924
owner_login: laurentS
owner_html_url: https://github.com/laurentS
- name: openapi-python-client
html_url: https://github.com/openapi-generators/openapi-python-client
stars: 1900
stars: 1915
owner_login: openapi-generators
owner_html_url: https://github.com/openapi-generators
- name: manage-fastapi
html_url: https://github.com/ycd/manage-fastapi
stars: 1894
stars: 1898
owner_login: ycd
owner_html_url: https://github.com/ycd
- name: slowapi
html_url: https://github.com/laurentS/slowapi
stars: 1891
owner_login: laurentS
owner_html_url: https://github.com/laurentS
- name: piccolo
html_url: https://github.com/piccolo-orm/piccolo
stars: 1854
stars: 1864
owner_login: piccolo-orm
owner_html_url: https://github.com/piccolo-orm
- name: fastapi-cache
html_url: https://github.com/long2ice/fastapi-cache
stars: 1816
stars: 1837
owner_login: long2ice
owner_html_url: https://github.com/long2ice
- name: FastAPI-boilerplate
html_url: https://github.com/benavlabs/FastAPI-boilerplate
stars: 1820
owner_login: benavlabs
owner_html_url: https://github.com/benavlabs
- name: python-week-2022
html_url: https://github.com/rochacbruno/python-week-2022
stars: 1813
stars: 1811
owner_login: rochacbruno
owner_html_url: https://github.com/rochacbruno
- name: ormar
html_url: https://github.com/collerek/ormar
stars: 1797
owner_login: collerek
owner_html_url: https://github.com/collerek
- name: FastAPI-boilerplate
html_url: https://github.com/benavlabs/FastAPI-boilerplate
stars: 1792
owner_login: benavlabs
owner_html_url: https://github.com/benavlabs
html_url: https://github.com/ormar-orm/ormar
stars: 1801
owner_login: ormar-orm
owner_html_url: https://github.com/ormar-orm
- name: termpair
html_url: https://github.com/cs01/termpair
stars: 1727
stars: 1728
owner_login: cs01
owner_html_url: https://github.com/cs01
- name: fastapi-crudrouter
html_url: https://github.com/awtkns/fastapi-crudrouter
stars: 1677
stars: 1682
owner_login: awtkns
owner_html_url: https://github.com/awtkns
- name: langchain-serve
html_url: https://github.com/jina-ai/langchain-serve
stars: 1634
stars: 1633
owner_login: jina-ai
owner_html_url: https://github.com/jina-ai
- name: fastapi-pagination
html_url: https://github.com/uriyyo/fastapi-pagination
stars: 1607
stars: 1631
owner_login: uriyyo
owner_html_url: https://github.com/uriyyo
- name: awesome-fastapi-projects
html_url: https://github.com/Kludex/awesome-fastapi-projects
stars: 1592
owner_login: Kludex
owner_html_url: https://github.com/Kludex
- name: bracket
html_url: https://github.com/evroon/bracket
stars: 1580
stars: 1619
owner_login: evroon
owner_html_url: https://github.com/evroon
- name: awesome-fastapi-projects
html_url: https://github.com/Kludex/awesome-fastapi-projects
stars: 1596
owner_login: Kludex
owner_html_url: https://github.com/Kludex
- name: coronavirus-tracker-api
html_url: https://github.com/ExpDev07/coronavirus-tracker-api
stars: 1570
stars: 1568
owner_login: ExpDev07
owner_html_url: https://github.com/ExpDev07
- name: fastapi-amis-admin
html_url: https://github.com/amisadmin/fastapi-amis-admin
stars: 1512
stars: 1520
owner_login: amisadmin
owner_html_url: https://github.com/amisadmin
- name: fastcrud
html_url: https://github.com/benavlabs/fastcrud
stars: 1471
stars: 1487
owner_login: benavlabs
owner_html_url: https://github.com/benavlabs
- name: fastapi-boilerplate
html_url: https://github.com/teamhide/fastapi-boilerplate
stars: 1461
stars: 1465
owner_login: teamhide
owner_html_url: https://github.com/teamhide
- name: awesome-python-resources
html_url: https://github.com/DjangoEx/awesome-python-resources
stars: 1435
stars: 1441
owner_login: DjangoEx
owner_html_url: https://github.com/DjangoEx
- name: prometheus-fastapi-instrumentator
html_url: https://github.com/trallnag/prometheus-fastapi-instrumentator
stars: 1417
stars: 1433
owner_login: trallnag
owner_html_url: https://github.com/trallnag
- name: fastapi-code-generator
html_url: https://github.com/koxudaxi/fastapi-code-generator
stars: 1382
stars: 1384
owner_login: koxudaxi
owner_html_url: https://github.com/koxudaxi
- name: fastapi-scaff
html_url: https://github.com/atpuxiner/fastapi-scaff
stars: 1367
owner_login: atpuxiner
owner_html_url: https://github.com/atpuxiner
- name: fastapi-tutorial
html_url: https://github.com/liaogx/fastapi-tutorial
stars: 1360
stars: 1365
owner_login: liaogx
owner_html_url: https://github.com/liaogx
- name: WebRPA
html_url: https://github.com/pmh1314520/WebRPA
stars: 1354
owner_login: pmh1314520
owner_html_url: https://github.com/pmh1314520
- name: budgetml
html_url: https://github.com/ebhy/budgetml
stars: 1343
stars: 1344
owner_login: ebhy
owner_html_url: https://github.com/ebhy
- name: fastapi-scaff
html_url: https://github.com/atpuxiner/fastapi-scaff
stars: 1305
owner_login: atpuxiner
owner_html_url: https://github.com/atpuxiner
- name: bolt-python
html_url: https://github.com/slackapi/bolt-python
stars: 1276
stars: 1278
owner_login: slackapi
owner_html_url: https://github.com/slackapi
- name: bedrock-chat
html_url: https://github.com/aws-samples/bedrock-chat
stars: 1268
stars: 1271
owner_login: aws-samples
owner_html_url: https://github.com/aws-samples
- name: fastapi-alembic-sqlmodel-async
html_url: https://github.com/vargasjona/fastapi-alembic-sqlmodel-async
stars: 1265
stars: 1269
owner_login: vargasjona
owner_html_url: https://github.com/vargasjona
- name: fastapi_production_template
html_url: https://github.com/zhanymkanov/fastapi_production_template
stars: 1227
stars: 1231
owner_login: zhanymkanov
owner_html_url: https://github.com/zhanymkanov
- name: restish
html_url: https://github.com/rest-sh/restish
stars: 1200
stars: 1225
owner_login: rest-sh
owner_html_url: https://github.com/rest-sh
- name: langchain-extract
html_url: https://github.com/langchain-ai/langchain-extract
stars: 1183
owner_login: langchain-ai
owner_html_url: https://github.com/langchain-ai
- name: odmantic
html_url: https://github.com/art049/odmantic
stars: 1162
owner_login: art049
owner_html_url: https://github.com/art049
- name: aktools
html_url: https://github.com/akfamily/aktools
stars: 1155
stars: 1223
owner_login: akfamily
owner_html_url: https://github.com/akfamily
- name: RuoYi-Vue3-FastAPI
html_url: https://github.com/insistence/RuoYi-Vue3-FastAPI
stars: 1155
stars: 1202
owner_login: insistence
owner_html_url: https://github.com/insistence
- name: langchain-extract
html_url: https://github.com/langchain-ai/langchain-extract
stars: 1189
owner_login: langchain-ai
owner_html_url: https://github.com/langchain-ai
- name: odmantic
html_url: https://github.com/art049/odmantic
stars: 1167
owner_login: art049
owner_html_url: https://github.com/art049
- name: authx
html_url: https://github.com/yezz123/authx
stars: 1142
stars: 1144
owner_login: yezz123
owner_html_url: https://github.com/yezz123
- name: SAG
html_url: https://github.com/Zleap-AI/SAG
stars: 1110
owner_login: Zleap-AI
owner_html_url: https://github.com/Zleap-AI
- name: flock
html_url: https://github.com/Onelevenvy/flock
stars: 1069
owner_login: Onelevenvy
owner_html_url: https://github.com/Onelevenvy
- name: fastapi-observability
html_url: https://github.com/blueswen/fastapi-observability
stars: 1063
owner_login: blueswen
owner_html_url: https://github.com/blueswen
- name: enterprise-deep-research
html_url: https://github.com/SalesforceAIResearch/enterprise-deep-research
stars: 1061
stars: 1123
owner_login: SalesforceAIResearch
owner_html_url: https://github.com/SalesforceAIResearch
- name: titiler
html_url: https://github.com/developmentseed/titiler
stars: 1039
owner_login: developmentseed
owner_html_url: https://github.com/developmentseed
- name: SAG
html_url: https://github.com/Zleap-AI/SAG
stars: 1115
owner_login: Zleap-AI
owner_html_url: https://github.com/Zleap-AI
- name: FileSync
html_url: https://github.com/polius/FileSync
stars: 1111
owner_login: polius
owner_html_url: https://github.com/polius
- name: every-pdf
html_url: https://github.com/DDULDDUCK/every-pdf
stars: 1017
stars: 1093
owner_login: DDULDDUCK
owner_html_url: https://github.com/DDULDDUCK
- name: autollm
html_url: https://github.com/viddexa/autollm
stars: 1005
owner_login: viddexa
owner_html_url: https://github.com/viddexa
- name: lanarky
html_url: https://github.com/ajndkr/lanarky
stars: 995
owner_login: ajndkr
owner_html_url: https://github.com/ajndkr
- name: fastapi-observability
html_url: https://github.com/blueswen/fastapi-observability
stars: 1079
owner_login: blueswen
owner_html_url: https://github.com/blueswen
- name: flock
html_url: https://github.com/Onelevenvy/flock
stars: 1073
owner_login: Onelevenvy
owner_html_url: https://github.com/Onelevenvy
- name: titiler
html_url: https://github.com/developmentseed/titiler
stars: 1060
owner_login: developmentseed
owner_html_url: https://github.com/developmentseed

View File

@@ -31,7 +31,7 @@ hard-coders:
hasansezertasan:
login: hasansezertasan
count: 95
avatarUrl: https://avatars.githubusercontent.com/u/13135006?u=99f0b0f0fc47e88e8abb337b4447357939ef93e7&v=4
avatarUrl: https://avatars.githubusercontent.com/u/13135006?u=d36995e41a00590da64e6204cfd112e0484ac1ca&v=4
url: https://github.com/hasansezertasan
alv2017:
login: alv2017
@@ -43,21 +43,31 @@ nazarepiedady:
count: 87
avatarUrl: https://avatars.githubusercontent.com/u/31008635?u=f69ddc4ea8bda3bdfac7aa0e2ea38de282e6ee2d&v=4
url: https://github.com/nazarepiedady
tiangolo:
login: tiangolo
count: 82
avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4
url: https://github.com/tiangolo
AlertRED:
login: AlertRED
count: 81
avatarUrl: https://avatars.githubusercontent.com/u/15695000?u=f5a4944c6df443030409c88da7d7fa0b7ead985c&v=4
url: https://github.com/AlertRED
tiangolo:
login: tiangolo
count: 78
avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4
url: https://github.com/tiangolo
Alexandrhub:
login: Alexandrhub
count: 68
avatarUrl: https://avatars.githubusercontent.com/u/119126536?u=9fc0d48f3307817bafecc5861eb2168401a6cb04&v=4
url: https://github.com/Alexandrhub
nilslindemann:
login: nilslindemann
count: 67
avatarUrl: https://avatars.githubusercontent.com/u/6892179?u=1dca6a22195d6cd1ab20737c0e19a4c55d639472&v=4
url: https://github.com/nilslindemann
YuriiMotov:
login: YuriiMotov
count: 65
avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=bc48be95c429989224786106b027f3c5e40cc354&v=4
url: https://github.com/YuriiMotov
cassiobotaro:
login: cassiobotaro
count: 64
@@ -68,21 +78,11 @@ waynerv:
count: 63
avatarUrl: https://avatars.githubusercontent.com/u/39515546?u=ec35139777597cdbbbddda29bf8b9d4396b429a9&v=4
url: https://github.com/waynerv
nilslindemann:
login: nilslindemann
count: 61
avatarUrl: https://avatars.githubusercontent.com/u/6892179?u=1dca6a22195d6cd1ab20737c0e19a4c55d639472&v=4
url: https://github.com/nilslindemann
mattwang44:
login: mattwang44
count: 61
avatarUrl: https://avatars.githubusercontent.com/u/24987826?u=58e37fb3927b9124b458945ac4c97aa0f1062d85&v=4
url: https://github.com/mattwang44
YuriiMotov:
login: YuriiMotov
count: 56
avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=bc48be95c429989224786106b027f3c5e40cc354&v=4
url: https://github.com/YuriiMotov
Laineyzhang55:
login: Laineyzhang55
count: 48
@@ -128,6 +128,11 @@ solomein-sv:
count: 38
avatarUrl: https://avatars.githubusercontent.com/u/46193920?u=789927ee09cfabd752d3bd554fa6baf4850d2777&v=4
url: https://github.com/solomein-sv
mezgoodle:
login: mezgoodle
count: 38
avatarUrl: https://avatars.githubusercontent.com/u/41520940?u=4a9c765af688389d54296845d18b8f6cd6ddf09a&v=4
url: https://github.com/mezgoodle
JavierSanchezCastro:
login: JavierSanchezCastro
count: 38
@@ -138,11 +143,6 @@ alejsdev:
count: 37
avatarUrl: https://avatars.githubusercontent.com/u/90076947?u=0facffe3abf87f57a1f05fa773d1119cc5c2f6a5&v=4
url: https://github.com/alejsdev
mezgoodle:
login: mezgoodle
count: 37
avatarUrl: https://avatars.githubusercontent.com/u/41520940?u=4a9c765af688389d54296845d18b8f6cd6ddf09a&v=4
url: https://github.com/mezgoodle
stlucasgarcia:
login: stlucasgarcia
count: 36
@@ -163,21 +163,21 @@ rjNemo:
count: 34
avatarUrl: https://avatars.githubusercontent.com/u/56785022?u=d5c3a02567c8649e146fcfc51b6060ccaf8adef8&v=4
url: https://github.com/rjNemo
codingjenny:
login: codingjenny
yychanlee:
login: yychanlee
count: 34
avatarUrl: https://avatars.githubusercontent.com/u/103817302?u=3a042740dc0ff58615da0d8679230966fd7693e8&v=4
url: https://github.com/codingjenny
url: https://github.com/yychanlee
Vincy1230:
login: Vincy1230
count: 34
avatarUrl: https://avatars.githubusercontent.com/u/81342412?u=ab5e256a4077a4a91f3f9cd2115ba80780454cbe&v=4
url: https://github.com/Vincy1230
akarev0:
login: akarev0
count: 33
avatarUrl: https://avatars.githubusercontent.com/u/53393089?u=6e528bb4789d56af887ce6fe237bea4010885406&v=4
url: https://github.com/akarev0
Vincy1230:
login: Vincy1230
count: 33
avatarUrl: https://avatars.githubusercontent.com/u/81342412?u=ab5e256a4077a4a91f3f9cd2115ba80780454cbe&v=4
url: https://github.com/Vincy1230
romashevchenko:
login: romashevchenko
count: 32
@@ -313,6 +313,11 @@ sattosan:
count: 19
avatarUrl: https://avatars.githubusercontent.com/u/20574756?u=b0d8474d2938189c6954423ae8d81d91013f80a8&v=4
url: https://github.com/sattosan
maru0123-2004:
login: maru0123-2004
count: 19
avatarUrl: https://avatars.githubusercontent.com/u/43961566?u=16ed8603a4d6a4665cb6c53a7aece6f31379b769&v=4
url: https://github.com/maru0123-2004
yes0ng:
login: yes0ng
count: 19
@@ -383,11 +388,6 @@ Joao-Pedro-P-Holanda:
count: 16
avatarUrl: https://avatars.githubusercontent.com/u/110267046?u=331bd016326dac4cf3df4848f6db2dbbf8b5f978&v=4
url: https://github.com/Joao-Pedro-P-Holanda
maru0123-2004:
login: maru0123-2004
count: 16
avatarUrl: https://avatars.githubusercontent.com/u/43961566?u=16ed8603a4d6a4665cb6c53a7aece6f31379b769&v=4
url: https://github.com/maru0123-2004
JaeHyuckSa:
login: JaeHyuckSa
count: 16
@@ -683,6 +683,11 @@ JoaoGustavoRogel:
count: 9
avatarUrl: https://avatars.githubusercontent.com/u/29525510?u=a0a91251f5e43e132608d55d28ccb8645c5ea405&v=4
url: https://github.com/JoaoGustavoRogel
valentinDruzhinin:
login: valentinDruzhinin
count: 9
avatarUrl: https://avatars.githubusercontent.com/u/12831905?u=aae1ebc675c91e8fa582df4fcc4fc4128106344d&v=4
url: https://github.com/valentinDruzhinin
Yarous:
login: Yarous
count: 9
@@ -738,6 +743,11 @@ sungchan1:
count: 8
avatarUrl: https://avatars.githubusercontent.com/u/28076127?u=fadbf24840186aca639d344bb3e0ecf7ff3441cf&v=4
url: https://github.com/sungchan1
roli2py:
login: roli2py
count: 8
avatarUrl: https://avatars.githubusercontent.com/u/61126128?u=bcb7a286e435a6b9d6a84b07db1232580ee796d4&v=4
url: https://github.com/roli2py
Serrones:
login: Serrones
count: 7
@@ -783,11 +793,6 @@ d2a-raudenaerde:
count: 7
avatarUrl: https://avatars.githubusercontent.com/u/5213150?u=e6d0ef65c571c7e544fc1c7ec151c7c0a72fb6bb&v=4
url: https://github.com/d2a-raudenaerde
valentinDruzhinin:
login: valentinDruzhinin
count: 7
avatarUrl: https://avatars.githubusercontent.com/u/12831905?u=aae1ebc675c91e8fa582df4fcc4fc4128106344d&v=4
url: https://github.com/valentinDruzhinin
Zerohertz:
login: Zerohertz
count: 7
@@ -1271,7 +1276,7 @@ rafsaf:
frnsimoes:
login: frnsimoes
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/66239468?u=cba345870d8d6b25dd6d56ee18f7120581e3c573&v=4
avatarUrl: https://avatars.githubusercontent.com/u/66239468?u=98fb2a38bcac765ea9651af8a0ab8f37df86570d&v=4
url: https://github.com/frnsimoes
lieryan:
login: lieryan
@@ -1371,7 +1376,7 @@ nymous:
EpsilonRationes:
login: EpsilonRationes
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/148639079?v=4
avatarUrl: https://avatars.githubusercontent.com/u/148639079?u=5dd6c4a3f570dea44d208465fd10b709bcdfa69a&v=4
url: https://github.com/EpsilonRationes
SametEmin:
login: SametEmin
@@ -1611,7 +1616,7 @@ raphaelauv:
Fahad-Md-Kamal:
login: Fahad-Md-Kamal
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/34704464?u=141086368c5557d5a1a533fe291f21f9fc584458&v=4
avatarUrl: https://avatars.githubusercontent.com/u/34704464?u=0b1da22a9b88b14d99e7e4368eadde7ecd695366&v=4
url: https://github.com/Fahad-Md-Kamal
zxcq544:
login: zxcq544

View File

@@ -10,7 +10,7 @@ jaystone776:
url: https://github.com/jaystone776
tiangolo:
login: tiangolo
count: 31
count: 46
avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4
url: https://github.com/tiangolo
ceb10n:
@@ -33,10 +33,15 @@ SwftAlpc:
count: 23
avatarUrl: https://avatars.githubusercontent.com/u/52768429?u=6a3aa15277406520ad37f6236e89466ed44bc5b8&v=4
url: https://github.com/SwftAlpc
YuriiMotov:
login: YuriiMotov
count: 23
avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=bc48be95c429989224786106b027f3c5e40cc354&v=4
url: https://github.com/YuriiMotov
hasansezertasan:
login: hasansezertasan
count: 22
avatarUrl: https://avatars.githubusercontent.com/u/13135006?u=99f0b0f0fc47e88e8abb337b4447357939ef93e7&v=4
avatarUrl: https://avatars.githubusercontent.com/u/13135006?u=d36995e41a00590da64e6204cfd112e0484ac1ca&v=4
url: https://github.com/hasansezertasan
waynerv:
login: waynerv
@@ -58,11 +63,11 @@ Joao-Pedro-P-Holanda:
count: 14
avatarUrl: https://avatars.githubusercontent.com/u/110267046?u=331bd016326dac4cf3df4848f6db2dbbf8b5f978&v=4
url: https://github.com/Joao-Pedro-P-Holanda
codingjenny:
login: codingjenny
yychanlee:
login: yychanlee
count: 14
avatarUrl: https://avatars.githubusercontent.com/u/103817302?u=3a042740dc0ff58615da0d8679230966fd7693e8&v=4
url: https://github.com/codingjenny
url: https://github.com/yychanlee
Xewus:
login: Xewus
count: 13
@@ -108,11 +113,6 @@ pablocm83:
count: 8
avatarUrl: https://avatars.githubusercontent.com/u/28315068?u=3310fbb05bb8bfc50d2c48b6cb64ac9ee4a14549&v=4
url: https://github.com/pablocm83
YuriiMotov:
login: YuriiMotov
count: 8
avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=bc48be95c429989224786106b027f3c5e40cc354&v=4
url: https://github.com/YuriiMotov
ptt3199:
login: ptt3199
count: 7
@@ -466,7 +466,7 @@ ArtemKhymenko:
hasnatsajid:
login: hasnatsajid
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/86589885?v=4
avatarUrl: https://avatars.githubusercontent.com/u/86589885?u=3712c0362d7a4000d76022339c545cf46aa5903f&v=4
url: https://github.com/hasnatsajid
alperiox:
login: alperiox
@@ -481,7 +481,7 @@ emrhnsyts:
vusallyv:
login: vusallyv
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/85983771?u=6fb8e2f876bca06e9f846606423c8f18fb46ad06&v=4
avatarUrl: https://avatars.githubusercontent.com/u/85983771?u=620ce103dcdc47953c952bb8d402a9cf8199014d&v=4
url: https://github.com/vusallyv
jackleeio:
login: jackleeio
@@ -496,7 +496,7 @@ choi-haram:
imtiaz101325:
login: imtiaz101325
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/54007087?u=194d972b501b9ea9d2ddeaed757c492936e0121a&v=4
avatarUrl: https://avatars.githubusercontent.com/u/54007087?u=61e79c4c39798cd4d339788045dc44d4c6252bde&v=4
url: https://github.com/imtiaz101325
fabianfalon:
login: fabianfalon

View File

@@ -166,7 +166,7 @@ See sections `### Special blocks` and `### Tab blocks` in the general prompt in
//// tab | Test
The link text should get translated, the link address should remain unchaged:
The link text should get translated, the link address should remain unchanged:
* [Link to heading above](#code-snippets)
* [Internal link](index.md#installation){.internal-link target=_blank}

View File

@@ -4,6 +4,12 @@ If you want to stream data that can be structured as JSON, you should [Stream JS
But if you want to **stream pure binary data** or strings, here's how you can do it.
/// info
Added in FastAPI 0.134.0.
///
## Use Cases { #use-cases }
You could use this if you want to stream pure strings, for example directly from the output of an **AI LLM** service.

View File

@@ -7,6 +7,30 @@ hide:
## Latest Changes
## 0.135.1
### Fixes
* 🐛 Fix, avoid yield from a TaskGroup, only as an async context manager, closed in the request async exit stack. PR [#15038](https://github.com/fastapi/fastapi/pull/15038) by [@tiangolo](https://github.com/tiangolo).
### Docs
* ✏️ Fix typo in `docs/en/docs/_llm-test.md`. PR [#15007](https://github.com/fastapi/fastapi/pull/15007) by [@adityagiri3600](https://github.com/adityagiri3600).
* 📝 Update Skill, optimize context, trim and refactor into references. PR [#15031](https://github.com/fastapi/fastapi/pull/15031) by [@tiangolo](https://github.com/tiangolo).
### Internal
* 👥 Update FastAPI People - Experts. PR [#15037](https://github.com/fastapi/fastapi/pull/15037) by [@tiangolo](https://github.com/tiangolo).
* 👥 Update FastAPI People - Contributors and Translators. PR [#15029](https://github.com/fastapi/fastapi/pull/15029) by [@tiangolo](https://github.com/tiangolo).
* 👥 Update FastAPI GitHub topic repositories. PR [#15036](https://github.com/fastapi/fastapi/pull/15036) by [@tiangolo](https://github.com/tiangolo).
## 0.135.0
### Features
* ✨ Add support for Server Sent Events. PR [#15030](https://github.com/fastapi/fastapi/pull/15030) by [@tiangolo](https://github.com/tiangolo).
* New docs: [Server-Sent Events (SSE)](https://fastapi.tiangolo.com/tutorial/server-sent-events/).
## 0.134.0
### Features

View File

@@ -0,0 +1,120 @@
# Server-Sent Events (SSE) { #server-sent-events-sse }
You can stream data to the client using **Server-Sent Events** (SSE).
This is similar to [Stream JSON Lines](stream-json-lines.md){.internal-link target=_blank}, but uses the `text/event-stream` format, which is supported natively by browsers with the <a href="https://developer.mozilla.org/en-US/docs/Web/API/EventSource" class="external-link" target="_blank">`EventSource` API</a>.
/// info
Added in FastAPI 0.135.0.
///
## What are Server-Sent Events? { #what-are-server-sent-events }
SSE is a standard for streaming data from the server to the client over HTTP.
Each event is a small text block with "fields" like `data`, `event`, `id`, and `retry`, separated by blank lines.
It looks like this:
```
data: {"name": "Portal Gun", "price": 999.99}
data: {"name": "Plumbus", "price": 32.99}
```
SSE is commonly used for AI chat streaming, live notifications, logs and observability, and other cases where the server pushes updates to the client.
/// tip
If you want to stream binary data, for example video or audio, check the advanced guide: [Stream Data](../advanced/stream-data.md){.internal-link target=_blank}.
///
## Stream SSE with FastAPI { #stream-sse-with-fastapi }
To stream SSE with FastAPI, use `yield` in your *path operation function* and set `response_class=EventSourceResponse`.
Import `EventSourceResponse` from `fastapi.sse`:
{* ../../docs_src/server_sent_events/tutorial001_py310.py ln[1:25] hl[4,22] *}
Each yielded item is encoded as JSON and sent in the `data:` field of an SSE event.
If you declare the return type as `AsyncIterable[Item]`, FastAPI will use it to **validate**, **document**, and **serialize** the data using Pydantic.
{* ../../docs_src/server_sent_events/tutorial001_py310.py ln[1:25] hl[10:12,23] *}
/// tip
As Pydantic will serialize it in the **Rust** side, you will get much higher **performance** than if you don't declare a return type.
///
### Non-async *path operation functions* { #non-async-path-operation-functions }
You can also use regular `def` functions (without `async`), and use `yield` the same way.
FastAPI will make sure it's run correctly so that it doesn't block the event loop.
As in this case the function is not async, the right return type would be `Iterable[Item]`:
{* ../../docs_src/server_sent_events/tutorial001_py310.py ln[28:31] hl[29] *}
### No Return Type { #no-return-type }
You can also omit the return type. FastAPI will use the [`jsonable_encoder`](./encoder.md){.internal-link target=_blank} to convert the data and send it.
{* ../../docs_src/server_sent_events/tutorial001_py310.py ln[34:37] hl[35] *}
## `ServerSentEvent` { #serversentevent }
If you need to set SSE fields like `event`, `id`, `retry`, or `comment`, you can yield `ServerSentEvent` objects instead of plain data.
Import `ServerSentEvent` from `fastapi.sse`:
{* ../../docs_src/server_sent_events/tutorial002_py310.py hl[4,26] *}
The `data` field is always encoded as JSON. You can pass any value that can be serialized as JSON, including Pydantic models.
## Raw Data { #raw-data }
If you need to send data **without** JSON encoding, use `raw_data` instead of `data`.
This is useful for sending pre-formatted text, log lines, or special <dfn title="A value used to indicate a special condition or state">"sentinel"</dfn> values like `[DONE]`.
{* ../../docs_src/server_sent_events/tutorial003_py310.py hl[17] *}
/// note
`data` and `raw_data` are mutually exclusive. You can only set one of them on each `ServerSentEvent`.
///
## Resuming with `Last-Event-ID` { #resuming-with-last-event-id }
When a browser reconnects after a connection drop, it sends the last received `id` in the `Last-Event-ID` header.
You can read it as a header parameter and use it to resume the stream from where the client left off:
{* ../../docs_src/server_sent_events/tutorial004_py310.py hl[25,27,31] *}
## SSE with POST { #sse-with-post }
SSE works with **any HTTP method**, not just `GET`.
This is useful for protocols like <a href="https://modelcontextprotocol.io" class="external-link" target="_blank">MCP</a> that stream SSE over `POST`:
{* ../../docs_src/server_sent_events/tutorial005_py310.py hl[14] *}
## Technical Details { #technical-details }
FastAPI implements some SSE best practices out of the box.
* Send a **"keep alive" `ping` comment** every 15 seconds when there hasn't been any message, to prevent some proxies from closing the connection, as suggested in the <a href="https://html.spec.whatwg.org/multipage/server-sent-events.html#authoring-notes" class="external-link" target="_blank">HTML specification: Server-Sent Events</a>.
* Set the `Cache-Control: no-cache` header to **prevent caching** of the stream.
* Set a special header `X-Accel-Buffering: no` to **prevent buffering** in some proxies like Nginx.
You don't have to do anything about it, it works out of the box. 🤓

View File

@@ -2,6 +2,12 @@
You could have a sequence of data that you would like to send in a "**stream**", you could do it with **JSON Lines**.
/// info
Added in FastAPI 0.134.0.
///
## What is a Stream? { #what-is-a-stream }
"**Streaming**" data means that your app will start sending data items to the client without waiting for the entire sequence of items to be ready.
@@ -100,6 +106,6 @@ You can also omit the return type. FastAPI will then use the [`jsonable_encoder`
{* ../../docs_src/stream_json_lines/tutorial001_py310.py ln[33:36] hl[34] *}
## Server Sent Events (SSE) { #server-sent-events-sse }
## Server-Sent Events (SSE) { #server-sent-events-sse }
A future version of FastAPI will also have first-class support for Server Sent Events (SSE), which are quite similar, but with a couple of extra details. 🤓
FastAPI also has first-class support for Server-Sent Events (SSE), which are quite similar but with a couple of extra details. You can learn about them in the next chapter: [Server-Sent Events (SSE)](server-sent-events.md){.internal-link target=_blank}. 🤓

View File

@@ -155,6 +155,7 @@ nav:
- tutorial/sql-databases.md
- tutorial/bigger-applications.md
- tutorial/stream-json-lines.md
- tutorial/server-sent-events.md
- tutorial/background-tasks.md
- tutorial/metadata.md
- tutorial/static-files.md

View File

View File

@@ -0,0 +1,43 @@
from collections.abc import AsyncIterable, Iterable
from fastapi import FastAPI
from fastapi.sse import EventSourceResponse
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None
items = [
Item(name="Plumbus", description="A multi-purpose household device."),
Item(name="Portal Gun", description="A portal opening device."),
Item(name="Meeseeks Box", description="A box that summons a Meeseeks."),
]
@app.get("/items/stream", response_class=EventSourceResponse)
async def sse_items() -> AsyncIterable[Item]:
for item in items:
yield item
@app.get("/items/stream-no-async", response_class=EventSourceResponse)
def sse_items_no_async() -> Iterable[Item]:
for item in items:
yield item
@app.get("/items/stream-no-annotation", response_class=EventSourceResponse)
async def sse_items_no_annotation():
for item in items:
yield item
@app.get("/items/stream-no-async-no-annotation", response_class=EventSourceResponse)
def sse_items_no_async_no_annotation():
for item in items:
yield item

View File

@@ -0,0 +1,26 @@
from collections.abc import AsyncIterable
from fastapi import FastAPI
from fastapi.sse import EventSourceResponse, ServerSentEvent
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
price: float
items = [
Item(name="Plumbus", price=32.99),
Item(name="Portal Gun", price=999.99),
Item(name="Meeseeks Box", price=49.99),
]
@app.get("/items/stream", response_class=EventSourceResponse)
async def stream_items() -> AsyncIterable[ServerSentEvent]:
yield ServerSentEvent(comment="stream of item updates")
for i, item in enumerate(items):
yield ServerSentEvent(data=item, event="item_update", id=str(i + 1), retry=5000)

View File

@@ -0,0 +1,17 @@
from collections.abc import AsyncIterable
from fastapi import FastAPI
from fastapi.sse import EventSourceResponse, ServerSentEvent
app = FastAPI()
@app.get("/logs/stream", response_class=EventSourceResponse)
async def stream_logs() -> AsyncIterable[ServerSentEvent]:
logs = [
"2025-01-01 INFO Application started",
"2025-01-01 DEBUG Connected to database",
"2025-01-01 WARN High memory usage detected",
]
for log_line in logs:
yield ServerSentEvent(raw_data=log_line)

View File

@@ -0,0 +1,31 @@
from collections.abc import AsyncIterable
from typing import Annotated
from fastapi import FastAPI, Header
from fastapi.sse import EventSourceResponse, ServerSentEvent
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
price: float
items = [
Item(name="Plumbus", price=32.99),
Item(name="Portal Gun", price=999.99),
Item(name="Meeseeks Box", price=49.99),
]
@app.get("/items/stream", response_class=EventSourceResponse)
async def stream_items(
last_event_id: Annotated[int | None, Header()] = None,
) -> AsyncIterable[ServerSentEvent]:
start = last_event_id + 1 if last_event_id is not None else 0
for i, item in enumerate(items):
if i < start:
continue
yield ServerSentEvent(data=item, id=str(i))

View File

@@ -0,0 +1,19 @@
from collections.abc import AsyncIterable
from fastapi import FastAPI
from fastapi.sse import EventSourceResponse, ServerSentEvent
from pydantic import BaseModel
app = FastAPI()
class Prompt(BaseModel):
text: str
@app.post("/chat/stream", response_class=EventSourceResponse)
async def stream_chat(prompt: Prompt) -> AsyncIterable[ServerSentEvent]:
words = prompt.text.split()
for word in words:
yield ServerSentEvent(data=word, event="token")
yield ServerSentEvent(raw_data="[DONE]", event="done")

View File

@@ -290,146 +290,11 @@ Apply shared dependencies at the router level via `dependencies=[Depends(...)]`.
## Dependency Injection
Use dependencies when:
See [the dependency injection reference](references/dependencies.md) for detailed patterns including `yield` with `scope`, and class dependencies.
* They can't be declared in Pydantic validation and require additional logic
* The logic depends on external resources or could block in any other way
* Other dependencies need their results (it's a sub-dependency)
* The logic can be shared by multiple endpoints to do things like error early, authentication, etc.
* They need to handle cleanup (e.g., DB sessions, file handles), using dependencies with `yield`
* Their logic needs input data from the request, like headers, query parameters, etc.
Use dependencies when the logic can't be declared in Pydantic validation, depends on external resources, needs cleanup (with `yield`), or is shared across endpoints.
### Dependencies with `yield` and `scope`
When using dependencies with `yield`, they can have a `scope` that defines when the exit code is run.
Use the default scope `"request"` to run the exit code after the response is sent back.
```python
from typing import Annotated
from fastapi import Depends, FastAPI
app = FastAPI()
def get_db():
db = DBSession()
try:
yield db
finally:
db.close()
DBDep = Annotated[DBSession, Depends(get_db)]
@app.get("/items/")
async def read_items(db: DBDep):
return db.query(Item).all()
```
Use the scope `"function"` when they should run the exit code after the response data is generated but before the response is sent back to the client.
```python
from typing import Annotated
from fastapi import Depends, FastAPI
app = FastAPI()
def get_username():
try:
yield "Rick"
finally:
print("Cleanup up before response is sent")
UserNameDep = Annotated[str, Depends(get_username, scope="function")]
@app.get("/users/me")
def get_user_me(username: UserNameDep):
return username
```
### Class Dependencies
Avoid creating class dependencies when possible.
If a class is needed, instead create a regular function dependency that returns a class instance.
Do this:
```python
from dataclasses import dataclass
from typing import Annotated
from fastapi import Depends, FastAPI
app = FastAPI()
@dataclass
class DatabasePaginator:
offset: int = 0
limit: int = 100
q: str | None = None
def get_page(self) -> dict:
# Simulate a page of data
return {
"offset": self.offset,
"limit": self.limit,
"q": self.q,
"items": [],
}
def get_db_paginator(
offset: int = 0, limit: int = 100, q: str | None = None
) -> DatabasePaginator:
return DatabasePaginator(offset=offset, limit=limit, q=q)
PaginatorDep = Annotated[DatabasePaginator, Depends(get_db_paginator)]
@app.get("/items/")
async def read_items(paginator: PaginatorDep):
return paginator.get_page()
```
instead of this:
```python
# DO NOT DO THIS
from typing import Annotated
from fastapi import Depends, FastAPI
app = FastAPI()
class DatabasePaginator:
def __init__(self, offset: int = 0, limit: int = 100, q: str | None = None):
self.offset = offset
self.limit = limit
self.q = q
def get_page(self) -> dict:
# Simulate a page of data
return {
"offset": self.offset,
"limit": self.limit,
"q": self.q,
"items": [],
}
@app.get("/items/")
async def read_items(paginator: Annotated[DatabasePaginator, Depends()]):
return paginator.get_page()
```
Apply shared dependencies at the router level via `dependencies=[Depends(...)]`.
## Async vs Sync *path operations*
@@ -461,120 +326,23 @@ The same rules apply to dependencies.
Make sure blocking code is not run inside of `async` functions. The logic will work, but will damage the performance heavily.
### Asyncer
When needing to mix blocking and async code, see Asyncer in [the other tools reference](references/other-tools.md).
When needing to run blocking code inside of async functions, or async code inside of blocking functions, suggest using Asyncer.
## Streaming (JSON Lines, SSE, bytes)
Install:
See [the streaming reference](references/streaming.md) for JSON Lines, Server-Sent Events (`EventSourceResponse`, `ServerSentEvent`), and byte streaming (`StreamingResponse`) patterns.
```bash
uv add asyncer
```
## Tooling
Run blocking sync code inside of async with `asyncify()`:
See [the other tools reference](references/other-tools.md) for details on uv, Ruff, ty for package management, linting, type checking, formatting, etc.
```python
from asyncer import asyncify
from fastapi import FastAPI
## Other Libraries
app = FastAPI()
See [the other tools reference](references/other-tools.md) for details on other libraries:
def do_blocking_work(name: str) -> str:
# Some blocking I/O operation
return f"Hello {name}"
@app.get("/items/")
async def read_items():
result = await asyncify(do_blocking_work)(name="World")
return {"message": result}
```
And run async code inside of blocking sync code with `syncify()`:
```python
from asyncer import syncify
from fastapi import FastAPI
app = FastAPI()
async def do_async_work(name: str) -> str:
return f"Hello {name}"
@app.get("/items/")
def read_items():
result = syncify(do_async_work)(name="World")
return {"message": result}
```
## Stream JSON Lines
To stream JSON Lines, declare the return type and use `yield` to return the data.
```python
@app.get("/items/stream")
async def stream_items() -> AsyncIterable[Item]:
for item in items:
yield item
```
## Stream bytes
To stream bytes, declare a `response_class=` of `StreamingResponse` or a sub-class, and use `yield` to return the data.
```python
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
from app.utils import read_image
app = FastAPI()
class PNGStreamingResponse(StreamingResponse):
media_type = "image/png"
@app.get("/image", response_class=PNGStreamingResponse)
def stream_image_no_async_no_annotation():
with read_image() as image_file:
yield from image_file
```
prefer this over returning a `StreamingResponse` directly:
```python
# DO NOT DO THIS
import anyio
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
from app.utils import read_image
app = FastAPI()
class PNGStreamingResponse(StreamingResponse):
media_type = "image/png"
@app.get("/")
async def main():
return PNGStreamingResponse(read_image())
```
## Use uv, ruff, ty
If uv is available, use it to manage dependencies.
If Ruff is available, use it to lint and format the code. Consider enabling the FastAPI rules.
If ty is available, use it to check types.
## SQLModel for SQL databases
When working with SQL databases, prefer using SQLModel as it is integrated with Pydantic and will allow declaring data validation with the same models.
* Asyncer for handling async and await, concurrency, mixing async and blocking code, prefer it over AnyIO or asyncio.
* SQLModel for working with SQL databases, prefer it over SQLAlchemy.
* HTTPX for interacting with HTTP (other APIs), prefer it over Requests.
## Do not use Pydantic RootModels

View File

@@ -0,0 +1,142 @@
# Dependency Injection
Use dependencies when:
* They can't be declared in Pydantic validation and require additional logic
* The logic depends on external resources or could block in any other way
* Other dependencies need their results (it's a sub-dependency)
* The logic can be shared by multiple endpoints to do things like error early, authentication, etc.
* They need to handle cleanup (e.g., DB sessions, file handles), using dependencies with `yield`
* Their logic needs input data from the request, like headers, query parameters, etc.
## Dependencies with `yield` and `scope`
When using dependencies with `yield`, they can have a `scope` that defines when the exit code is run.
Use the default scope `"request"` to run the exit code after the response is sent back.
```python
from typing import Annotated
from fastapi import Depends, FastAPI
app = FastAPI()
def get_db():
db = DBSession()
try:
yield db
finally:
db.close()
DBDep = Annotated[DBSession, Depends(get_db)]
@app.get("/items/")
async def read_items(db: DBDep):
return db.query(Item).all()
```
Use the scope `"function"` when they should run the exit code after the response data is generated but before the response is sent back to the client.
```python
from typing import Annotated
from fastapi import Depends, FastAPI
app = FastAPI()
def get_username():
try:
yield "Rick"
finally:
print("Cleanup up before response is sent")
UserNameDep = Annotated[str, Depends(get_username, scope="function")]
@app.get("/users/me")
def get_user_me(username: UserNameDep):
return username
```
## Class Dependencies
Avoid creating class dependencies when possible.
If a class is needed, instead create a regular function dependency that returns a class instance.
Do this:
```python
from dataclasses import dataclass
from typing import Annotated
from fastapi import Depends, FastAPI
app = FastAPI()
@dataclass
class DatabasePaginator:
offset: int = 0
limit: int = 100
q: str | None = None
def get_page(self) -> dict:
# Simulate a page of data
return {
"offset": self.offset,
"limit": self.limit,
"q": self.q,
"items": [],
}
def get_db_paginator(
offset: int = 0, limit: int = 100, q: str | None = None
) -> DatabasePaginator:
return DatabasePaginator(offset=offset, limit=limit, q=q)
PaginatorDep = Annotated[DatabasePaginator, Depends(get_db_paginator)]
@app.get("/items/")
async def read_items(paginator: PaginatorDep):
return paginator.get_page()
```
instead of this:
```python
# DO NOT DO THIS
from typing import Annotated
from fastapi import Depends, FastAPI
app = FastAPI()
class DatabasePaginator:
def __init__(self, offset: int = 0, limit: int = 100, q: str | None = None):
self.offset = offset
self.limit = limit
self.q = q
def get_page(self) -> dict:
# Simulate a page of data
return {
"offset": self.offset,
"limit": self.limit,
"q": self.q,
"items": [],
}
@app.get("/items/")
async def read_items(paginator: Annotated[DatabasePaginator, Depends()]):
return paginator.get_page()
```

View File

@@ -0,0 +1,76 @@
# Other Tools
## uv
If uv is available, use it to manage dependencies.
## Ruff
If Ruff is available, use it to lint and format the code. Consider enabling the FastAPI rules.
## ty
If ty is available, use it to check types.
## Asyncer
When needing to run blocking code inside of async functions, or async code inside of blocking functions, suggest using Asyncer.
Prefer it over AnyIO or asyncio.
Install:
```bash
uv add asyncer
```
Run blocking sync code inside of async with `asyncify()`:
```python
from asyncer import asyncify
from fastapi import FastAPI
app = FastAPI()
def do_blocking_work(name: str) -> str:
# Some blocking I/O operation
return f"Hello {name}"
@app.get("/items/")
async def read_items():
result = await asyncify(do_blocking_work)(name="World")
return {"message": result}
```
And run async code inside of blocking sync code with `syncify()`:
```python
from asyncer import syncify
from fastapi import FastAPI
app = FastAPI()
async def do_async_work(name: str) -> str:
return f"Hello {name}"
@app.get("/items/")
def read_items():
result = syncify(do_async_work)(name="World")
return {"message": result}
```
## SQLModel for SQL databases
When working with SQL databases, prefer using SQLModel as it is integrated with Pydantic and will allow declaring data validation with the same models.
Prefer it over SQLAlchemy.
## HTTPX
Use HTTPX for handling HTTP communication (e.g. with other APIs). It support sync and async usage.
Prefer it over Requests.

View File

@@ -0,0 +1,105 @@
# Streaming
## Stream JSON Lines
To stream JSON Lines, declare the return type and use `yield` to return the data.
```python
@app.get("/items/stream")
async def stream_items() -> AsyncIterable[Item]:
for item in items:
yield item
```
## Server-Sent Events (SSE)
To stream Server-Sent Events, use `response_class=EventSourceResponse` and `yield` items from the endpoint.
Plain objects are automatically JSON-serialized as `data:` fields, declare the return type so the serialization is done by Pydantic:
```python
from collections.abc import AsyncIterable
from fastapi import FastAPI
from fastapi.sse import EventSourceResponse
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
price: float
@app.get("/items/stream", response_class=EventSourceResponse)
async def stream_items() -> AsyncIterable[Item]:
yield Item(name="Plumbus", price=32.99)
yield Item(name="Portal Gun", price=999.99)
```
For full control over SSE fields (`event`, `id`, `retry`, `comment`), yield `ServerSentEvent` instances:
```python
from collections.abc import AsyncIterable
from fastapi import FastAPI
from fastapi.sse import EventSourceResponse, ServerSentEvent
app = FastAPI()
@app.get("/events", response_class=EventSourceResponse)
async def stream_events() -> AsyncIterable[ServerSentEvent]:
yield ServerSentEvent(data={"status": "started"}, event="status", id="1")
yield ServerSentEvent(data={"progress": 50}, event="progress", id="2")
```
Use `raw_data` instead of `data` to send pre-formatted strings without JSON encoding:
```python
yield ServerSentEvent(raw_data="plain text line", event="log")
```
## Stream bytes
To stream bytes, declare a `response_class=` of `StreamingResponse` or a sub-class, and use `yield` to return the data.
```python
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
from app.utils import read_image
app = FastAPI()
class PNGStreamingResponse(StreamingResponse):
media_type = "image/png"
@app.get("/image", response_class=PNGStreamingResponse)
def stream_image_no_async_no_annotation():
with read_image() as image_file:
yield from image_file
```
prefer this over returning a `StreamingResponse` directly:
```python
# DO NOT DO THIS
import anyio
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
from app.utils import read_image
app = FastAPI()
class PNGStreamingResponse(StreamingResponse):
media_type = "image/png"
@app.get("/")
async def main():
return PNGStreamingResponse(read_image())
```

View File

@@ -1,6 +1,6 @@
"""FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
__version__ = "0.134.0"
__version__ = "0.135.1"
from starlette import status as status

View File

@@ -29,6 +29,7 @@ from fastapi.openapi.constants import METHODS_WITH_BODY, REF_PREFIX
from fastapi.openapi.models import OpenAPI
from fastapi.params import Body, ParamTypes
from fastapi.responses import Response
from fastapi.sse import _SSE_EVENT_SCHEMA
from fastapi.types import ModelNameMap
from fastapi.utils import (
deep_dict_update,
@@ -372,6 +373,26 @@ def get_openapi_path(
operation.setdefault("responses", {}).setdefault(
status_code, {}
).setdefault("content", {})["application/jsonl"] = jsonl_content
elif route.is_sse_stream:
sse_content: dict[str, Any] = {}
item_schema = copy.deepcopy(_SSE_EVENT_SCHEMA)
if route.stream_item_field:
content_schema = get_schema_from_model_field(
field=route.stream_item_field,
model_name_map=model_name_map,
field_mapping=field_mapping,
separate_input_output_schemas=separate_input_output_schemas,
)
item_schema["required"] = ["data"]
item_schema["properties"]["data"] = {
"type": "string",
"contentMediaType": "application/json",
"contentSchema": content_schema,
}
sse_content["itemSchema"] = item_schema
operation.setdefault("responses", {}).setdefault(
status_code, {}
).setdefault("content", {})["text/event-stream"] = sse_content
elif route_response_media_type:
response_schema = {"type": "string"}
if lenient_issubclass(current_response_class, JSONResponse):

View File

@@ -1,6 +1,7 @@
from typing import Any
from fastapi.exceptions import FastAPIDeprecationWarning
from fastapi.sse import EventSourceResponse as EventSourceResponse # noqa
from starlette.responses import FileResponse as FileResponse # noqa
from starlette.responses import HTMLResponse as HTMLResponse # noqa
from starlette.responses import JSONResponse as JSONResponse # noqa

View File

@@ -30,6 +30,7 @@ from typing import (
import anyio
from annotated_doc import Doc
from anyio.abc import ObjectReceiveStream
from fastapi import params
from fastapi._compat import (
ModelField,
@@ -56,6 +57,13 @@ from fastapi.exceptions import (
ResponseValidationError,
WebSocketRequestValidationError,
)
from fastapi.sse import (
_PING_INTERVAL,
KEEPALIVE_COMMENT,
EventSourceResponse,
ServerSentEvent,
format_sse_event,
)
from fastapi.types import DecoratedCallable, IncEx
from fastapi.utils import (
create_model_field,
@@ -66,7 +74,7 @@ from fastapi.utils import (
from starlette import routing
from starlette._exception_handler import wrap_app_handling_exceptions
from starlette._utils import is_async_callable
from starlette.concurrency import run_in_threadpool
from starlette.concurrency import iterate_in_threadpool, run_in_threadpool
from starlette.exceptions import HTTPException
from starlette.requests import Request
from starlette.responses import JSONResponse, Response, StreamingResponse
@@ -361,6 +369,7 @@ def get_request_handler(
actual_response_class: type[Response] = response_class.value
else:
actual_response_class = response_class
is_sse_stream = lenient_issubclass(actual_response_class, EventSourceResponse)
if isinstance(strict_content_type, DefaultPlaceholder):
actual_strict_content_type: bool = strict_content_type.value
else:
@@ -452,35 +461,164 @@ def get_request_handler(
errors = solved_result.errors
assert dependant.call # For types
if not errors:
if is_json_stream:
# Shared serializer for stream items (JSONL and SSE).
# Validates against stream_item_field when set, then
# serializes to JSON bytes.
def _serialize_data(data: Any) -> bytes:
if stream_item_field:
value, errors_ = stream_item_field.validate(
data, {}, loc=("response",)
)
if errors_:
ctx = endpoint_ctx or EndpointContext()
raise ResponseValidationError(
errors=errors_,
body=data,
endpoint_ctx=ctx,
)
return stream_item_field.serialize_json(
value,
include=response_model_include,
exclude=response_model_exclude,
by_alias=response_model_by_alias,
exclude_unset=response_model_exclude_unset,
exclude_defaults=response_model_exclude_defaults,
exclude_none=response_model_exclude_none,
)
else:
data = jsonable_encoder(data)
return json.dumps(data).encode("utf-8")
if is_sse_stream:
# Generator endpoint: stream as Server-Sent Events
gen = dependant.call(**solved_result.values)
def _serialize_sse_item(item: Any) -> bytes:
if isinstance(item, ServerSentEvent):
# User controls the event structure.
# Serialize the data payload if present.
# For ServerSentEvent items we skip stream_item_field
# validation (the user may mix types intentionally).
if item.raw_data is not None:
data_str: str | None = item.raw_data
elif item.data is not None:
if hasattr(item.data, "model_dump_json"):
data_str = item.data.model_dump_json()
else:
data_str = json.dumps(jsonable_encoder(item.data))
else:
data_str = None
return format_sse_event(
data_str=data_str,
event=item.event,
id=item.id,
retry=item.retry,
comment=item.comment,
)
else:
# Plain object: validate + serialize via
# stream_item_field (if set) and wrap in data field
return format_sse_event(
data_str=_serialize_data(item).decode("utf-8")
)
if dependant.is_async_gen_callable:
sse_aiter: AsyncIterator[Any] = gen.__aiter__()
else:
sse_aiter = iterate_in_threadpool(gen)
@asynccontextmanager
async def _sse_producer_cm() -> AsyncIterator[
ObjectReceiveStream[bytes]
]:
# Use a memory stream to decouple generator iteration
# from the keepalive timer. A producer task pulls items
# from the generator independently, so
# `anyio.fail_after` never wraps the generator's
# `__anext__` directly - avoiding CancelledError that
# would finalize the generator and also working for sync
# generators running in a thread pool.
#
# This context manager is entered on the request-scoped
# AsyncExitStack so its __aexit__ (which cancels the
# task group) is called by the exit stack after the
# streaming response completes — not by async generator
# finalization via GeneratorExit.
# Ref: https://peps.python.org/pep-0789/
send_stream, receive_stream = anyio.create_memory_object_stream[
bytes
](max_buffer_size=1)
async def _producer() -> None:
async with send_stream:
async for raw_item in sse_aiter:
await send_stream.send(_serialize_sse_item(raw_item))
send_keepalive, receive_keepalive = (
anyio.create_memory_object_stream[bytes](max_buffer_size=1)
)
async def _keepalive_inserter() -> None:
"""Read from the producer and forward to the output,
inserting keepalive comments on timeout."""
async with send_keepalive, receive_stream:
try:
while True:
try:
with anyio.fail_after(_PING_INTERVAL):
data = await receive_stream.receive()
await send_keepalive.send(data)
except TimeoutError:
await send_keepalive.send(KEEPALIVE_COMMENT)
except anyio.EndOfStream:
pass
async with anyio.create_task_group() as tg:
tg.start_soon(_producer)
tg.start_soon(_keepalive_inserter)
yield receive_keepalive
tg.cancel_scope.cancel()
# Enter the SSE context manager on the request-scoped
# exit stack. The stack outlives the streaming response,
# so __aexit__ runs via proper structured teardown, not
# via GeneratorExit thrown into an async generator.
sse_receive_stream = await async_exit_stack.enter_async_context(
_sse_producer_cm()
)
# Ensure the receive stream is closed when the exit stack
# unwinds, preventing ResourceWarning from __del__.
async_exit_stack.push_async_callback(sse_receive_stream.aclose)
async def _sse_with_checkpoints(
stream: ObjectReceiveStream[bytes],
) -> AsyncIterator[bytes]:
async for data in stream:
yield data
# Guarantee a checkpoint so cancellation can be
# delivered even when the producer is faster than
# the consumer and receive() never suspends.
await anyio.sleep(0)
sse_stream_content: AsyncIterator[bytes] | Iterator[bytes] = (
_sse_with_checkpoints(sse_receive_stream)
)
response = StreamingResponse(
sse_stream_content,
media_type="text/event-stream",
background=solved_result.background_tasks,
)
response.headers["Cache-Control"] = "no-cache"
# For Nginx proxies to not buffer server sent events
response.headers["X-Accel-Buffering"] = "no"
response.headers.raw.extend(solved_result.response.headers.raw)
elif is_json_stream:
# Generator endpoint: stream as JSONL
gen = dependant.call(**solved_result.values)
def _serialize_item(item: Any) -> bytes:
if stream_item_field:
value, errors = stream_item_field.validate(
item, {}, loc=("response",)
)
if errors:
ctx = endpoint_ctx or EndpointContext()
raise ResponseValidationError(
errors=errors,
body=item,
endpoint_ctx=ctx,
)
line = stream_item_field.serialize_json(
value,
include=response_model_include,
exclude=response_model_exclude,
by_alias=response_model_by_alias,
exclude_unset=response_model_exclude_unset,
exclude_defaults=response_model_exclude_defaults,
exclude_none=response_model_exclude_none,
)
return line + b"\n"
else:
data = jsonable_encoder(item)
return json.dumps(data).encode("utf-8") + b"\n"
return _serialize_data(item) + b"\n"
if dependant.is_async_gen_callable:
@@ -491,7 +629,7 @@ def get_request_handler(
# Ref: https://github.com/fastapi/fastapi/issues/14680
await anyio.sleep(0)
stream_content: AsyncIterator[bytes] | Iterator[bytes] = (
jsonl_stream_content: AsyncIterator[bytes] | Iterator[bytes] = (
_async_stream_jsonl()
)
else:
@@ -500,10 +638,10 @@ def get_request_handler(
for item in gen:
yield _serialize_item(item)
stream_content = _sync_stream_jsonl()
jsonl_stream_content = _sync_stream_jsonl()
response = StreamingResponse(
stream_content,
jsonl_stream_content,
media_type="application/jsonl",
background=solved_result.background_tasks,
)
@@ -709,9 +847,16 @@ class APIRoute(routing.Route):
else:
stream_item = get_stream_item_type(return_annotation)
if stream_item is not None:
# Only extract item type for JSONL streaming when no
# explicit response_class (e.g. StreamingResponse) was set
if isinstance(response_class, DefaultPlaceholder):
# Extract item type for JSONL or SSE streaming when
# response_class is DefaultPlaceholder (JSONL) or
# EventSourceResponse (SSE).
# ServerSentEvent is excluded: it's a transport
# wrapper, not a data model, so it shouldn't feed
# into validation or OpenAPI schema generation.
if (
isinstance(response_class, DefaultPlaceholder)
or lenient_issubclass(response_class, EventSourceResponse)
) and not lenient_issubclass(stream_item, ServerSentEvent):
self.stream_item_type = stream_item
response_model = None
else:
@@ -814,11 +959,16 @@ class APIRoute(routing.Route):
name=self.unique_id,
embed_body_fields=self._embed_body_fields,
)
# Detect generator endpoints that should stream as JSONL
# (only when no explicit response_class like StreamingResponse is set)
self.is_json_stream = isinstance(response_class, DefaultPlaceholder) and (
# Detect generator endpoints that should stream as JSONL or SSE
is_generator = (
self.dependant.is_async_gen_callable or self.dependant.is_gen_callable
)
self.is_sse_stream = is_generator and lenient_issubclass(
response_class, EventSourceResponse
)
self.is_json_stream = is_generator and isinstance(
response_class, DefaultPlaceholder
)
self.app = request_response(self.get_route_handler())
def get_route_handler(self) -> Callable[[Request], Coroutine[Any, Any, Response]]:

222
fastapi/sse.py Normal file
View File

@@ -0,0 +1,222 @@
from typing import Annotated, Any
from annotated_doc import Doc
from pydantic import AfterValidator, BaseModel, Field, model_validator
from starlette.responses import StreamingResponse
# Canonical SSE event schema matching the OpenAPI 3.2 spec
# (Section 4.14.4 "Special Considerations for Server-Sent Events")
_SSE_EVENT_SCHEMA: dict[str, Any] = {
"type": "object",
"properties": {
"data": {"type": "string"},
"event": {"type": "string"},
"id": {"type": "string"},
"retry": {"type": "integer", "minimum": 0},
},
}
class EventSourceResponse(StreamingResponse):
"""Streaming response with `text/event-stream` media type.
Use as `response_class=EventSourceResponse` on a *path operation* that uses `yield`
to enable Server Sent Events (SSE) responses.
Works with **any HTTP method** (`GET`, `POST`, etc.), which makes it compatible
with protocols like MCP that stream SSE over `POST`.
The actual encoding logic lives in the FastAPI routing layer. This class
serves mainly as a marker and sets the correct `Content-Type`.
"""
media_type = "text/event-stream"
def _check_id_no_null(v: str | None) -> str | None:
if v is not None and "\0" in v:
raise ValueError("SSE 'id' must not contain null characters")
return v
class ServerSentEvent(BaseModel):
"""Represents a single Server-Sent Event.
When `yield`ed from a *path operation function* that uses
`response_class=EventSourceResponse`, each `ServerSentEvent` is encoded
into the [SSE wire format](https://html.spec.whatwg.org/multipage/server-sent-events.html#parsing-an-event-stream)
(`text/event-stream`).
If you yield a plain object (dict, Pydantic model, etc.) instead, it is
automatically JSON-encoded and sent as the `data:` field.
All `data` values **including plain strings** are JSON-serialized.
For example, `data="hello"` produces `data: "hello"` on the wire (with
quotes).
"""
data: Annotated[
Any,
Doc(
"""
The event payload.
Can be any JSON-serializable value: a Pydantic model, dict, list,
string, number, etc. It is **always** serialized to JSON: strings
are quoted (`"hello"` becomes `data: "hello"` on the wire).
Mutually exclusive with `raw_data`.
"""
),
] = None
raw_data: Annotated[
str | None,
Doc(
"""
Raw string to send as the `data:` field **without** JSON encoding.
Use this when you need to send pre-formatted text, HTML fragments,
CSV lines, or any non-JSON payload. The string is placed directly
into the `data:` field as-is.
Mutually exclusive with `data`.
"""
),
] = None
event: Annotated[
str | None,
Doc(
"""
Optional event type name.
Maps to `addEventListener(event, ...)` on the browser. When omitted,
the browser dispatches on the generic `message` event.
"""
),
] = None
id: Annotated[
str | None,
AfterValidator(_check_id_no_null),
Doc(
"""
Optional event ID.
The browser sends this value back as the `Last-Event-ID` header on
automatic reconnection. **Must not contain null (`\\0`) characters.**
"""
),
] = None
retry: Annotated[
int | None,
Field(ge=0),
Doc(
"""
Optional reconnection time in **milliseconds**.
Tells the browser how long to wait before reconnecting after the
connection is lost. Must be a non-negative integer.
"""
),
] = None
comment: Annotated[
str | None,
Doc(
"""
Optional comment line(s).
Comment lines start with `:` in the SSE wire format and are ignored by
`EventSource` clients. Useful for keep-alive pings to prevent
proxy/load-balancer timeouts.
"""
),
] = None
@model_validator(mode="after")
def _check_data_exclusive(self) -> "ServerSentEvent":
if self.data is not None and self.raw_data is not None:
raise ValueError(
"Cannot set both 'data' and 'raw_data' on the same "
"ServerSentEvent. Use 'data' for JSON-serialized payloads "
"or 'raw_data' for pre-formatted strings."
)
return self
def format_sse_event(
*,
data_str: Annotated[
str | None,
Doc(
"""
Pre-serialized data string to use as the `data:` field.
"""
),
] = None,
event: Annotated[
str | None,
Doc(
"""
Optional event type name (`event:` field).
"""
),
] = None,
id: Annotated[
str | None,
Doc(
"""
Optional event ID (`id:` field).
"""
),
] = None,
retry: Annotated[
int | None,
Doc(
"""
Optional reconnection time in milliseconds (`retry:` field).
"""
),
] = None,
comment: Annotated[
str | None,
Doc(
"""
Optional comment line(s) (`:` prefix).
"""
),
] = None,
) -> bytes:
"""Build SSE wire-format bytes from **pre-serialized** data.
The result always ends with `\n\n` (the event terminator).
"""
lines: list[str] = []
if comment is not None:
for line in comment.splitlines():
lines.append(f": {line}")
if event is not None:
lines.append(f"event: {event}")
if data_str is not None:
for line in data_str.splitlines():
lines.append(f"data: {line}")
if id is not None:
lines.append(f"id: {id}")
if retry is not None:
lines.append(f"retry: {retry}")
lines.append("")
lines.append("")
return "\n".join(lines).encode("utf-8")
# Keep-alive comment, per the SSE spec recommendation
KEEPALIVE_COMMENT = b": ping\n\n"
# Seconds between keep-alive pings when a generator is idle.
# Private but importable so tests can monkeypatch it.
_PING_INTERVAL: float = 15.0

View File

@@ -324,6 +324,7 @@ ignore = [
"docs_src/stream_json_lines/tutorial001_py310.py" = ["UP028"]
"docs_src/stream_data/tutorial001_py310.py" = ["UP028"]
"docs_src/stream_data/tutorial002_py310.py" = ["UP028"]
"docs_src/server_sent_events/tutorial001_py310.py" = ["UP028"]
[tool.ruff.lint.isort]
known-third-party = ["fastapi", "pydantic", "starlette"]

318
tests/test_sse.py Normal file
View File

@@ -0,0 +1,318 @@
import asyncio
import time
from collections.abc import AsyncIterable, Iterable
import fastapi.routing
import pytest
from fastapi import APIRouter, FastAPI
from fastapi.responses import EventSourceResponse
from fastapi.sse import ServerSentEvent
from fastapi.testclient import TestClient
from pydantic import BaseModel
class Item(BaseModel):
name: str
description: str | None = None
items = [
Item(name="Plumbus", description="A multi-purpose household device."),
Item(name="Portal Gun", description="A portal opening device."),
Item(name="Meeseeks Box", description="A box that summons a Meeseeks."),
]
app = FastAPI()
@app.get("/items/stream", response_class=EventSourceResponse)
async def sse_items() -> AsyncIterable[Item]:
for item in items:
yield item
@app.get("/items/stream-sync", response_class=EventSourceResponse)
def sse_items_sync() -> Iterable[Item]:
yield from items
@app.get("/items/stream-no-annotation", response_class=EventSourceResponse)
async def sse_items_no_annotation():
for item in items:
yield item
@app.get("/items/stream-sync-no-annotation", response_class=EventSourceResponse)
def sse_items_sync_no_annotation():
yield from items
@app.get("/items/stream-dict", response_class=EventSourceResponse)
async def sse_items_dict():
for item in items:
yield {"name": item.name, "description": item.description}
@app.get("/items/stream-sse-event", response_class=EventSourceResponse)
async def sse_items_event():
yield ServerSentEvent(data="hello", event="greeting", id="1")
yield ServerSentEvent(data={"key": "value"}, event="json-data", id="2")
yield ServerSentEvent(comment="just a comment")
yield ServerSentEvent(data="retry-test", retry=5000)
@app.get("/items/stream-mixed", response_class=EventSourceResponse)
async def sse_items_mixed() -> AsyncIterable[Item]:
yield items[0]
yield ServerSentEvent(data="custom-event", event="special")
yield items[1]
@app.get("/items/stream-string", response_class=EventSourceResponse)
async def sse_items_string():
yield ServerSentEvent(data="plain text data")
@app.post("/items/stream-post", response_class=EventSourceResponse)
async def sse_items_post() -> AsyncIterable[Item]:
for item in items:
yield item
@app.get("/items/stream-raw", response_class=EventSourceResponse)
async def sse_items_raw():
yield ServerSentEvent(raw_data="plain text without quotes")
yield ServerSentEvent(raw_data="<div>html fragment</div>", event="html")
yield ServerSentEvent(raw_data="cpu,87.3,1709145600", event="csv")
router = APIRouter()
@router.get("/events", response_class=EventSourceResponse)
async def stream_events():
yield {"msg": "hello"}
yield {"msg": "world"}
app.include_router(router, prefix="/api")
@pytest.fixture(name="client")
def client_fixture():
with TestClient(app) as c:
yield c
def test_async_generator_with_model(client: TestClient):
response = client.get("/items/stream")
assert response.status_code == 200
assert response.headers["content-type"] == "text/event-stream; charset=utf-8"
assert response.headers["cache-control"] == "no-cache"
assert response.headers["x-accel-buffering"] == "no"
lines = response.text.strip().split("\n")
data_lines = [line for line in lines if line.startswith("data: ")]
assert len(data_lines) == 3
assert '"name":"Plumbus"' in data_lines[0] or '"name": "Plumbus"' in data_lines[0]
assert (
'"name":"Portal Gun"' in data_lines[1]
or '"name": "Portal Gun"' in data_lines[1]
)
assert (
'"name":"Meeseeks Box"' in data_lines[2]
or '"name": "Meeseeks Box"' in data_lines[2]
)
def test_sync_generator_with_model(client: TestClient):
response = client.get("/items/stream-sync")
assert response.status_code == 200
assert response.headers["content-type"] == "text/event-stream; charset=utf-8"
data_lines = [
line for line in response.text.strip().split("\n") if line.startswith("data: ")
]
assert len(data_lines) == 3
def test_async_generator_no_annotation(client: TestClient):
response = client.get("/items/stream-no-annotation")
assert response.status_code == 200
assert response.headers["content-type"] == "text/event-stream; charset=utf-8"
data_lines = [
line for line in response.text.strip().split("\n") if line.startswith("data: ")
]
assert len(data_lines) == 3
def test_sync_generator_no_annotation(client: TestClient):
response = client.get("/items/stream-sync-no-annotation")
assert response.status_code == 200
assert response.headers["content-type"] == "text/event-stream; charset=utf-8"
data_lines = [
line for line in response.text.strip().split("\n") if line.startswith("data: ")
]
assert len(data_lines) == 3
def test_dict_items(client: TestClient):
response = client.get("/items/stream-dict")
assert response.status_code == 200
data_lines = [
line for line in response.text.strip().split("\n") if line.startswith("data: ")
]
assert len(data_lines) == 3
assert '"name"' in data_lines[0]
def test_post_method_sse(client: TestClient):
"""SSE should work with POST (needed for MCP compatibility)."""
response = client.post("/items/stream-post")
assert response.status_code == 200
assert response.headers["content-type"] == "text/event-stream; charset=utf-8"
data_lines = [
line for line in response.text.strip().split("\n") if line.startswith("data: ")
]
assert len(data_lines) == 3
def test_sse_events_with_fields(client: TestClient):
response = client.get("/items/stream-sse-event")
assert response.status_code == 200
text = response.text
assert "event: greeting\n" in text
assert 'data: "hello"\n' in text
assert "id: 1\n" in text
assert "event: json-data\n" in text
assert "id: 2\n" in text
assert 'data: {"key": "value"}\n' in text
assert ": just a comment\n" in text
assert "retry: 5000\n" in text
assert 'data: "retry-test"\n' in text
def test_mixed_plain_and_sse_events(client: TestClient):
response = client.get("/items/stream-mixed")
assert response.status_code == 200
text = response.text
assert "event: special\n" in text
assert 'data: "custom-event"\n' in text
assert '"name"' in text
def test_string_data_json_encoded(client: TestClient):
"""Strings are always JSON-encoded (quoted)."""
response = client.get("/items/stream-string")
assert response.status_code == 200
assert 'data: "plain text data"\n' in response.text
def test_server_sent_event_null_id_rejected():
with pytest.raises(ValueError, match="null"):
ServerSentEvent(data="test", id="has\0null")
def test_server_sent_event_negative_retry_rejected():
with pytest.raises(ValueError):
ServerSentEvent(data="test", retry=-1)
def test_server_sent_event_float_retry_rejected():
with pytest.raises(ValueError):
ServerSentEvent(data="test", retry=1.5) # type: ignore[arg-type]
def test_raw_data_sent_without_json_encoding(client: TestClient):
"""raw_data is sent as-is, not JSON-encoded."""
response = client.get("/items/stream-raw")
assert response.status_code == 200
text = response.text
# raw_data should appear without JSON quotes
assert "data: plain text without quotes\n" in text
# Not JSON-quoted
assert 'data: "plain text without quotes"' not in text
assert "event: html\n" in text
assert "data: <div>html fragment</div>\n" in text
assert "event: csv\n" in text
assert "data: cpu,87.3,1709145600\n" in text
def test_data_and_raw_data_mutually_exclusive():
"""Cannot set both data and raw_data."""
with pytest.raises(ValueError, match="Cannot set both"):
ServerSentEvent(data="json", raw_data="raw")
def test_sse_on_router_included_in_app(client: TestClient):
response = client.get("/api/events")
assert response.status_code == 200
assert response.headers["content-type"] == "text/event-stream; charset=utf-8"
data_lines = [
line for line in response.text.strip().split("\n") if line.startswith("data: ")
]
assert len(data_lines) == 2
# Keepalive ping tests
keepalive_app = FastAPI()
@keepalive_app.get("/slow-async", response_class=EventSourceResponse)
async def slow_async_stream():
yield {"n": 1}
# Sleep longer than the (monkeypatched) ping interval so a keepalive
# comment is emitted before the next item.
await asyncio.sleep(0.3)
yield {"n": 2}
@keepalive_app.get("/slow-sync", response_class=EventSourceResponse)
def slow_sync_stream():
yield {"n": 1}
time.sleep(0.3)
yield {"n": 2}
def test_keepalive_ping_async(monkeypatch: pytest.MonkeyPatch):
monkeypatch.setattr(fastapi.routing, "_PING_INTERVAL", 0.05)
with TestClient(keepalive_app) as c:
response = c.get("/slow-async")
assert response.status_code == 200
text = response.text
# The keepalive comment ": ping" should appear between the two data events
assert ": ping\n" in text
data_lines = [line for line in text.split("\n") if line.startswith("data: ")]
assert len(data_lines) == 2
def test_keepalive_ping_sync(monkeypatch: pytest.MonkeyPatch):
monkeypatch.setattr(fastapi.routing, "_PING_INTERVAL", 0.05)
with TestClient(keepalive_app) as c:
response = c.get("/slow-sync")
assert response.status_code == 200
text = response.text
assert ": ping\n" in text
data_lines = [line for line in text.split("\n") if line.startswith("data: ")]
assert len(data_lines) == 2
def test_no_keepalive_when_fast(client: TestClient):
"""No keepalive comment when items arrive quickly."""
response = client.get("/items/stream")
assert response.status_code == 200
# KEEPALIVE_COMMENT is ": ping\n\n".
assert ": ping\n" not in response.text

View File

View File

@@ -0,0 +1,191 @@
import importlib
import pytest
from fastapi.testclient import TestClient
from inline_snapshot import snapshot
@pytest.fixture(
name="client",
params=[
pytest.param("tutorial001_py310"),
],
)
def get_client(request: pytest.FixtureRequest):
mod = importlib.import_module(f"docs_src.server_sent_events.{request.param}")
client = TestClient(mod.app)
return client
@pytest.mark.parametrize(
"path",
[
"/items/stream",
"/items/stream-no-async",
"/items/stream-no-annotation",
"/items/stream-no-async-no-annotation",
],
)
def test_stream_items(client: TestClient, path: str):
response = client.get(path)
assert response.status_code == 200, response.text
assert response.headers["content-type"] == "text/event-stream; charset=utf-8"
data_lines = [
line for line in response.text.strip().split("\n") if line.startswith("data: ")
]
assert len(data_lines) == 3
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == snapshot(
{
"openapi": "3.1.0",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/stream": {
"get": {
"summary": "Sse Items",
"operationId": "sse_items_items_stream_get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"text/event-stream": {
"itemSchema": {
"type": "object",
"properties": {
"data": {
"type": "string",
"contentMediaType": "application/json",
"contentSchema": {
"$ref": "#/components/schemas/Item"
},
},
"event": {"type": "string"},
"id": {"type": "string"},
"retry": {
"type": "integer",
"minimum": 0,
},
},
"required": ["data"],
}
}
},
}
},
}
},
"/items/stream-no-async": {
"get": {
"summary": "Sse Items No Async",
"operationId": "sse_items_no_async_items_stream_no_async_get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"text/event-stream": {
"itemSchema": {
"type": "object",
"properties": {
"data": {
"type": "string",
"contentMediaType": "application/json",
"contentSchema": {
"$ref": "#/components/schemas/Item"
},
},
"event": {"type": "string"},
"id": {"type": "string"},
"retry": {
"type": "integer",
"minimum": 0,
},
},
"required": ["data"],
}
}
},
}
},
}
},
"/items/stream-no-annotation": {
"get": {
"summary": "Sse Items No Annotation",
"operationId": "sse_items_no_annotation_items_stream_no_annotation_get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"text/event-stream": {
"itemSchema": {
"type": "object",
"properties": {
"data": {"type": "string"},
"event": {"type": "string"},
"id": {"type": "string"},
"retry": {
"type": "integer",
"minimum": 0,
},
},
}
}
},
}
},
}
},
"/items/stream-no-async-no-annotation": {
"get": {
"summary": "Sse Items No Async No Annotation",
"operationId": "sse_items_no_async_no_annotation_items_stream_no_async_no_annotation_get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"text/event-stream": {
"itemSchema": {
"type": "object",
"properties": {
"data": {"type": "string"},
"event": {"type": "string"},
"id": {"type": "string"},
"retry": {
"type": "integer",
"minimum": 0,
},
},
}
}
},
}
},
}
},
},
"components": {
"schemas": {
"Item": {
"properties": {
"name": {"type": "string", "title": "Name"},
"description": {
"anyOf": [
{"type": "string"},
{"type": "null"},
],
"title": "Description",
},
},
"type": "object",
"required": ["name", "description"],
"title": "Item",
}
}
},
}
)

View File

@@ -0,0 +1,83 @@
import importlib
import pytest
from fastapi.testclient import TestClient
from inline_snapshot import snapshot
@pytest.fixture(
name="client",
params=[
pytest.param("tutorial002_py310"),
],
)
def get_client(request: pytest.FixtureRequest):
mod = importlib.import_module(f"docs_src.server_sent_events.{request.param}")
client = TestClient(mod.app)
return client
def test_stream_items(client: TestClient):
response = client.get("/items/stream")
assert response.status_code == 200, response.text
assert response.headers["content-type"] == "text/event-stream; charset=utf-8"
lines = response.text.strip().split("\n")
# First event is a comment-only event
assert lines[0] == ": stream of item updates"
# Remaining lines contain event:, data:, id:, retry: fields
event_lines = [line for line in lines if line.startswith("event: ")]
assert len(event_lines) == 3
assert all(line == "event: item_update" for line in event_lines)
data_lines = [line for line in lines if line.startswith("data: ")]
assert len(data_lines) == 3
id_lines = [line for line in lines if line.startswith("id: ")]
assert id_lines == ["id: 1", "id: 2", "id: 3"]
retry_lines = [line for line in lines if line.startswith("retry: ")]
assert len(retry_lines) == 3
assert all(line == "retry: 5000" for line in retry_lines)
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == snapshot(
{
"openapi": "3.1.0",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/stream": {
"get": {
"summary": "Stream Items",
"operationId": "stream_items_items_stream_get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"text/event-stream": {
"itemSchema": {
"type": "object",
"properties": {
"data": {"type": "string"},
"event": {"type": "string"},
"id": {"type": "string"},
"retry": {
"type": "integer",
"minimum": 0,
},
},
}
}
},
}
},
}
}
},
}
)

View File

@@ -0,0 +1,73 @@
import importlib
import pytest
from fastapi.testclient import TestClient
from inline_snapshot import snapshot
@pytest.fixture(
name="client",
params=[
pytest.param("tutorial003_py310"),
],
)
def get_client(request: pytest.FixtureRequest):
mod = importlib.import_module(f"docs_src.server_sent_events.{request.param}")
client = TestClient(mod.app)
return client
def test_stream_logs(client: TestClient):
response = client.get("/logs/stream")
assert response.status_code == 200, response.text
assert response.headers["content-type"] == "text/event-stream; charset=utf-8"
data_lines = [
line for line in response.text.strip().split("\n") if line.startswith("data: ")
]
assert len(data_lines) == 3
# raw_data is sent without JSON encoding (no quotes around the string)
assert data_lines[0] == "data: 2025-01-01 INFO Application started"
assert data_lines[1] == "data: 2025-01-01 DEBUG Connected to database"
assert data_lines[2] == "data: 2025-01-01 WARN High memory usage detected"
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == snapshot(
{
"openapi": "3.1.0",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/logs/stream": {
"get": {
"summary": "Stream Logs",
"operationId": "stream_logs_logs_stream_get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"text/event-stream": {
"itemSchema": {
"type": "object",
"properties": {
"data": {"type": "string"},
"event": {"type": "string"},
"id": {"type": "string"},
"retry": {
"type": "integer",
"minimum": 0,
},
},
}
}
},
}
},
}
}
},
}
)

View File

@@ -0,0 +1,164 @@
import importlib
import pytest
from fastapi.testclient import TestClient
from inline_snapshot import snapshot
@pytest.fixture(
name="client",
params=[
pytest.param("tutorial004_py310"),
],
)
def get_client(request: pytest.FixtureRequest):
mod = importlib.import_module(f"docs_src.server_sent_events.{request.param}")
client = TestClient(mod.app)
return client
def test_stream_all_items(client: TestClient):
response = client.get("/items/stream")
assert response.status_code == 200, response.text
data_lines = [
line for line in response.text.strip().split("\n") if line.startswith("data: ")
]
assert len(data_lines) == 3
id_lines = [
line for line in response.text.strip().split("\n") if line.startswith("id: ")
]
assert id_lines == ["id: 0", "id: 1", "id: 2"]
def test_resume_from_last_event_id(client: TestClient):
response = client.get(
"/items/stream",
headers={"last-event-id": "0"},
)
assert response.status_code == 200, response.text
data_lines = [
line for line in response.text.strip().split("\n") if line.startswith("data: ")
]
assert len(data_lines) == 2
id_lines = [
line for line in response.text.strip().split("\n") if line.startswith("id: ")
]
assert id_lines == ["id: 1", "id: 2"]
def test_resume_from_last_item(client: TestClient):
response = client.get(
"/items/stream",
headers={"last-event-id": "1"},
)
assert response.status_code == 200, response.text
data_lines = [
line for line in response.text.strip().split("\n") if line.startswith("data: ")
]
assert len(data_lines) == 1
id_lines = [
line for line in response.text.strip().split("\n") if line.startswith("id: ")
]
assert id_lines == ["id: 2"]
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == snapshot(
{
"openapi": "3.1.0",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/stream": {
"get": {
"summary": "Stream Items",
"operationId": "stream_items_items_stream_get",
"parameters": [
{
"name": "last-event-id",
"in": "header",
"required": False,
"schema": {
"anyOf": [{"type": "integer"}, {"type": "null"}],
"title": "Last-Event-Id",
},
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"text/event-stream": {
"itemSchema": {
"type": "object",
"properties": {
"data": {"type": "string"},
"event": {"type": "string"},
"id": {"type": "string"},
"retry": {
"type": "integer",
"minimum": 0,
},
},
}
}
},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
}
}
},
"components": {
"schemas": {
"HTTPValidationError": {
"properties": {
"detail": {
"items": {
"$ref": "#/components/schemas/ValidationError"
},
"type": "array",
"title": "Detail",
}
},
"type": "object",
"title": "HTTPValidationError",
},
"ValidationError": {
"properties": {
"loc": {
"items": {
"anyOf": [{"type": "string"}, {"type": "integer"}]
},
"type": "array",
"title": "Location",
},
"msg": {"type": "string", "title": "Message"},
"type": {"type": "string", "title": "Error Type"},
"input": {"title": "Input"},
"ctx": {"type": "object", "title": "Context"},
},
"type": "object",
"required": ["loc", "msg", "type"],
"title": "ValidationError",
},
}
},
}
)

View File

@@ -0,0 +1,141 @@
import importlib
import pytest
from fastapi.testclient import TestClient
from inline_snapshot import snapshot
@pytest.fixture(
name="client",
params=[
pytest.param("tutorial005_py310"),
],
)
def get_client(request: pytest.FixtureRequest):
mod = importlib.import_module(f"docs_src.server_sent_events.{request.param}")
client = TestClient(mod.app)
return client
def test_stream_chat(client: TestClient):
response = client.post(
"/chat/stream",
json={"text": "hello world"},
)
assert response.status_code == 200, response.text
assert response.headers["content-type"] == "text/event-stream; charset=utf-8"
lines = response.text.strip().split("\n")
event_lines = [line for line in lines if line.startswith("event: ")]
assert event_lines == [
"event: token",
"event: token",
"event: done",
]
data_lines = [line for line in lines if line.startswith("data: ")]
assert data_lines == [
'data: "hello"',
'data: "world"',
"data: [DONE]",
]
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == snapshot(
{
"openapi": "3.1.0",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/chat/stream": {
"post": {
"summary": "Stream Chat",
"operationId": "stream_chat_chat_stream_post",
"requestBody": {
"content": {
"application/json": {
"schema": {"$ref": "#/components/schemas/Prompt"}
}
},
"required": True,
},
"responses": {
"200": {
"description": "Successful Response",
"content": {
"text/event-stream": {
"itemSchema": {
"type": "object",
"properties": {
"data": {"type": "string"},
"event": {"type": "string"},
"id": {"type": "string"},
"retry": {
"type": "integer",
"minimum": 0,
},
},
}
}
},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
}
}
},
"components": {
"schemas": {
"HTTPValidationError": {
"properties": {
"detail": {
"items": {
"$ref": "#/components/schemas/ValidationError"
},
"type": "array",
"title": "Detail",
}
},
"type": "object",
"title": "HTTPValidationError",
},
"Prompt": {
"properties": {"text": {"type": "string", "title": "Text"}},
"type": "object",
"required": ["text"],
"title": "Prompt",
},
"ValidationError": {
"properties": {
"loc": {
"items": {
"anyOf": [{"type": "string"}, {"type": "integer"}]
},
"type": "array",
"title": "Location",
},
"msg": {"type": "string", "title": "Message"},
"type": {"type": "string", "title": "Error Type"},
"input": {"title": "Input"},
"ctx": {"type": "object", "title": "Context"},
},
"type": "object",
"required": ["loc", "msg", "type"],
"title": "ValidationError",
},
}
},
}
)