Browse Source

Merge branch 'master' into feature_offline_docs

pull/13825/head
Motov Yurii 3 months ago
committed by GitHub
parent
commit
33bf4baf59
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 2
      .github/labeler.yml
  2. 2
      .github/workflows/build-docs.yml
  3. 2
      .github/workflows/deploy-docs.yml
  4. 10
      .github/workflows/publish.yml
  5. 2
      .github/workflows/smokeshow.yml
  6. 58
      .github/workflows/test.yml
  7. 10
      docs/de/docs/advanced/websockets.md
  8. 41
      docs/en/data/contributors.yml
  9. 746
      docs/en/data/people.yml
  10. 328
      docs/en/data/topic_repos.yml
  11. 89
      docs/en/data/translation_reviewers.yml
  12. 26
      docs/en/data/translators.yml
  13. 2
      docs/en/docs/_llm-test.md
  14. 104
      docs/en/docs/advanced/custom-response.md
  15. 63
      docs/en/docs/advanced/json-base64-bytes.md
  16. 32
      docs/en/docs/advanced/response-directly.md
  17. 117
      docs/en/docs/advanced/stream-data.md
  18. 88
      docs/en/docs/advanced/strict-content-type.md
  19. 10
      docs/en/docs/advanced/websockets.md
  20. 4
      docs/en/docs/css/custom.css
  21. 4
      docs/en/docs/how-to/general.md
  22. BIN
      docs/en/docs/img/tutorial/json-base64-bytes/image01.png
  23. 29
      docs/en/docs/js/init_kapa_widget.js
  24. 8
      docs/en/docs/reference/responses.md
  25. 130
      docs/en/docs/release-notes.md
  26. 1
      docs/en/docs/tutorial/response-model.md
  27. 120
      docs/en/docs/tutorial/server-sent-events.md
  28. 111
      docs/en/docs/tutorial/stream-json-lines.md
  29. 6
      docs/en/mkdocs.yml
  30. 10
      docs/es/docs/advanced/websockets.md
  31. 10
      docs/fr/docs/advanced/websockets.md
  32. 10
      docs/ja/docs/advanced/websockets.md
  33. 10
      docs/ko/docs/advanced/websockets.md
  34. 10
      docs/pt/docs/advanced/websockets.md
  35. 2
      docs/ru/docs/advanced/middleware.md
  36. 10
      docs/ru/docs/advanced/websockets.md
  37. 2
      docs/ru/docs/deployment/docker.md
  38. 2
      docs/ru/docs/history-design-future.md
  39. 2
      docs/ru/docs/tutorial/security/oauth2-jwt.md
  40. 10
      docs/tr/docs/advanced/websockets.md
  41. 10
      docs/uk/docs/advanced/websockets.md
  42. 10
      docs/zh-hant/docs/advanced/websockets.md
  43. 10
      docs/zh/docs/advanced/websockets.md
  44. 2
      docs_src/custom_response/tutorial007_py310.py
  45. 6
      docs_src/custom_response/tutorial010_py310.py
  46. 0
      docs_src/json_base64_bytes/__init__.py
  47. 46
      docs_src/json_base64_bytes/tutorial001_py310.py
  48. 2
      docs_src/python_types/tutorial005_py39.py
  49. 0
      docs_src/server_sent_events/__init__.py
  50. 43
      docs_src/server_sent_events/tutorial001_py310.py
  51. 26
      docs_src/server_sent_events/tutorial002_py310.py
  52. 17
      docs_src/server_sent_events/tutorial003_py310.py
  53. 31
      docs_src/server_sent_events/tutorial004_py310.py
  54. 19
      docs_src/server_sent_events/tutorial005_py310.py
  55. 0
      docs_src/stream_data/__init__.py
  56. 65
      docs_src/stream_data/tutorial001_py310.py
  57. 54
      docs_src/stream_data/tutorial002_py310.py
  58. 0
      docs_src/stream_json_lines/__init__.py
  59. 42
      docs_src/stream_json_lines/tutorial001_py310.py
  60. 0
      docs_src/strict_content_type/__init__.py
  61. 14
      docs_src/strict_content_type/tutorial001_py310.py
  62. 0
      docs_src/websockets_/__init__.py
  63. 0
      docs_src/websockets_/tutorial001_py310.py
  64. 0
      docs_src/websockets_/tutorial002_an_py310.py
  65. 0
      docs_src/websockets_/tutorial002_py310.py
  66. 0
      docs_src/websockets_/tutorial003_py310.py
  67. 436
      fastapi/.agents/skills/fastapi/SKILL.md
  68. 142
      fastapi/.agents/skills/fastapi/references/dependencies.md
  69. 76
      fastapi/.agents/skills/fastapi/references/other-tools.md
  70. 105
      fastapi/.agents/skills/fastapi/references/streaming.md
  71. 2
      fastapi/__init__.py
  72. 45
      fastapi/_compat/v2.py
  73. 40
      fastapi/applications.py
  74. 2
      fastapi/datastructures.py
  75. 32
      fastapi/dependencies/utils.py
  76. 18
      fastapi/openapi/docs.py
  77. 72
      fastapi/openapi/utils.py
  78. 53
      fastapi/responses.py
  79. 408
      fastapi/routing.py
  80. 222
      fastapi/sse.py
  81. 40
      pdm_build.py
  82. 265
      pyproject.toml
  83. 83
      scripts/people.py
  84. 37
      scripts/playwright/json_base64_bytes/image01.py
  85. 3
      scripts/test-cov-html.sh
  86. 6
      scripts/test-cov.sh
  87. 2
      scripts/test.sh
  88. 12
      scripts/tests/test_translation_fixer/conftest.py
  89. 73
      tests/test_deprecated_responses.py
  90. 51
      tests/test_dump_json_fast_path.py
  91. 75
      tests/test_openapi_cache_root_path.py
  92. 13
      tests/test_orjson_response_class.py
  93. 20
      tests/test_request_params/test_file/test_list.py
  94. 8
      tests/test_request_params/test_file/test_optional.py
  95. 20
      tests/test_request_params/test_file/test_optional_list.py
  96. 16
      tests/test_request_params/test_file/test_required.py
  97. 318
      tests/test_sse.py
  98. 42
      tests/test_stream_bare_type.py
  99. 88
      tests/test_stream_cancellation.py
  100. 40
      tests/test_stream_json_validation_error.py

2
.github/labeler.yml

@ -29,8 +29,6 @@ internal:
- scripts/** - scripts/**
- .gitignore - .gitignore
- .pre-commit-config.yaml - .pre-commit-config.yaml
- pdm_build.py
- requirements*.txt
- uv.lock - uv.lock
- docs/en/data/sponsors.yml - docs/en/data/sponsors.yml
- docs/en/overrides/main.html - docs/en/overrides/main.html

2
.github/workflows/build-docs.yml

@ -97,7 +97,7 @@ jobs:
path: docs/${{ matrix.lang }}/.cache path: docs/${{ matrix.lang }}/.cache
- name: Build Docs - name: Build Docs
run: uv run ./scripts/docs.py build-lang ${{ matrix.lang }} run: uv run ./scripts/docs.py build-lang ${{ matrix.lang }}
- uses: actions/upload-artifact@v6 - uses: actions/upload-artifact@v7
with: with:
name: docs-site-${{ matrix.lang }} name: docs-site-${{ matrix.lang }}
path: ./site/** path: ./site/**

2
.github/workflows/deploy-docs.yml

@ -45,7 +45,7 @@ jobs:
run: | run: |
rm -rf ./site rm -rf ./site
mkdir ./site mkdir ./site
- uses: actions/download-artifact@v7 - uses: actions/download-artifact@v8
with: with:
path: ./site/ path: ./site/
pattern: docs-site-* pattern: docs-site-*

10
.github/workflows/publish.yml

@ -8,11 +8,6 @@ on:
jobs: jobs:
publish: publish:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy:
matrix:
package:
- fastapi
- fastapi-slim
permissions: permissions:
id-token: write id-token: write
contents: read contents: read
@ -26,14 +21,9 @@ jobs:
uses: actions/setup-python@v6 uses: actions/setup-python@v6
with: with:
python-version-file: ".python-version" python-version-file: ".python-version"
# Issue ref: https://github.com/actions/setup-python/issues/436
# cache: "pip"
# cache-dependency-path: pyproject.toml
- name: Install uv - name: Install uv
uses: astral-sh/setup-uv@v7 uses: astral-sh/setup-uv@v7
- name: Build distribution - name: Build distribution
run: uv build run: uv build
env:
TIANGOLO_BUILD_PACKAGE: ${{ matrix.package }}
- name: Publish - name: Publish
run: uv publish run: uv publish

2
.github/workflows/smokeshow.yml

@ -28,7 +28,7 @@ jobs:
pyproject.toml pyproject.toml
uv.lock uv.lock
- run: uv sync --locked --no-dev --group github-actions - run: uv sync --locked --no-dev --group github-actions
- uses: actions/download-artifact@v7 - uses: actions/download-artifact@v8
with: with:
name: coverage-html name: coverage-html
path: htmlcov path: htmlcov

58
.github/workflows/test.yml

@ -45,7 +45,7 @@ jobs:
test: test:
needs: needs:
- changes - changes
if: needs.changes.outputs.src == 'true' if: needs.changes.outputs.src == 'true' || github.ref == 'refs/heads/master'
strategy: strategy:
matrix: matrix:
os: [ windows-latest, macos-latest ] os: [ windows-latest, macos-latest ]
@ -68,10 +68,8 @@ jobs:
python-version: "3.13" python-version: "3.13"
coverage: coverage coverage: coverage
uv-resolution: highest uv-resolution: highest
# Ubuntu with 3.13 needs coverage for CodSpeed benchmarks
- os: ubuntu-latest - os: ubuntu-latest
python-version: "3.13" python-version: "3.13"
coverage: coverage
uv-resolution: highest uv-resolution: highest
codspeed: codspeed codspeed: codspeed
- os: ubuntu-latest - os: ubuntu-latest
@ -109,29 +107,52 @@ jobs:
run: uv pip install "git+https://github.com/Kludex/starlette@main" run: uv pip install "git+https://github.com/Kludex/starlette@main"
- run: mkdir coverage - run: mkdir coverage
- name: Test - name: Test
if: matrix.codspeed != 'codspeed' run: uv run --no-sync bash scripts/test-cov.sh
run: uv run --no-sync bash scripts/test.sh
env: env:
COVERAGE_FILE: coverage/.coverage.${{ runner.os }}-py${{ matrix.python-version }} COVERAGE_FILE: coverage/.coverage.${{ runner.os }}-py${{ matrix.python-version }}
CONTEXT: ${{ runner.os }}-py${{ matrix.python-version }} CONTEXT: ${{ runner.os }}-py${{ matrix.python-version }}
- name: CodSpeed benchmarks
if: matrix.codspeed == 'codspeed'
uses: CodSpeedHQ/action@v4
env:
COVERAGE_FILE: coverage/.coverage.${{ runner.os }}-py${{ matrix.python-version }}
CONTEXT: ${{ runner.os }}-py${{ matrix.python-version }}
with:
mode: simulation
run: uv run --no-sync coverage run -m pytest tests/ --codspeed
# Do not store coverage for all possible combinations to avoid file size max errors in Smokeshow # Do not store coverage for all possible combinations to avoid file size max errors in Smokeshow
- name: Store coverage files - name: Store coverage files
if: matrix.coverage == 'coverage' if: matrix.coverage == 'coverage'
uses: actions/upload-artifact@v6 uses: actions/upload-artifact@v7
with: with:
name: coverage-${{ runner.os }}-${{ matrix.python-version }}-${{ hashFiles('**/coverage/.coverage.*') }} name: coverage-${{ runner.os }}-${{ matrix.python-version }}-${{ hashFiles('**/coverage/.coverage.*') }}
path: coverage path: coverage
include-hidden-files: true include-hidden-files: true
benchmark:
needs:
- changes
if: needs.changes.outputs.src == 'true' || github.ref == 'refs/heads/master'
runs-on: ubuntu-latest
env:
UV_PYTHON: "3.13"
UV_RESOLUTION: highest
steps:
- name: Dump GitHub context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.13"
- name: Setup uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
cache-dependency-glob: |
pyproject.toml
uv.lock
- name: Install Dependencies
run: uv sync --no-dev --group tests --extra all
- name: CodSpeed benchmarks
uses: CodSpeedHQ/action@v4
with:
mode: simulation
run: uv run --no-sync pytest tests/benchmarks --codspeed
coverage-combine: coverage-combine:
needs: needs:
- test - test
@ -155,7 +176,7 @@ jobs:
- name: Install Dependencies - name: Install Dependencies
run: uv sync --locked --no-dev --group tests --extra all run: uv sync --locked --no-dev --group tests --extra all
- name: Get coverage files - name: Get coverage files
uses: actions/download-artifact@v7 uses: actions/download-artifact@v8
with: with:
pattern: coverage-* pattern: coverage-*
path: coverage path: coverage
@ -164,7 +185,7 @@ jobs:
- run: uv run coverage combine coverage - run: uv run coverage combine coverage
- run: uv run coverage html --title "Coverage for ${{ github.sha }}" - run: uv run coverage html --title "Coverage for ${{ github.sha }}"
- name: Store coverage HTML - name: Store coverage HTML
uses: actions/upload-artifact@v6 uses: actions/upload-artifact@v7
with: with:
name: coverage-html name: coverage-html
path: htmlcov path: htmlcov
@ -176,6 +197,7 @@ jobs:
if: always() if: always()
needs: needs:
- coverage-combine - coverage-combine
- benchmark
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Dump GitHub context - name: Dump GitHub context
@ -186,4 +208,4 @@ jobs:
uses: re-actors/alls-green@release/v1 uses: re-actors/alls-green@release/v1
with: with:
jobs: ${{ toJSON(needs) }} jobs: ${{ toJSON(needs) }}
allowed-skips: coverage-combine,test allowed-skips: coverage-combine,test,benchmark

10
docs/de/docs/advanced/websockets.md

@ -38,13 +38,13 @@ In der Produktion hätten Sie eine der oben genannten Optionen.
Aber es ist der einfachste Weg, sich auf die Serverseite von WebSockets zu konzentrieren und ein funktionierendes Beispiel zu haben: Aber es ist der einfachste Weg, sich auf die Serverseite von WebSockets zu konzentrieren und ein funktionierendes Beispiel zu haben:
{* ../../docs_src/websockets/tutorial001_py310.py hl[2,6:38,41:43] *} {* ../../docs_src/websockets_/tutorial001_py310.py hl[2,6:38,41:43] *}
## Einen `websocket` erstellen { #create-a-websocket } ## Einen `websocket` erstellen { #create-a-websocket }
Erstellen Sie in Ihrer **FastAPI**-Anwendung einen `websocket`: Erstellen Sie in Ihrer **FastAPI**-Anwendung einen `websocket`:
{* ../../docs_src/websockets/tutorial001_py310.py hl[1,46:47] *} {* ../../docs_src/websockets_/tutorial001_py310.py hl[1,46:47] *}
/// note | Technische Details /// note | Technische Details
@ -58,7 +58,7 @@ Sie könnten auch `from starlette.websockets import WebSocket` verwenden.
In Ihrer WebSocket-Route können Sie Nachrichten `await`en und Nachrichten senden. In Ihrer WebSocket-Route können Sie Nachrichten `await`en und Nachrichten senden.
{* ../../docs_src/websockets/tutorial001_py310.py hl[48:52] *} {* ../../docs_src/websockets_/tutorial001_py310.py hl[48:52] *}
Sie können Binär-, Text- und JSON-Daten empfangen und senden. Sie können Binär-, Text- und JSON-Daten empfangen und senden.
@ -109,7 +109,7 @@ In WebSocket-Endpunkten können Sie Folgendes aus `fastapi` importieren und verw
Diese funktionieren auf die gleiche Weise wie für andere FastAPI-Endpunkte/*Pfadoperationen*: Diese funktionieren auf die gleiche Weise wie für andere FastAPI-Endpunkte/*Pfadoperationen*:
{* ../../docs_src/websockets/tutorial002_an_py310.py hl[68:69,82] *} {* ../../docs_src/websockets_/tutorial002_an_py310.py hl[68:69,82] *}
/// info | Info /// info | Info
@ -154,7 +154,7 @@ Damit können Sie den WebSocket verbinden und dann Nachrichten senden und empfan
Wenn eine WebSocket-Verbindung geschlossen wird, löst `await websocket.receive_text()` eine `WebSocketDisconnect`-Exception aus, die Sie dann wie in folgendem Beispiel abfangen und behandeln können. Wenn eine WebSocket-Verbindung geschlossen wird, löst `await websocket.receive_text()` eine `WebSocketDisconnect`-Exception aus, die Sie dann wie in folgendem Beispiel abfangen und behandeln können.
{* ../../docs_src/websockets/tutorial003_py310.py hl[79:81] *} {* ../../docs_src/websockets_/tutorial003_py310.py hl[79:81] *}
Zum Ausprobieren: Zum Ausprobieren:

41
docs/en/data/contributors.yml

@ -1,13 +1,18 @@
tiangolo: tiangolo:
login: tiangolo login: tiangolo
count: 871 count: 922
avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4 avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4
url: https://github.com/tiangolo url: https://github.com/tiangolo
dependabot: dependabot:
login: dependabot login: dependabot
count: 133 count: 142
avatarUrl: https://avatars.githubusercontent.com/in/29110?v=4 avatarUrl: https://avatars.githubusercontent.com/in/29110?v=4
url: https://github.com/apps/dependabot 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: alejsdev:
login: alejsdev login: alejsdev
count: 53 count: 53
@ -18,11 +23,6 @@ pre-commit-ci:
count: 50 count: 50
avatarUrl: https://avatars.githubusercontent.com/in/68672?v=4 avatarUrl: https://avatars.githubusercontent.com/in/68672?v=4
url: https://github.com/apps/pre-commit-ci 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: github-actions:
login: github-actions login: github-actions
count: 26 count: 26
@ -33,16 +33,16 @@ Kludex:
count: 25 count: 25
avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=df8a3f06ba8f55ae1967a3e2d5ed882903a4e330&v=4 avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=df8a3f06ba8f55ae1967a3e2d5ed882903a4e330&v=4
url: https://github.com/Kludex 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: dmontagu:
login: dmontagu login: dmontagu
count: 17 count: 17
avatarUrl: https://avatars.githubusercontent.com/u/35119617?u=540f30c937a6450812628b9592a1dfe91bbe148e&v=4 avatarUrl: https://avatars.githubusercontent.com/u/35119617?u=540f30c937a6450812628b9592a1dfe91bbe148e&v=4
url: https://github.com/dmontagu 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: nilslindemann:
login: nilslindemann login: nilslindemann
count: 15 count: 15
@ -148,6 +148,11 @@ AlexWendland:
count: 4 count: 4
avatarUrl: https://avatars.githubusercontent.com/u/3949212?u=c4c0c615e0ea33d00bfe16b779cf6ebc0f58071c&v=4 avatarUrl: https://avatars.githubusercontent.com/u/3949212?u=c4c0c615e0ea33d00bfe16b779cf6ebc0f58071c&v=4
url: https://github.com/AlexWendland 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: divums:
login: divums login: divums
count: 3 count: 3
@ -283,11 +288,6 @@ hamidrasti:
count: 3 count: 3
avatarUrl: https://avatars.githubusercontent.com/u/43915620?v=4 avatarUrl: https://avatars.githubusercontent.com/u/43915620?v=4
url: https://github.com/hamidrasti 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: kkinder:
login: kkinder login: kkinder
count: 2 count: 2
@ -521,7 +521,7 @@ s111d:
estebanx64: estebanx64:
login: estebanx64 login: estebanx64
count: 2 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 url: https://github.com/estebanx64
ndimares: ndimares:
login: ndimares login: ndimares
@ -573,3 +573,8 @@ Taoup:
count: 2 count: 2
avatarUrl: https://avatars.githubusercontent.com/u/22348542?v=4 avatarUrl: https://avatars.githubusercontent.com/u/22348542?v=4
url: https://github.com/Taoup 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

746
docs/en/data/people.yml

@ -1,23 +1,23 @@
maintainers: maintainers:
- login: tiangolo - login: tiangolo
answers: 1900 answers: 1925
avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4 avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4
url: https://github.com/tiangolo url: https://github.com/tiangolo
experts: experts:
- login: tiangolo - login: tiangolo
count: 1900 count: 1925
avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4 avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4
url: https://github.com/tiangolo url: https://github.com/tiangolo
- login: YuriiMotov - login: YuriiMotov
count: 971 count: 1120
avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=b9b13d598dddfab529a52d264df80a900bfe7060&v=4 avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=bc48be95c429989224786106b027f3c5e40cc354&v=4
url: https://github.com/YuriiMotov url: https://github.com/YuriiMotov
- login: github-actions - login: github-actions
count: 769 count: 770
avatarUrl: https://avatars.githubusercontent.com/in/15368?v=4 avatarUrl: https://avatars.githubusercontent.com/in/15368?v=4
url: https://github.com/apps/github-actions url: https://github.com/apps/github-actions
- login: Kludex - login: Kludex
count: 654 count: 656
avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=df8a3f06ba8f55ae1967a3e2d5ed882903a4e330&v=4 avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=df8a3f06ba8f55ae1967a3e2d5ed882903a4e330&v=4
url: https://github.com/Kludex url: https://github.com/Kludex
- login: jgould22 - login: jgould22
@ -37,7 +37,7 @@ experts:
avatarUrl: https://avatars.githubusercontent.com/u/62724709?u=f1e7bae394a315da950912c92dc861a8eaf95d4c&v=4 avatarUrl: https://avatars.githubusercontent.com/u/62724709?u=f1e7bae394a315da950912c92dc861a8eaf95d4c&v=4
url: https://github.com/ycd url: https://github.com/ycd
- login: JarroVGIT - login: JarroVGIT
count: 190 count: 192
avatarUrl: https://avatars.githubusercontent.com/u/13659033?u=e8bea32d07a5ef72f7dde3b2079ceb714923ca05&v=4 avatarUrl: https://avatars.githubusercontent.com/u/13659033?u=e8bea32d07a5ef72f7dde3b2079ceb714923ca05&v=4
url: https://github.com/JarroVGIT url: https://github.com/JarroVGIT
- login: euri10 - login: euri10
@ -53,11 +53,11 @@ experts:
avatarUrl: https://avatars.githubusercontent.com/u/331403?v=4 avatarUrl: https://avatars.githubusercontent.com/u/331403?v=4
url: https://github.com/phy25 url: https://github.com/phy25
- login: JavierSanchezCastro - login: JavierSanchezCastro
count: 94 count: 107
avatarUrl: https://avatars.githubusercontent.com/u/72013291?u=ae5679e6bd971d9d98cd5e76e8683f83642ba950&v=4 avatarUrl: https://avatars.githubusercontent.com/u/72013291?u=ae5679e6bd971d9d98cd5e76e8683f83642ba950&v=4
url: https://github.com/JavierSanchezCastro url: https://github.com/JavierSanchezCastro
- login: luzzodev - login: luzzodev
count: 89 count: 104
avatarUrl: https://avatars.githubusercontent.com/u/27291415?u=5607ae1ce75c5f54f09500ca854227f7bfd2033b&v=4 avatarUrl: https://avatars.githubusercontent.com/u/27291415?u=5607ae1ce75c5f54f09500ca854227f7bfd2033b&v=4
url: https://github.com/luzzodev url: https://github.com/luzzodev
- login: raphaelauv - login: raphaelauv
@ -81,32 +81,32 @@ experts:
avatarUrl: https://avatars.githubusercontent.com/u/653031?u=ad9838e089058c9e5a0bab94c0eec7cc181e0cd0&v=4 avatarUrl: https://avatars.githubusercontent.com/u/653031?u=ad9838e089058c9e5a0bab94c0eec7cc181e0cd0&v=4
url: https://github.com/falkben url: https://github.com/falkben
- login: yinziyan1206 - login: yinziyan1206
count: 54 count: 55
avatarUrl: https://avatars.githubusercontent.com/u/37829370?u=da44ca53aefd5c23f346fab8e9fd2e108294c179&v=4 avatarUrl: https://avatars.githubusercontent.com/u/37829370?u=da44ca53aefd5c23f346fab8e9fd2e108294c179&v=4
url: https://github.com/yinziyan1206 url: https://github.com/yinziyan1206
- login: acidjunk
count: 50
avatarUrl: https://avatars.githubusercontent.com/u/685002?u=b5094ab4527fc84b006c0ac9ff54367bdebb2267&v=4
url: https://github.com/acidjunk
- login: sm-Fifteen - login: sm-Fifteen
count: 49 count: 49
avatarUrl: https://avatars.githubusercontent.com/u/516999?u=437c0c5038558c67e887ccd863c1ba0f846c03da&v=4 avatarUrl: https://avatars.githubusercontent.com/u/516999?u=437c0c5038558c67e887ccd863c1ba0f846c03da&v=4
url: https://github.com/sm-Fifteen url: https://github.com/sm-Fifteen
- login: acidjunk
count: 49
avatarUrl: https://avatars.githubusercontent.com/u/685002?u=b5094ab4527fc84b006c0ac9ff54367bdebb2267&v=4
url: https://github.com/acidjunk
- login: adriangb - login: adriangb
count: 46 count: 46
avatarUrl: https://avatars.githubusercontent.com/u/1755071?u=612704256e38d6ac9cbed24f10e4b6ac2da74ecb&v=4 avatarUrl: https://avatars.githubusercontent.com/u/1755071?u=612704256e38d6ac9cbed24f10e4b6ac2da74ecb&v=4
url: https://github.com/adriangb url: https://github.com/adriangb
- login: Dustyposa
count: 45
avatarUrl: https://avatars.githubusercontent.com/u/27180793?u=5cf2877f50b3eb2bc55086089a78a36f07042889&v=4
url: https://github.com/Dustyposa
- login: insomnes - login: insomnes
count: 45 count: 45
avatarUrl: https://avatars.githubusercontent.com/u/16958893?u=f8be7088d5076d963984a21f95f44e559192d912&v=4 avatarUrl: https://avatars.githubusercontent.com/u/16958893?u=f8be7088d5076d963984a21f95f44e559192d912&v=4
url: https://github.com/insomnes url: https://github.com/insomnes
- login: Dustyposa
count: 45
avatarUrl: https://avatars.githubusercontent.com/u/27180793?u=5cf2877f50b3eb2bc55086089a78a36f07042889&v=4
url: https://github.com/Dustyposa
- login: frankie567 - login: frankie567
count: 43 count: 43
avatarUrl: https://avatars.githubusercontent.com/u/1144727?u=c159fe047727aedecbbeeaa96a1b03ceb9d39add&v=4 avatarUrl: https://avatars.githubusercontent.com/u/1144727?u=f3e79acfe4ed207e15c2145161a8a9759925fcd2&v=4
url: https://github.com/frankie567 url: https://github.com/frankie567
- login: odiseo0 - login: odiseo0
count: 43 count: 43
@ -120,14 +120,14 @@ experts:
count: 40 count: 40
avatarUrl: https://avatars.githubusercontent.com/u/11836741?u=8bd5ef7e62fe6a82055e33c4c0e0a7879ff8cfb6&v=4 avatarUrl: https://avatars.githubusercontent.com/u/11836741?u=8bd5ef7e62fe6a82055e33c4c0e0a7879ff8cfb6&v=4
url: https://github.com/includeamin url: https://github.com/includeamin
- login: STeveShary
count: 37
avatarUrl: https://avatars.githubusercontent.com/u/5167622?u=de8f597c81d6336fcebc37b32dfd61a3f877160c&v=4
url: https://github.com/STeveShary
- login: chbndrhnns - login: chbndrhnns
count: 37 count: 37
avatarUrl: https://avatars.githubusercontent.com/u/7534547?v=4 avatarUrl: https://avatars.githubusercontent.com/u/7534547?v=4
url: https://github.com/chbndrhnns url: https://github.com/chbndrhnns
- login: STeveShary
count: 37
avatarUrl: https://avatars.githubusercontent.com/u/5167622?u=de8f597c81d6336fcebc37b32dfd61a3f877160c&v=4
url: https://github.com/STeveShary
- login: krishnardt - login: krishnardt
count: 35 count: 35
avatarUrl: https://avatars.githubusercontent.com/u/31960541?u=47f4829c77f4962ab437ffb7995951e41eeebe9b&v=4 avatarUrl: https://avatars.githubusercontent.com/u/31960541?u=47f4829c77f4962ab437ffb7995951e41eeebe9b&v=4
@ -136,18 +136,22 @@ experts:
count: 32 count: 32
avatarUrl: https://avatars.githubusercontent.com/u/41326348?u=ba2fda6b30110411ecbf406d187907e2b420ac19&v=4 avatarUrl: https://avatars.githubusercontent.com/u/41326348?u=ba2fda6b30110411ecbf406d187907e2b420ac19&v=4
url: https://github.com/panla url: https://github.com/panla
- login: valentinDruzhinin
count: 30
avatarUrl: https://avatars.githubusercontent.com/u/12831905?u=aae1ebc675c91e8fa582df4fcc4fc4128106344d&v=4
url: https://github.com/valentinDruzhinin
- login: prostomarkeloff - login: prostomarkeloff
count: 28 count: 28
avatarUrl: https://avatars.githubusercontent.com/u/28061158?u=6918e39a1224194ba636e897461a02a20126d7ad&v=4 avatarUrl: https://avatars.githubusercontent.com/u/28061158?u=6918e39a1224194ba636e897461a02a20126d7ad&v=4
url: https://github.com/prostomarkeloff url: https://github.com/prostomarkeloff
- login: hasansezertasan
count: 27
avatarUrl: https://avatars.githubusercontent.com/u/13135006?u=99f0b0f0fc47e88e8abb337b4447357939ef93e7&v=4
url: https://github.com/hasansezertasan
- login: alv2017 - login: alv2017
count: 26 count: 27
avatarUrl: https://avatars.githubusercontent.com/u/31544722?v=4 avatarUrl: https://avatars.githubusercontent.com/u/31544722?v=4
url: https://github.com/alv2017 url: https://github.com/alv2017
- login: hasansezertasan
count: 27
avatarUrl: https://avatars.githubusercontent.com/u/13135006?u=d36995e41a00590da64e6204cfd112e0484ac1ca&v=4
url: https://github.com/hasansezertasan
- login: dbanty - login: dbanty
count: 26 count: 26
avatarUrl: https://avatars.githubusercontent.com/u/43723790?u=9d726785d08e50b1e1cd96505800c8ea8405bce2&v=4 avatarUrl: https://avatars.githubusercontent.com/u/43723790?u=9d726785d08e50b1e1cd96505800c8ea8405bce2&v=4
@ -156,10 +160,6 @@ experts:
count: 25 count: 25
avatarUrl: https://avatars.githubusercontent.com/u/365303?u=07ca03c5ee811eb0920e633cc3c3db73dbec1aa5&v=4 avatarUrl: https://avatars.githubusercontent.com/u/365303?u=07ca03c5ee811eb0920e633cc3c3db73dbec1aa5&v=4
url: https://github.com/wshayes url: https://github.com/wshayes
- login: valentinDruzhinin
count: 24
avatarUrl: https://avatars.githubusercontent.com/u/12831905?u=aae1ebc675c91e8fa582df4fcc4fc4128106344d&v=4
url: https://github.com/valentinDruzhinin
- login: SirTelemak - login: SirTelemak
count: 23 count: 23
avatarUrl: https://avatars.githubusercontent.com/u/9435877?u=719327b7d2c4c62212456d771bfa7c6b8dbb9eac&v=4 avatarUrl: https://avatars.githubusercontent.com/u/9435877?u=719327b7d2c4c62212456d771bfa7c6b8dbb9eac&v=4
@ -176,6 +176,10 @@ experts:
count: 22 count: 22
avatarUrl: https://avatars.githubusercontent.com/u/79946379?u=03d85b22d696a58a9603e55fbbbe2de6b0f4face&v=4 avatarUrl: https://avatars.githubusercontent.com/u/79946379?u=03d85b22d696a58a9603e55fbbbe2de6b0f4face&v=4
url: https://github.com/chrisK824 url: https://github.com/chrisK824
- login: ceb10n
count: 21
avatarUrl: https://avatars.githubusercontent.com/u/235213?u=edcce471814a1eba9f0cdaa4cd0de18921a940a6&v=4
url: https://github.com/ceb10n
- login: rafsaf - login: rafsaf
count: 21 count: 21
avatarUrl: https://avatars.githubusercontent.com/u/51059348?u=5fe59a56e1f2f9ccd8005d71752a8276f133ae1a&v=4 avatarUrl: https://avatars.githubusercontent.com/u/51059348?u=5fe59a56e1f2f9ccd8005d71752a8276f133ae1a&v=4
@ -194,7 +198,7 @@ experts:
url: https://github.com/ebottos94 url: https://github.com/ebottos94
- login: estebanx64 - login: estebanx64
count: 19 count: 19
avatarUrl: https://avatars.githubusercontent.com/u/10840422?u=1900887aeed268699e5ea6f3fb7db614f7b77cd3&v=4 avatarUrl: https://avatars.githubusercontent.com/u/10840422?u=2ca073ee47a625e495a9573bd374ddcd7be5ec91&v=4
url: https://github.com/estebanx64 url: https://github.com/estebanx64
- login: sehraramiz - login: sehraramiz
count: 18 count: 18
@ -236,467 +240,471 @@ experts:
count: 16 count: 16
avatarUrl: https://avatars.githubusercontent.com/u/26334101?u=f601c3f111f2148bd9244c2cb3ebbd57b592e674&v=4 avatarUrl: https://avatars.githubusercontent.com/u/26334101?u=f601c3f111f2148bd9244c2cb3ebbd57b592e674&v=4
url: https://github.com/jonatasoli url: https://github.com/jonatasoli
- login: ghost - login: mattmess1221
count: 15
avatarUrl: https://avatars.githubusercontent.com/u/10137?u=b1951d34a583cf12ec0d3b0781ba19be97726318&v=4
url: https://github.com/ghost
- login: abhint
count: 15 count: 15
avatarUrl: https://avatars.githubusercontent.com/u/25699289?u=b5d219277b4d001ac26fb8be357fddd88c29d51b&v=4 avatarUrl: https://avatars.githubusercontent.com/u/3409962?u=d22ea18aa8ea688af25a45df306134d593621a44&v=4
url: https://github.com/abhint url: https://github.com/mattmess1221
last_month_experts: last_month_experts:
- login: YuriiMotov - login: YuriiMotov
count: 17 count: 31
avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=b9b13d598dddfab529a52d264df80a900bfe7060&v=4 avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=bc48be95c429989224786106b027f3c5e40cc354&v=4
url: https://github.com/YuriiMotov url: https://github.com/YuriiMotov
- login: valentinDruzhinin - login: Toygarmetu
count: 8
avatarUrl: https://avatars.githubusercontent.com/u/92878791?u=538530cb6d5554e71f9c28709d794db9a74d23d9&v=4
url: https://github.com/Toygarmetu
- login: JavierSanchezCastro
count: 5 count: 5
avatarUrl: https://avatars.githubusercontent.com/u/12831905?u=aae1ebc675c91e8fa582df4fcc4fc4128106344d&v=4 avatarUrl: https://avatars.githubusercontent.com/u/72013291?u=ae5679e6bd971d9d98cd5e76e8683f83642ba950&v=4
url: https://github.com/valentinDruzhinin url: https://github.com/JavierSanchezCastro
- login: yinziyan1206
count: 4
avatarUrl: https://avatars.githubusercontent.com/u/37829370?u=da44ca53aefd5c23f346fab8e9fd2e108294c179&v=4
url: https://github.com/yinziyan1206
- login: tiangolo - login: tiangolo
count: 2 count: 3
avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4 avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4
url: https://github.com/tiangolo url: https://github.com/tiangolo
- login: luzzodev - login: valentinDruzhinin
count: 2 count: 2
avatarUrl: https://avatars.githubusercontent.com/u/27291415?u=5607ae1ce75c5f54f09500ca854227f7bfd2033b&v=4 avatarUrl: https://avatars.githubusercontent.com/u/12831905?u=aae1ebc675c91e8fa582df4fcc4fc4128106344d&v=4
url: https://github.com/luzzodev url: https://github.com/valentinDruzhinin
three_months_experts: three_months_experts:
- login: YuriiMotov - login: YuriiMotov
count: 397 count: 91
avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=b9b13d598dddfab529a52d264df80a900bfe7060&v=4 avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=bc48be95c429989224786106b027f3c5e40cc354&v=4
url: https://github.com/YuriiMotov url: https://github.com/YuriiMotov
- login: valentinDruzhinin
count: 24
avatarUrl: https://avatars.githubusercontent.com/u/12831905?u=aae1ebc675c91e8fa582df4fcc4fc4128106344d&v=4
url: https://github.com/valentinDruzhinin
- login: luzzodev
count: 17
avatarUrl: https://avatars.githubusercontent.com/u/27291415?u=5607ae1ce75c5f54f09500ca854227f7bfd2033b&v=4
url: https://github.com/luzzodev
- login: raceychan
count: 6
avatarUrl: https://avatars.githubusercontent.com/u/75417963?u=060c62870ec5a791765e63ac20d8885d11143786&v=4
url: https://github.com/raceychan
- login: yinziyan1206
count: 5
avatarUrl: https://avatars.githubusercontent.com/u/37829370?u=da44ca53aefd5c23f346fab8e9fd2e108294c179&v=4
url: https://github.com/yinziyan1206
- login: DoctorJohn
count: 5
avatarUrl: https://avatars.githubusercontent.com/u/14076775?u=2913e70a6142772847e91e2aaa5b9152391715e9&v=4
url: https://github.com/DoctorJohn
- login: tiangolo - login: tiangolo
count: 4 count: 13
avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4 avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4
url: https://github.com/tiangolo url: https://github.com/tiangolo
- login: sachinh35 - login: Toygarmetu
count: 8
avatarUrl: https://avatars.githubusercontent.com/u/92878791?u=538530cb6d5554e71f9c28709d794db9a74d23d9&v=4
url: https://github.com/Toygarmetu
- login: JavierSanchezCastro
count: 7
avatarUrl: https://avatars.githubusercontent.com/u/72013291?u=ae5679e6bd971d9d98cd5e76e8683f83642ba950&v=4
url: https://github.com/JavierSanchezCastro
- login: ceb10n
count: 5
avatarUrl: https://avatars.githubusercontent.com/u/235213?u=edcce471814a1eba9f0cdaa4cd0de18921a940a6&v=4
url: https://github.com/ceb10n
- login: valentinDruzhinin
count: 4 count: 4
avatarUrl: https://avatars.githubusercontent.com/u/12831905?u=aae1ebc675c91e8fa582df4fcc4fc4128106344d&v=4
url: https://github.com/valentinDruzhinin
- login: sachinh35
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/21972708?u=8560b97b8b41e175f476270b56de8a493b84f302&v=4 avatarUrl: https://avatars.githubusercontent.com/u/21972708?u=8560b97b8b41e175f476270b56de8a493b84f302&v=4
url: https://github.com/sachinh35 url: https://github.com/sachinh35
- login: eqsdxr - login: RichieB2B
count: 4
avatarUrl: https://avatars.githubusercontent.com/u/157279130?u=58fddf77ed76966eaa8c73eea9bea4bb0c53b673&v=4
url: https://github.com/eqsdxr
- login: Jelle-tenB
count: 3 count: 3
avatarUrl: https://avatars.githubusercontent.com/u/210023470?u=c25d66addf36a747bd9fab773c4a6e7b238f45d4&v=4 avatarUrl: https://avatars.githubusercontent.com/u/1461970?u=edaa57d1077705244ea5c9244f4783d94ff11f12&v=4
url: https://github.com/Jelle-tenB url: https://github.com/RichieB2B
- login: pythonweb2 - login: EmmanuelNiyonshuti
count: 2 count: 2
avatarUrl: https://avatars.githubusercontent.com/u/32141163?v=4 avatarUrl: https://avatars.githubusercontent.com/u/142030687?u=ab131d5ad4670280a978f489babe71c9bf9c1097&v=4
url: https://github.com/pythonweb2 url: https://github.com/EmmanuelNiyonshuti
- login: WilliamDEdwards - login: luzzodev
count: 2 count: 2
avatarUrl: https://avatars.githubusercontent.com/u/12184311?u=9b29d5d1d71f5f1a7ef9e439963ad3529e3b33a4&v=4 avatarUrl: https://avatars.githubusercontent.com/u/27291415?u=5607ae1ce75c5f54f09500ca854227f7bfd2033b&v=4
url: https://github.com/WilliamDEdwards url: https://github.com/luzzodev
- login: Brikas - login: davidbrochart
count: 2 count: 2
avatarUrl: https://avatars.githubusercontent.com/u/80290187?v=4 avatarUrl: https://avatars.githubusercontent.com/u/4711805?u=d39696d995a9e02ec3613ffb2f62b20b14f92f26&v=4
url: https://github.com/Brikas url: https://github.com/davidbrochart
- login: purepani - login: CharlieReitzel
count: 2 count: 2
avatarUrl: https://avatars.githubusercontent.com/u/7587353?v=4 avatarUrl: https://avatars.githubusercontent.com/u/20848272?v=4
url: https://github.com/purepani url: https://github.com/CharlieReitzel
- login: JavierSanchezCastro - login: dotmitsu
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/72013291?u=ae5679e6bd971d9d98cd5e76e8683f83642ba950&v=4
url: https://github.com/JavierSanchezCastro
- login: TaigoFr
count: 2 count: 2
avatarUrl: https://avatars.githubusercontent.com/u/17792131?u=372b27056ec82f1ae03d8b3f37ef55b04a7cfdd1&v=4 avatarUrl: https://avatars.githubusercontent.com/u/42657211?u=3bccc9a2f386a3f24230ec393080f8904fe2a5b2&v=4
url: https://github.com/TaigoFr url: https://github.com/dotmitsu
- login: Garrett-R - login: dolfinus
count: 2 count: 2
avatarUrl: https://avatars.githubusercontent.com/u/6614695?u=c128fd775002882f6e391bda5a89d1bdc5bdf45f&v=4 avatarUrl: https://avatars.githubusercontent.com/u/4661021?u=ed5ddadcf36d9b943ebe61febe0b96ee34e5425d&v=4
url: https://github.com/Garrett-R url: https://github.com/dolfinus
- login: jymchng - login: garg-khushi
count: 2 count: 2
avatarUrl: https://avatars.githubusercontent.com/u/27895426?u=fb88c47775147d62a395fdb895d1af4148c7b566&v=4 avatarUrl: https://avatars.githubusercontent.com/u/139839680?u=7faffa70275f8ab16f163e0c742a11d2662f9c66&v=4
url: https://github.com/jymchng url: https://github.com/garg-khushi
- login: davidhuser - login: florentx
count: 2 count: 2
avatarUrl: https://avatars.githubusercontent.com/u/4357648?u=6ed702f8f6d49a8b2a0ed33cbd8ab59c2d7db7f7&v=4 avatarUrl: https://avatars.githubusercontent.com/u/142113?u=bf10f10080026346b092633c380977b61cee0d9c&v=4
url: https://github.com/davidhuser url: https://github.com/florentx
six_months_experts: six_months_experts:
- login: YuriiMotov - login: YuriiMotov
count: 763 count: 163
avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=b9b13d598dddfab529a52d264df80a900bfe7060&v=4 avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=bc48be95c429989224786106b027f3c5e40cc354&v=4
url: https://github.com/YuriiMotov url: https://github.com/YuriiMotov
- login: luzzodev
count: 45
avatarUrl: https://avatars.githubusercontent.com/u/27291415?u=5607ae1ce75c5f54f09500ca854227f7bfd2033b&v=4
url: https://github.com/luzzodev
- login: valentinDruzhinin
count: 24
avatarUrl: https://avatars.githubusercontent.com/u/12831905?u=aae1ebc675c91e8fa582df4fcc4fc4128106344d&v=4
url: https://github.com/valentinDruzhinin
- login: alv2017
count: 16
avatarUrl: https://avatars.githubusercontent.com/u/31544722?v=4
url: https://github.com/alv2017
- login: sachinh35
count: 9
avatarUrl: https://avatars.githubusercontent.com/u/21972708?u=8560b97b8b41e175f476270b56de8a493b84f302&v=4
url: https://github.com/sachinh35
- login: yauhen-sobaleu
count: 9
avatarUrl: https://avatars.githubusercontent.com/u/51629535?u=fc1817060daf2df438bfca86c44f33da5cd667db&v=4
url: https://github.com/yauhen-sobaleu
- login: tiangolo - login: tiangolo
count: 6 count: 24
avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4 avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4
url: https://github.com/tiangolo url: https://github.com/tiangolo
- login: luzzodev
count: 15
avatarUrl: https://avatars.githubusercontent.com/u/27291415?u=5607ae1ce75c5f54f09500ca854227f7bfd2033b&v=4
url: https://github.com/luzzodev
- login: engripaye
count: 14
avatarUrl: https://avatars.githubusercontent.com/u/155247530?u=645169bc81856b7f1bd20090ecb0171a56dcbeb4&v=4
url: https://github.com/engripaye
- login: JavierSanchezCastro - login: JavierSanchezCastro
count: 6 count: 13
avatarUrl: https://avatars.githubusercontent.com/u/72013291?u=ae5679e6bd971d9d98cd5e76e8683f83642ba950&v=4 avatarUrl: https://avatars.githubusercontent.com/u/72013291?u=ae5679e6bd971d9d98cd5e76e8683f83642ba950&v=4
url: https://github.com/JavierSanchezCastro url: https://github.com/JavierSanchezCastro
- login: raceychan - login: Toygarmetu
count: 8
avatarUrl: https://avatars.githubusercontent.com/u/92878791?u=538530cb6d5554e71f9c28709d794db9a74d23d9&v=4
url: https://github.com/Toygarmetu
- login: valentinDruzhinin
count: 6 count: 6
avatarUrl: https://avatars.githubusercontent.com/u/75417963?u=060c62870ec5a791765e63ac20d8885d11143786&v=4 avatarUrl: https://avatars.githubusercontent.com/u/12831905?u=aae1ebc675c91e8fa582df4fcc4fc4128106344d&v=4
url: https://github.com/raceychan url: https://github.com/valentinDruzhinin
- login: yinziyan1206 - login: ceb10n
count: 5 count: 5
avatarUrl: https://avatars.githubusercontent.com/u/37829370?u=da44ca53aefd5c23f346fab8e9fd2e108294c179&v=4 avatarUrl: https://avatars.githubusercontent.com/u/235213?u=edcce471814a1eba9f0cdaa4cd0de18921a940a6&v=4
url: https://github.com/yinziyan1206 url: https://github.com/ceb10n
- login: DoctorJohn - login: RichieB2B
count: 5 count: 5
avatarUrl: https://avatars.githubusercontent.com/u/14076775?u=2913e70a6142772847e91e2aaa5b9152391715e9&v=4 avatarUrl: https://avatars.githubusercontent.com/u/1461970?u=edaa57d1077705244ea5c9244f4783d94ff11f12&v=4
url: https://github.com/DoctorJohn url: https://github.com/RichieB2B
- login: eqsdxr - login: JunjieAraoXiong
count: 5
avatarUrl: https://avatars.githubusercontent.com/u/167785867?u=b69afe090c8bf5fd73f2d23fc3a887b28f68f192&v=4
url: https://github.com/JunjieAraoXiong
- login: CodeKraken-cmd
count: 5
avatarUrl: https://avatars.githubusercontent.com/u/48470371?u=e7c0e7ec8e35ca5fb3ae40a586ed5e788fd0fe6d&v=4
url: https://github.com/CodeKraken-cmd
- login: svlandeg
count: 5
avatarUrl: https://avatars.githubusercontent.com/u/8796347?u=556c97650c27021911b0b9447ec55e75987b0e8a&v=4
url: https://github.com/svlandeg
- login: ArmanShirzad
count: 4 count: 4
avatarUrl: https://avatars.githubusercontent.com/u/157279130?u=58fddf77ed76966eaa8c73eea9bea4bb0c53b673&v=4 avatarUrl: https://avatars.githubusercontent.com/u/68951175?u=1f1efae2fa5d0d17c38a1a8413bedca5e538cedb&v=4
url: https://github.com/eqsdxr url: https://github.com/ArmanShirzad
- login: Kludex - login: krylosov-aa
count: 4 count: 4
avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=df8a3f06ba8f55ae1967a3e2d5ed882903a4e330&v=4 avatarUrl: https://avatars.githubusercontent.com/u/242901957?u=4c9c7b468203b09bca64936fb464620e32cdd252&v=4
url: https://github.com/Kludex url: https://github.com/krylosov-aa
- login: Jelle-tenB - login: sachinh35
count: 3 count: 3
avatarUrl: https://avatars.githubusercontent.com/u/210023470?u=c25d66addf36a747bd9fab773c4a6e7b238f45d4&v=4 avatarUrl: https://avatars.githubusercontent.com/u/21972708?u=8560b97b8b41e175f476270b56de8a493b84f302&v=4
url: https://github.com/Jelle-tenB url: https://github.com/sachinh35
- login: adsouza - login: simone-trubian
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/5606840?u=65703af3c605feca61ce49e4009bb4e26495b425&v=4
url: https://github.com/simone-trubian
- login: mahimairaja
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/81288263?u=4eef6b4a36b96e84bd666fc1937aa589036ccb9a&v=4
url: https://github.com/mahimairaja
- login: pankeshpatel
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/1482917?u=666f39197a88cfa38b8bd78d39ef04d95c948b6b&v=4
url: https://github.com/pankeshpatel
- login: EmmanuelNiyonshuti
count: 2 count: 2
avatarUrl: https://avatars.githubusercontent.com/u/275832?u=f90f110cfafeafed2f14339e840941c2c328c186&v=4 avatarUrl: https://avatars.githubusercontent.com/u/142030687?u=ab131d5ad4670280a978f489babe71c9bf9c1097&v=4
url: https://github.com/adsouza url: https://github.com/EmmanuelNiyonshuti
- login: pythonweb2 - login: huynguyengl99
count: 2 count: 2
avatarUrl: https://avatars.githubusercontent.com/u/32141163?v=4 avatarUrl: https://avatars.githubusercontent.com/u/49433085?u=7b626115686c5d97a2a32a03119f5300e425cc9f&v=4
url: https://github.com/pythonweb2 url: https://github.com/huynguyengl99
- login: WilliamDEdwards - login: davidbrochart
count: 2 count: 2
avatarUrl: https://avatars.githubusercontent.com/u/12184311?u=9b29d5d1d71f5f1a7ef9e439963ad3529e3b33a4&v=4 avatarUrl: https://avatars.githubusercontent.com/u/4711805?u=d39696d995a9e02ec3613ffb2f62b20b14f92f26&v=4
url: https://github.com/WilliamDEdwards url: https://github.com/davidbrochart
- login: Brikas - login: CharlieReitzel
count: 2 count: 2
avatarUrl: https://avatars.githubusercontent.com/u/80290187?v=4 avatarUrl: https://avatars.githubusercontent.com/u/20848272?v=4
url: https://github.com/Brikas url: https://github.com/CharlieReitzel
- login: purepani - login: dotmitsu
count: 2 count: 2
avatarUrl: https://avatars.githubusercontent.com/u/7587353?v=4 avatarUrl: https://avatars.githubusercontent.com/u/42657211?u=3bccc9a2f386a3f24230ec393080f8904fe2a5b2&v=4
url: https://github.com/purepani url: https://github.com/dotmitsu
- login: TaigoFr - login: dolfinus
count: 2 count: 2
avatarUrl: https://avatars.githubusercontent.com/u/17792131?u=372b27056ec82f1ae03d8b3f37ef55b04a7cfdd1&v=4 avatarUrl: https://avatars.githubusercontent.com/u/4661021?u=ed5ddadcf36d9b943ebe61febe0b96ee34e5425d&v=4
url: https://github.com/TaigoFr url: https://github.com/dolfinus
- login: Garrett-R - login: Kludex
count: 2 count: 2
avatarUrl: https://avatars.githubusercontent.com/u/6614695?u=c128fd775002882f6e391bda5a89d1bdc5bdf45f&v=4 avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=df8a3f06ba8f55ae1967a3e2d5ed882903a4e330&v=4
url: https://github.com/Garrett-R url: https://github.com/Kludex
- login: EverStarck - login: garg-khushi
count: 2 count: 2
avatarUrl: https://avatars.githubusercontent.com/u/51029456?u=343409b7cb6b3ea6a59359f4e8370d9c3f140ecd&v=4 avatarUrl: https://avatars.githubusercontent.com/u/139839680?u=7faffa70275f8ab16f163e0c742a11d2662f9c66&v=4
url: https://github.com/EverStarck url: https://github.com/garg-khushi
- login: henrymcl - login: skion
count: 2 count: 2
avatarUrl: https://avatars.githubusercontent.com/u/26480299?v=4 avatarUrl: https://avatars.githubusercontent.com/u/532192?v=4
url: https://github.com/henrymcl url: https://github.com/skion
- login: jymchng - login: florentx
count: 2 count: 2
avatarUrl: https://avatars.githubusercontent.com/u/27895426?u=fb88c47775147d62a395fdb895d1af4148c7b566&v=4 avatarUrl: https://avatars.githubusercontent.com/u/142113?u=bf10f10080026346b092633c380977b61cee0d9c&v=4
url: https://github.com/jymchng url: https://github.com/florentx
- login: davidhuser - login: jc-louis
count: 2 count: 2
avatarUrl: https://avatars.githubusercontent.com/u/4357648?u=6ed702f8f6d49a8b2a0ed33cbd8ab59c2d7db7f7&v=4 avatarUrl: https://avatars.githubusercontent.com/u/51329768?v=4
url: https://github.com/davidhuser url: https://github.com/jc-louis
- login: PidgeyBE - login: WilliamDEdwards
count: 2 count: 2
avatarUrl: https://avatars.githubusercontent.com/u/19860056?u=47b584eb1c1ab45e31c1b474109a962d7e82be49&v=4 avatarUrl: https://avatars.githubusercontent.com/u/12184311?u=9b29d5d1d71f5f1a7ef9e439963ad3529e3b33a4&v=4
url: https://github.com/PidgeyBE url: https://github.com/WilliamDEdwards
- login: KianAnbarestani - login: bughuntr7
count: 2 count: 2
avatarUrl: https://avatars.githubusercontent.com/u/145364424?u=dcc3d8fb4ca07d36fb52a17f38b6650565de40be&v=4 avatarUrl: https://avatars.githubusercontent.com/u/236391583?u=7f51ff690e3a5711f845a115903c39e21c8af938&v=4
url: https://github.com/KianAnbarestani url: https://github.com/bughuntr7
- login: jgould22 - login: jymchng
count: 2 count: 2
avatarUrl: https://avatars.githubusercontent.com/u/4335847?u=ed77f67e0bb069084639b24d812dbb2a2b1dc554&v=4 avatarUrl: https://avatars.githubusercontent.com/u/27895426?u=fb88c47775147d62a395fdb895d1af4148c7b566&v=4
url: https://github.com/jgould22 url: https://github.com/jymchng
- login: marsboy02 - login: XieJiSS
count: 2 count: 2
avatarUrl: https://avatars.githubusercontent.com/u/86903678?u=04cc319d6605f8d1ba3a0bed9f4f55a582719ae6&v=4 avatarUrl: https://avatars.githubusercontent.com/u/24671280?u=7ea0d9bfe46cf762594d62fd2f3c6d3813c3584c&v=4
url: https://github.com/marsboy02 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
one_year_experts: one_year_experts:
- login: YuriiMotov - login: YuriiMotov
count: 824 count: 918
avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=b9b13d598dddfab529a52d264df80a900bfe7060&v=4 avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=bc48be95c429989224786106b027f3c5e40cc354&v=4
url: https://github.com/YuriiMotov url: https://github.com/YuriiMotov
- login: luzzodev - login: luzzodev
count: 89 count: 60
avatarUrl: https://avatars.githubusercontent.com/u/27291415?u=5607ae1ce75c5f54f09500ca854227f7bfd2033b&v=4 avatarUrl: https://avatars.githubusercontent.com/u/27291415?u=5607ae1ce75c5f54f09500ca854227f7bfd2033b&v=4
url: https://github.com/luzzodev url: https://github.com/luzzodev
- login: Kludex - login: tiangolo
count: 50 count: 31
avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=df8a3f06ba8f55ae1967a3e2d5ed882903a4e330&v=4 avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4
url: https://github.com/Kludex url: https://github.com/tiangolo
- login: sinisaos
count: 33
avatarUrl: https://avatars.githubusercontent.com/u/30960668?v=4
url: https://github.com/sinisaos
- login: alv2017
count: 26
avatarUrl: https://avatars.githubusercontent.com/u/31544722?v=4
url: https://github.com/alv2017
- login: valentinDruzhinin - login: valentinDruzhinin
count: 24 count: 30
avatarUrl: https://avatars.githubusercontent.com/u/12831905?u=aae1ebc675c91e8fa582df4fcc4fc4128106344d&v=4 avatarUrl: https://avatars.githubusercontent.com/u/12831905?u=aae1ebc675c91e8fa582df4fcc4fc4128106344d&v=4
url: https://github.com/valentinDruzhinin url: https://github.com/valentinDruzhinin
- login: JavierSanchezCastro - login: JavierSanchezCastro
count: 24 count: 19
avatarUrl: https://avatars.githubusercontent.com/u/72013291?u=ae5679e6bd971d9d98cd5e76e8683f83642ba950&v=4 avatarUrl: https://avatars.githubusercontent.com/u/72013291?u=ae5679e6bd971d9d98cd5e76e8683f83642ba950&v=4
url: https://github.com/JavierSanchezCastro url: https://github.com/JavierSanchezCastro
- login: jgould22 - login: alv2017
count: 17 count: 17
avatarUrl: https://avatars.githubusercontent.com/u/4335847?u=ed77f67e0bb069084639b24d812dbb2a2b1dc554&v=4 avatarUrl: https://avatars.githubusercontent.com/u/31544722?v=4
url: https://github.com/jgould22 url: https://github.com/alv2017
- login: tiangolo - login: engripaye
count: 14 count: 14
avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4 avatarUrl: https://avatars.githubusercontent.com/u/155247530?u=645169bc81856b7f1bd20090ecb0171a56dcbeb4&v=4
url: https://github.com/tiangolo url: https://github.com/engripaye
- login: Kfir-G
count: 13
avatarUrl: https://avatars.githubusercontent.com/u/57500876?u=a3bf923ab27bce3d1b13779a8dd22eb7675017fd&v=4
url: https://github.com/Kfir-G
- login: sehraramiz
count: 11
avatarUrl: https://avatars.githubusercontent.com/u/14166324?u=8fac65e84dfff24245d304a5b5b09f7b5bd69dc9&v=4
url: https://github.com/sehraramiz
- login: sachinh35 - login: sachinh35
count: 9 count: 12
avatarUrl: https://avatars.githubusercontent.com/u/21972708?u=8560b97b8b41e175f476270b56de8a493b84f302&v=4 avatarUrl: https://avatars.githubusercontent.com/u/21972708?u=8560b97b8b41e175f476270b56de8a493b84f302&v=4
url: https://github.com/sachinh35 url: https://github.com/sachinh35
- login: yauhen-sobaleu - login: yauhen-sobaleu
count: 9 count: 9
avatarUrl: https://avatars.githubusercontent.com/u/51629535?u=fc1817060daf2df438bfca86c44f33da5cd667db&v=4 avatarUrl: https://avatars.githubusercontent.com/u/51629535?u=fc1817060daf2df438bfca86c44f33da5cd667db&v=4
url: https://github.com/yauhen-sobaleu url: https://github.com/yauhen-sobaleu
- login: estebanx64 - login: Toygarmetu
count: 7 count: 8
avatarUrl: https://avatars.githubusercontent.com/u/10840422?u=1900887aeed268699e5ea6f3fb7db614f7b77cd3&v=4 avatarUrl: https://avatars.githubusercontent.com/u/92878791?u=538530cb6d5554e71f9c28709d794db9a74d23d9&v=4
url: https://github.com/estebanx64 url: https://github.com/Toygarmetu
- login: ceb10n
count: 7
avatarUrl: https://avatars.githubusercontent.com/u/235213?u=edcce471814a1eba9f0cdaa4cd0de18921a940a6&v=4
url: https://github.com/ceb10n
- login: yvallois
count: 7
avatarUrl: https://avatars.githubusercontent.com/u/36999744?v=4
url: https://github.com/yvallois
- login: raceychan - login: raceychan
count: 6 count: 6
avatarUrl: https://avatars.githubusercontent.com/u/75417963?u=060c62870ec5a791765e63ac20d8885d11143786&v=4 avatarUrl: https://avatars.githubusercontent.com/u/75417963?u=060c62870ec5a791765e63ac20d8885d11143786&v=4
url: https://github.com/raceychan url: https://github.com/raceychan
- login: yinziyan1206 - login: yinziyan1206
count: 5 count: 6
avatarUrl: https://avatars.githubusercontent.com/u/37829370?u=da44ca53aefd5c23f346fab8e9fd2e108294c179&v=4 avatarUrl: https://avatars.githubusercontent.com/u/37829370?u=da44ca53aefd5c23f346fab8e9fd2e108294c179&v=4
url: https://github.com/yinziyan1206 url: https://github.com/yinziyan1206
- login: Kludex
count: 6
avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=df8a3f06ba8f55ae1967a3e2d5ed882903a4e330&v=4
url: https://github.com/Kludex
- login: ceb10n
count: 5
avatarUrl: https://avatars.githubusercontent.com/u/235213?u=edcce471814a1eba9f0cdaa4cd0de18921a940a6&v=4
url: https://github.com/ceb10n
- login: RichieB2B
count: 5
avatarUrl: https://avatars.githubusercontent.com/u/1461970?u=edaa57d1077705244ea5c9244f4783d94ff11f12&v=4
url: https://github.com/RichieB2B
- login: JunjieAraoXiong
count: 5
avatarUrl: https://avatars.githubusercontent.com/u/167785867?u=b69afe090c8bf5fd73f2d23fc3a887b28f68f192&v=4
url: https://github.com/JunjieAraoXiong
- login: CodeKraken-cmd
count: 5
avatarUrl: https://avatars.githubusercontent.com/u/48470371?u=e7c0e7ec8e35ca5fb3ae40a586ed5e788fd0fe6d&v=4
url: https://github.com/CodeKraken-cmd
- login: svlandeg
count: 5
avatarUrl: https://avatars.githubusercontent.com/u/8796347?u=556c97650c27021911b0b9447ec55e75987b0e8a&v=4
url: https://github.com/svlandeg
- login: DoctorJohn - login: DoctorJohn
count: 5 count: 5
avatarUrl: https://avatars.githubusercontent.com/u/14076775?u=2913e70a6142772847e91e2aaa5b9152391715e9&v=4 avatarUrl: https://avatars.githubusercontent.com/u/14076775?u=ec43fe79a98dbc864b428afc7220753e25ca3af2&v=4
url: https://github.com/DoctorJohn url: https://github.com/DoctorJohn
- login: n8sty
count: 5
avatarUrl: https://avatars.githubusercontent.com/u/2964996?v=4
url: https://github.com/n8sty
- login: pythonweb2
count: 4
avatarUrl: https://avatars.githubusercontent.com/u/32141163?v=4
url: https://github.com/pythonweb2
- login: eqsdxr
count: 4
avatarUrl: https://avatars.githubusercontent.com/u/157279130?u=58fddf77ed76966eaa8c73eea9bea4bb0c53b673&v=4
url: https://github.com/eqsdxr
- login: yokwejuste
count: 4
avatarUrl: https://avatars.githubusercontent.com/u/71908316?u=4ba43bd63c169b5c015137d8916752a44001445a&v=4
url: https://github.com/yokwejuste
- login: WilliamDEdwards - login: WilliamDEdwards
count: 3 count: 4
avatarUrl: https://avatars.githubusercontent.com/u/12184311?u=9b29d5d1d71f5f1a7ef9e439963ad3529e3b33a4&v=4 avatarUrl: https://avatars.githubusercontent.com/u/12184311?u=9b29d5d1d71f5f1a7ef9e439963ad3529e3b33a4&v=4
url: https://github.com/WilliamDEdwards url: https://github.com/WilliamDEdwards
- login: mattmess1221 - login: ArmanShirzad
count: 3 count: 4
avatarUrl: https://avatars.githubusercontent.com/u/3409962?u=d22ea18aa8ea688af25a45df306134d593621a44&v=4 avatarUrl: https://avatars.githubusercontent.com/u/68951175?u=1f1efae2fa5d0d17c38a1a8413bedca5e538cedb&v=4
url: https://github.com/mattmess1221 url: https://github.com/ArmanShirzad
- login: Jelle-tenB - login: krylosov-aa
count: 3 count: 4
avatarUrl: https://avatars.githubusercontent.com/u/210023470?u=c25d66addf36a747bd9fab773c4a6e7b238f45d4&v=4 avatarUrl: https://avatars.githubusercontent.com/u/242901957?u=4c9c7b468203b09bca64936fb464620e32cdd252&v=4
url: https://github.com/Jelle-tenB url: https://github.com/krylosov-aa
- login: viniciusCalcantara - login: isgin01
count: 4
avatarUrl: https://avatars.githubusercontent.com/u/157279130?u=16d6466476cf7dbc55a4cd575b6ea920ebdd81e1&v=4
url: https://github.com/isgin01
- login: sinisaos
count: 3 count: 3
avatarUrl: https://avatars.githubusercontent.com/u/108818737?u=80f3ec7427fa6a41d5896984d0c526432f2299fa&v=4 avatarUrl: https://avatars.githubusercontent.com/u/30960668?v=4
url: https://github.com/viniciusCalcantara url: https://github.com/sinisaos
- login: davidhuser - login: dolfinus
count: 3 count: 3
avatarUrl: https://avatars.githubusercontent.com/u/4357648?u=6ed702f8f6d49a8b2a0ed33cbd8ab59c2d7db7f7&v=4 avatarUrl: https://avatars.githubusercontent.com/u/4661021?u=ed5ddadcf36d9b943ebe61febe0b96ee34e5425d&v=4
url: https://github.com/davidhuser url: https://github.com/dolfinus
- login: dbfreem - login: jymchng
count: 3 count: 3
avatarUrl: https://avatars.githubusercontent.com/u/9778569?u=f2f1e9135b5e4f1b0c6821a548b17f97572720fc&v=4 avatarUrl: https://avatars.githubusercontent.com/u/27895426?u=fb88c47775147d62a395fdb895d1af4148c7b566&v=4
url: https://github.com/dbfreem url: https://github.com/jymchng
- login: SobikXexe - login: simone-trubian
count: 3 count: 3
avatarUrl: https://avatars.githubusercontent.com/u/87701130?v=4 avatarUrl: https://avatars.githubusercontent.com/u/5606840?u=65703af3c605feca61ce49e4009bb4e26495b425&v=4
url: https://github.com/SobikXexe url: https://github.com/simone-trubian
- login: pawelad - login: mahimairaja
count: 3 count: 3
avatarUrl: https://avatars.githubusercontent.com/u/7062874?u=d27dc220545a8401ad21840590a97d474d7101e6&v=4 avatarUrl: https://avatars.githubusercontent.com/u/81288263?u=4eef6b4a36b96e84bd666fc1937aa589036ccb9a&v=4
url: https://github.com/pawelad url: https://github.com/mahimairaja
- login: Isuxiz - login: pankeshpatel
count: 3 count: 3
avatarUrl: https://avatars.githubusercontent.com/u/48672727?u=34d7b4ade252687d22a27cf53037b735b244bfc1&v=4 avatarUrl: https://avatars.githubusercontent.com/u/1482917?u=666f39197a88cfa38b8bd78d39ef04d95c948b6b&v=4
url: https://github.com/Isuxiz url: https://github.com/pankeshpatel
- login: Minibrams - login: Jelle-tenB
count: 3 count: 3
avatarUrl: https://avatars.githubusercontent.com/u/8108085?u=b028dbc308fa8485e0e2e9402b3d03d8deb22bf9&v=4 avatarUrl: https://avatars.githubusercontent.com/u/210023470?u=c25d66addf36a747bd9fab773c4a6e7b238f45d4&v=4
url: https://github.com/Minibrams url: https://github.com/Jelle-tenB
- login: adsouza - 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 count: 2
avatarUrl: https://avatars.githubusercontent.com/u/275832?u=f90f110cfafeafed2f14339e840941c2c328c186&v=4 avatarUrl: https://avatars.githubusercontent.com/u/17792131?u=372b27056ec82f1ae03d8b3f37ef55b04a7cfdd1&v=4
url: https://github.com/adsouza url: https://github.com/TaigoFr
- login: Synrom - 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 count: 2
avatarUrl: https://avatars.githubusercontent.com/u/30272537?v=4 avatarUrl: https://avatars.githubusercontent.com/u/56644812?u=a7dd773084f1c17c5f05019cc25a984e24873691&v=4
url: https://github.com/Synrom url: https://github.com/stan-dot
- login: gaby - login: Damon0603
count: 2 count: 2
avatarUrl: https://avatars.githubusercontent.com/u/835733?u=8c72dec16fa560bdc81113354f2ffd79ad062bde&v=4 avatarUrl: https://avatars.githubusercontent.com/u/110039208?u=f24bf5c30317bc4959118d1b919587c473a865b6&v=4
url: https://github.com/gaby url: https://github.com/Damon0603
- login: huynguyengl99
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/49433085?u=7b626115686c5d97a2a32a03119f5300e425cc9f&v=4
url: https://github.com/huynguyengl99
- login: Ale-Cas - login: Ale-Cas
count: 2 count: 2
avatarUrl: https://avatars.githubusercontent.com/u/64859146?u=d52a6ecf8d83d2927e2ae270bdfcc83495dba8c9&v=4 avatarUrl: https://avatars.githubusercontent.com/u/64859146?u=d52a6ecf8d83d2927e2ae270bdfcc83495dba8c9&v=4
url: https://github.com/Ale-Cas url: https://github.com/Ale-Cas
- login: CharlesPerrotMinotHCHB - login: tiborrr
count: 2 count: 2
avatarUrl: https://avatars.githubusercontent.com/u/112571330?u=e3a666718ff5ad1d1c49d6c31358a9f80c841b30&v=4 avatarUrl: https://avatars.githubusercontent.com/u/16014746?u=0ce47015e53009e90393582fe86b7b90e809bc28&v=4
url: https://github.com/CharlesPerrotMinotHCHB url: https://github.com/tiborrr
- login: yanggeorge - login: davidbrochart
count: 2 count: 2
avatarUrl: https://avatars.githubusercontent.com/u/2434407?v=4 avatarUrl: https://avatars.githubusercontent.com/u/4711805?u=d39696d995a9e02ec3613ffb2f62b20b14f92f26&v=4
url: https://github.com/yanggeorge url: https://github.com/davidbrochart
- login: Brikas - login: CharlieReitzel
count: 2 count: 2
avatarUrl: https://avatars.githubusercontent.com/u/80290187?v=4 avatarUrl: https://avatars.githubusercontent.com/u/20848272?v=4
url: https://github.com/Brikas url: https://github.com/CharlieReitzel
- login: dolfinus - login: kiranzo
count: 2 count: 2
avatarUrl: https://avatars.githubusercontent.com/u/4661021?u=ed5ddadcf36d9b943ebe61febe0b96ee34e5425d&v=4 avatarUrl: https://avatars.githubusercontent.com/u/1070878?u=68f78a891c9751dd87571ac712a6309090c4bc01&v=4
url: https://github.com/dolfinus url: https://github.com/kiranzo
- login: slafs - login: dotmitsu
count: 2 count: 2
avatarUrl: https://avatars.githubusercontent.com/u/210173?v=4 avatarUrl: https://avatars.githubusercontent.com/u/42657211?u=3bccc9a2f386a3f24230ec393080f8904fe2a5b2&v=4
url: https://github.com/slafs url: https://github.com/dotmitsu
- login: purepani - login: Brikas
count: 2 count: 2
avatarUrl: https://avatars.githubusercontent.com/u/7587353?v=4 avatarUrl: https://avatars.githubusercontent.com/u/80290187?u=2b72e497ca4444ecec1f9dc2d1b8d5437a27b83f&v=4
url: https://github.com/purepani url: https://github.com/Brikas
- login: ddahan - login: BloodyRain2k
count: 2 count: 2
avatarUrl: https://avatars.githubusercontent.com/u/1933516?u=1d200a620e8d6841df017e9f2bb7efb58b580f40&v=4 avatarUrl: https://avatars.githubusercontent.com/u/1014362?v=4
url: https://github.com/ddahan url: https://github.com/BloodyRain2k
- login: TaigoFr - login: usiqwerty
count: 2 count: 2
avatarUrl: https://avatars.githubusercontent.com/u/17792131?u=372b27056ec82f1ae03d8b3f37ef55b04a7cfdd1&v=4 avatarUrl: https://avatars.githubusercontent.com/u/37992525?u=0c6e91d7b3887aa558755f4225ce74a003cbe852&v=4
url: https://github.com/TaigoFr url: https://github.com/usiqwerty
- login: Garrett-R - login: garg-khushi
count: 2 count: 2
avatarUrl: https://avatars.githubusercontent.com/u/6614695?u=c128fd775002882f6e391bda5a89d1bdc5bdf45f&v=4 avatarUrl: https://avatars.githubusercontent.com/u/139839680?u=7faffa70275f8ab16f163e0c742a11d2662f9c66&v=4
url: https://github.com/Garrett-R url: https://github.com/garg-khushi
- login: jd-solanki - login: sk-
count: 2 count: 2
avatarUrl: https://avatars.githubusercontent.com/u/47495003?u=6e225cb42c688d0cd70e65c6baedb9f5922b1178&v=4 avatarUrl: https://avatars.githubusercontent.com/u/911768?u=3bfaf87089eb03ef0fa378f316b9c783f431aa9b&v=4
url: https://github.com/jd-solanki url: https://github.com/sk-
- login: EverStarck - login: skion
count: 2 count: 2
avatarUrl: https://avatars.githubusercontent.com/u/51029456?u=343409b7cb6b3ea6a59359f4e8370d9c3f140ecd&v=4 avatarUrl: https://avatars.githubusercontent.com/u/532192?v=4
url: https://github.com/EverStarck url: https://github.com/skion
- login: henrymcl - login: Danstiv
count: 2 count: 2
avatarUrl: https://avatars.githubusercontent.com/u/26480299?v=4 avatarUrl: https://avatars.githubusercontent.com/u/50794055?v=4
url: https://github.com/henrymcl url: https://github.com/Danstiv
- login: jymchng - login: florentx
count: 2 count: 2
avatarUrl: https://avatars.githubusercontent.com/u/27895426?u=fb88c47775147d62a395fdb895d1af4148c7b566&v=4 avatarUrl: https://avatars.githubusercontent.com/u/142113?u=bf10f10080026346b092633c380977b61cee0d9c&v=4
url: https://github.com/jymchng url: https://github.com/florentx
- login: christiansicari - login: jc-louis
count: 2 count: 2
avatarUrl: https://avatars.githubusercontent.com/u/29756552?v=4 avatarUrl: https://avatars.githubusercontent.com/u/51329768?v=4
url: https://github.com/christiansicari url: https://github.com/jc-louis
- login: JacobHayes - login: bughuntr7
count: 2 count: 2
avatarUrl: https://avatars.githubusercontent.com/u/2555532?u=354a525847a276bbb4426b0c95791a8ba5970f9b&v=4 avatarUrl: https://avatars.githubusercontent.com/u/236391583?u=7f51ff690e3a5711f845a115903c39e21c8af938&v=4
url: https://github.com/JacobHayes url: https://github.com/bughuntr7
- login: iloveitaly - login: purepani
count: 2 count: 2
avatarUrl: https://avatars.githubusercontent.com/u/150855?v=4 avatarUrl: https://avatars.githubusercontent.com/u/7587353?v=4
url: https://github.com/iloveitaly url: https://github.com/purepani
- login: iiotsrc - login: asmaier
count: 2 count: 2
avatarUrl: https://avatars.githubusercontent.com/u/131771119?u=bcaf2559ef6266af70b151b7fda31a1ee3dbecb3&v=4 avatarUrl: https://avatars.githubusercontent.com/u/3169297?v=4
url: https://github.com/iiotsrc url: https://github.com/asmaier
- login: PidgeyBE - login: henrymcl
count: 2 count: 2
avatarUrl: https://avatars.githubusercontent.com/u/19860056?u=47b584eb1c1ab45e31c1b474109a962d7e82be49&v=4 avatarUrl: https://avatars.githubusercontent.com/u/26480299?v=4
url: https://github.com/PidgeyBE url: https://github.com/henrymcl
- login: KianAnbarestani - login: potiuk
count: 2 count: 2
avatarUrl: https://avatars.githubusercontent.com/u/145364424?u=dcc3d8fb4ca07d36fb52a17f38b6650565de40be&v=4 avatarUrl: https://avatars.githubusercontent.com/u/595491?v=4
url: https://github.com/KianAnbarestani url: https://github.com/potiuk
- login: ykaiqx - login: EverStarck
count: 2 count: 2
avatarUrl: https://avatars.githubusercontent.com/u/56395004?u=1eebf5ce25a8067f7bfa6251a24f667be492d9d6&v=4 avatarUrl: https://avatars.githubusercontent.com/u/51029456?u=343409b7cb6b3ea6a59359f4e8370d9c3f140ecd&v=4
url: https://github.com/ykaiqx url: https://github.com/EverStarck
- login: AliYmn - login: sanderbollen-clockworks
count: 2 count: 2
avatarUrl: https://avatars.githubusercontent.com/u/18416653?u=a77e2605e3ce6aaf6fef8ad4a7b0d32954fba47a&v=4 avatarUrl: https://avatars.githubusercontent.com/u/183479560?v=4
url: https://github.com/AliYmn url: https://github.com/sanderbollen-clockworks
- login: gelezo43 - login: davidhuser
count: 2 count: 2
avatarUrl: https://avatars.githubusercontent.com/u/40732698?u=611f39d3c1d2f4207a590937a78c1f10eed6232c&v=4 avatarUrl: https://avatars.githubusercontent.com/u/4357648?u=6ed702f8f6d49a8b2a0ed33cbd8ab59c2d7db7f7&v=4
url: https://github.com/gelezo43 url: https://github.com/davidhuser
- login: jfeaver - login: XieJiSS
count: 2 count: 2
avatarUrl: https://avatars.githubusercontent.com/u/1091338?u=0bcba366447d8fadad63f6705a52d128da4c7ec2&v=4 avatarUrl: https://avatars.githubusercontent.com/u/24671280?u=7ea0d9bfe46cf762594d62fd2f3c6d3813c3584c&v=4
url: https://github.com/jfeaver url: https://github.com/XieJiSS

328
docs/en/data/topic_repos.yml

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

89
docs/en/data/translation_reviewers.yml

@ -31,7 +31,7 @@ hard-coders:
hasansezertasan: hasansezertasan:
login: hasansezertasan login: hasansezertasan
count: 95 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 url: https://github.com/hasansezertasan
alv2017: alv2017:
login: alv2017 login: alv2017
@ -43,21 +43,31 @@ nazarepiedady:
count: 87 count: 87
avatarUrl: https://avatars.githubusercontent.com/u/31008635?u=f69ddc4ea8bda3bdfac7aa0e2ea38de282e6ee2d&v=4 avatarUrl: https://avatars.githubusercontent.com/u/31008635?u=f69ddc4ea8bda3bdfac7aa0e2ea38de282e6ee2d&v=4
url: https://github.com/nazarepiedady 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: AlertRED:
login: AlertRED login: AlertRED
count: 81 count: 81
avatarUrl: https://avatars.githubusercontent.com/u/15695000?u=f5a4944c6df443030409c88da7d7fa0b7ead985c&v=4 avatarUrl: https://avatars.githubusercontent.com/u/15695000?u=f5a4944c6df443030409c88da7d7fa0b7ead985c&v=4
url: https://github.com/AlertRED 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: Alexandrhub:
login: Alexandrhub login: Alexandrhub
count: 68 count: 68
avatarUrl: https://avatars.githubusercontent.com/u/119126536?u=9fc0d48f3307817bafecc5861eb2168401a6cb04&v=4 avatarUrl: https://avatars.githubusercontent.com/u/119126536?u=9fc0d48f3307817bafecc5861eb2168401a6cb04&v=4
url: https://github.com/Alexandrhub 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: cassiobotaro:
login: cassiobotaro login: cassiobotaro
count: 64 count: 64
@ -68,21 +78,11 @@ waynerv:
count: 63 count: 63
avatarUrl: https://avatars.githubusercontent.com/u/39515546?u=ec35139777597cdbbbddda29bf8b9d4396b429a9&v=4 avatarUrl: https://avatars.githubusercontent.com/u/39515546?u=ec35139777597cdbbbddda29bf8b9d4396b429a9&v=4
url: https://github.com/waynerv 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: mattwang44:
login: mattwang44 login: mattwang44
count: 61 count: 61
avatarUrl: https://avatars.githubusercontent.com/u/24987826?u=58e37fb3927b9124b458945ac4c97aa0f1062d85&v=4 avatarUrl: https://avatars.githubusercontent.com/u/24987826?u=58e37fb3927b9124b458945ac4c97aa0f1062d85&v=4
url: https://github.com/mattwang44 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: Laineyzhang55:
login: Laineyzhang55 login: Laineyzhang55
count: 48 count: 48
@ -128,6 +128,11 @@ solomein-sv:
count: 38 count: 38
avatarUrl: https://avatars.githubusercontent.com/u/46193920?u=789927ee09cfabd752d3bd554fa6baf4850d2777&v=4 avatarUrl: https://avatars.githubusercontent.com/u/46193920?u=789927ee09cfabd752d3bd554fa6baf4850d2777&v=4
url: https://github.com/solomein-sv 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: JavierSanchezCastro:
login: JavierSanchezCastro login: JavierSanchezCastro
count: 38 count: 38
@ -138,11 +143,6 @@ alejsdev:
count: 37 count: 37
avatarUrl: https://avatars.githubusercontent.com/u/90076947?u=0facffe3abf87f57a1f05fa773d1119cc5c2f6a5&v=4 avatarUrl: https://avatars.githubusercontent.com/u/90076947?u=0facffe3abf87f57a1f05fa773d1119cc5c2f6a5&v=4
url: https://github.com/alejsdev 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: stlucasgarcia:
login: stlucasgarcia login: stlucasgarcia
count: 36 count: 36
@ -163,21 +163,21 @@ rjNemo:
count: 34 count: 34
avatarUrl: https://avatars.githubusercontent.com/u/56785022?u=d5c3a02567c8649e146fcfc51b6060ccaf8adef8&v=4 avatarUrl: https://avatars.githubusercontent.com/u/56785022?u=d5c3a02567c8649e146fcfc51b6060ccaf8adef8&v=4
url: https://github.com/rjNemo url: https://github.com/rjNemo
codingjenny: yychanlee:
login: codingjenny login: yychanlee
count: 34 count: 34
avatarUrl: https://avatars.githubusercontent.com/u/103817302?u=3a042740dc0ff58615da0d8679230966fd7693e8&v=4 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: akarev0:
login: akarev0 login: akarev0
count: 33 count: 33
avatarUrl: https://avatars.githubusercontent.com/u/53393089?u=6e528bb4789d56af887ce6fe237bea4010885406&v=4 avatarUrl: https://avatars.githubusercontent.com/u/53393089?u=6e528bb4789d56af887ce6fe237bea4010885406&v=4
url: https://github.com/akarev0 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: romashevchenko:
login: romashevchenko login: romashevchenko
count: 32 count: 32
@ -313,6 +313,11 @@ sattosan:
count: 19 count: 19
avatarUrl: https://avatars.githubusercontent.com/u/20574756?u=b0d8474d2938189c6954423ae8d81d91013f80a8&v=4 avatarUrl: https://avatars.githubusercontent.com/u/20574756?u=b0d8474d2938189c6954423ae8d81d91013f80a8&v=4
url: https://github.com/sattosan 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: yes0ng:
login: yes0ng login: yes0ng
count: 19 count: 19
@ -383,11 +388,6 @@ Joao-Pedro-P-Holanda:
count: 16 count: 16
avatarUrl: https://avatars.githubusercontent.com/u/110267046?u=331bd016326dac4cf3df4848f6db2dbbf8b5f978&v=4 avatarUrl: https://avatars.githubusercontent.com/u/110267046?u=331bd016326dac4cf3df4848f6db2dbbf8b5f978&v=4
url: https://github.com/Joao-Pedro-P-Holanda 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: JaeHyuckSa:
login: JaeHyuckSa login: JaeHyuckSa
count: 16 count: 16
@ -683,6 +683,11 @@ JoaoGustavoRogel:
count: 9 count: 9
avatarUrl: https://avatars.githubusercontent.com/u/29525510?u=a0a91251f5e43e132608d55d28ccb8645c5ea405&v=4 avatarUrl: https://avatars.githubusercontent.com/u/29525510?u=a0a91251f5e43e132608d55d28ccb8645c5ea405&v=4
url: https://github.com/JoaoGustavoRogel 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: Yarous:
login: Yarous login: Yarous
count: 9 count: 9
@ -738,6 +743,11 @@ sungchan1:
count: 8 count: 8
avatarUrl: https://avatars.githubusercontent.com/u/28076127?u=fadbf24840186aca639d344bb3e0ecf7ff3441cf&v=4 avatarUrl: https://avatars.githubusercontent.com/u/28076127?u=fadbf24840186aca639d344bb3e0ecf7ff3441cf&v=4
url: https://github.com/sungchan1 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: Serrones:
login: Serrones login: Serrones
count: 7 count: 7
@ -783,11 +793,6 @@ d2a-raudenaerde:
count: 7 count: 7
avatarUrl: https://avatars.githubusercontent.com/u/5213150?u=e6d0ef65c571c7e544fc1c7ec151c7c0a72fb6bb&v=4 avatarUrl: https://avatars.githubusercontent.com/u/5213150?u=e6d0ef65c571c7e544fc1c7ec151c7c0a72fb6bb&v=4
url: https://github.com/d2a-raudenaerde 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: Zerohertz:
login: Zerohertz login: Zerohertz
count: 7 count: 7
@ -1271,7 +1276,7 @@ rafsaf:
frnsimoes: frnsimoes:
login: frnsimoes login: frnsimoes
count: 3 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 url: https://github.com/frnsimoes
lieryan: lieryan:
login: lieryan login: lieryan
@ -1371,7 +1376,7 @@ nymous:
EpsilonRationes: EpsilonRationes:
login: EpsilonRationes login: EpsilonRationes
count: 3 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 url: https://github.com/EpsilonRationes
SametEmin: SametEmin:
login: SametEmin login: SametEmin
@ -1611,7 +1616,7 @@ raphaelauv:
Fahad-Md-Kamal: Fahad-Md-Kamal:
login: Fahad-Md-Kamal login: Fahad-Md-Kamal
count: 2 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 url: https://github.com/Fahad-Md-Kamal
zxcq544: zxcq544:
login: zxcq544 login: zxcq544

26
docs/en/data/translators.yml

@ -10,7 +10,7 @@ jaystone776:
url: https://github.com/jaystone776 url: https://github.com/jaystone776
tiangolo: tiangolo:
login: tiangolo login: tiangolo
count: 31 count: 46
avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4 avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4
url: https://github.com/tiangolo url: https://github.com/tiangolo
ceb10n: ceb10n:
@ -33,10 +33,15 @@ SwftAlpc:
count: 23 count: 23
avatarUrl: https://avatars.githubusercontent.com/u/52768429?u=6a3aa15277406520ad37f6236e89466ed44bc5b8&v=4 avatarUrl: https://avatars.githubusercontent.com/u/52768429?u=6a3aa15277406520ad37f6236e89466ed44bc5b8&v=4
url: https://github.com/SwftAlpc 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: hasansezertasan:
login: hasansezertasan login: hasansezertasan
count: 22 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 url: https://github.com/hasansezertasan
waynerv: waynerv:
login: waynerv login: waynerv
@ -58,11 +63,11 @@ Joao-Pedro-P-Holanda:
count: 14 count: 14
avatarUrl: https://avatars.githubusercontent.com/u/110267046?u=331bd016326dac4cf3df4848f6db2dbbf8b5f978&v=4 avatarUrl: https://avatars.githubusercontent.com/u/110267046?u=331bd016326dac4cf3df4848f6db2dbbf8b5f978&v=4
url: https://github.com/Joao-Pedro-P-Holanda url: https://github.com/Joao-Pedro-P-Holanda
codingjenny: yychanlee:
login: codingjenny login: yychanlee
count: 14 count: 14
avatarUrl: https://avatars.githubusercontent.com/u/103817302?u=3a042740dc0ff58615da0d8679230966fd7693e8&v=4 avatarUrl: https://avatars.githubusercontent.com/u/103817302?u=3a042740dc0ff58615da0d8679230966fd7693e8&v=4
url: https://github.com/codingjenny url: https://github.com/yychanlee
Xewus: Xewus:
login: Xewus login: Xewus
count: 13 count: 13
@ -108,11 +113,6 @@ pablocm83:
count: 8 count: 8
avatarUrl: https://avatars.githubusercontent.com/u/28315068?u=3310fbb05bb8bfc50d2c48b6cb64ac9ee4a14549&v=4 avatarUrl: https://avatars.githubusercontent.com/u/28315068?u=3310fbb05bb8bfc50d2c48b6cb64ac9ee4a14549&v=4
url: https://github.com/pablocm83 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: ptt3199:
login: ptt3199 login: ptt3199
count: 7 count: 7
@ -466,7 +466,7 @@ ArtemKhymenko:
hasnatsajid: hasnatsajid:
login: hasnatsajid login: hasnatsajid
count: 2 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 url: https://github.com/hasnatsajid
alperiox: alperiox:
login: alperiox login: alperiox
@ -481,7 +481,7 @@ emrhnsyts:
vusallyv: vusallyv:
login: vusallyv login: vusallyv
count: 2 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 url: https://github.com/vusallyv
jackleeio: jackleeio:
login: jackleeio login: jackleeio
@ -496,7 +496,7 @@ choi-haram:
imtiaz101325: imtiaz101325:
login: imtiaz101325 login: imtiaz101325
count: 2 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 url: https://github.com/imtiaz101325
fabianfalon: fabianfalon:
login: fabianfalon login: fabianfalon

2
docs/en/docs/_llm-test.md

@ -166,7 +166,7 @@ See sections `### Special blocks` and `### Tab blocks` in the general prompt in
//// tab | Test //// 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) * [Link to heading above](#code-snippets)
* [Internal link](index.md#installation){.internal-link target=_blank} * [Internal link](index.md#installation){.internal-link target=_blank}

104
docs/en/docs/advanced/custom-response.md

@ -1,6 +1,6 @@
# Custom Response - HTML, Stream, File, others { #custom-response-html-stream-file-others } # Custom Response - HTML, Stream, File, others { #custom-response-html-stream-file-others }
By default, **FastAPI** will return the responses using `JSONResponse`. By default, **FastAPI** will return JSON responses.
You can override it by returning a `Response` directly as seen in [Return a Response directly](response-directly.md){.internal-link target=_blank}. You can override it by returning a `Response` directly as seen in [Return a Response directly](response-directly.md){.internal-link target=_blank}.
@ -10,43 +10,27 @@ But you can also declare the `Response` that you want to be used (e.g. any `Resp
The contents that you return from your *path operation function* will be put inside of that `Response`. The contents that you return from your *path operation function* will be put inside of that `Response`.
And if that `Response` has a JSON media type (`application/json`), like is the case with the `JSONResponse` and `UJSONResponse`, the data you return will be automatically converted (and filtered) with any Pydantic `response_model` that you declared in the *path operation decorator*.
/// note /// note
If you use a response class with no media type, FastAPI will expect your response to have no content, so it will not document the response format in its generated OpenAPI docs. If you use a response class with no media type, FastAPI will expect your response to have no content, so it will not document the response format in its generated OpenAPI docs.
/// ///
## Use `ORJSONResponse` { #use-orjsonresponse } ## JSON Responses { #json-responses }
For example, if you are squeezing performance, you can install and use <a href="https://github.com/ijl/orjson" class="external-link" target="_blank">`orjson`</a> and set the response to be `ORJSONResponse`.
Import the `Response` class (sub-class) you want to use and declare it in the *path operation decorator*.
For large responses, returning a `Response` directly is much faster than returning a dictionary.
This is because by default, FastAPI will inspect every item inside and make sure it is serializable as JSON, using the same [JSON Compatible Encoder](../tutorial/encoder.md){.internal-link target=_blank} explained in the tutorial. This is what allows you to return **arbitrary objects**, for example database models.
But if you are certain that the content that you are returning is **serializable with JSON**, you can pass it directly to the response class and avoid the extra overhead that FastAPI would have by passing your return content through the `jsonable_encoder` before passing it to the response class.
{* ../../docs_src/custom_response/tutorial001b_py310.py hl[2,7] *} By default FastAPI returns JSON responses.
/// info If you declare a [Response Model](../tutorial/response-model.md){.internal-link target=_blank} FastAPI will use it to serialize the data to JSON, using Pydantic.
The parameter `response_class` will also be used to define the "media type" of the response.
In this case, the HTTP header `Content-Type` will be set to `application/json`. If you don't declare a response model, FastAPI will use the `jsonable_encoder` explained in [JSON Compatible Encoder](../tutorial/encoder.md){.internal-link target=_blank} and put it in a `JSONResponse`.
And it will be documented as such in OpenAPI. If you declare a `response_class` with a JSON media type (`application/json`), like is the case with the `JSONResponse`, the data you return will be automatically converted (and filtered) with any Pydantic `response_model` that you declared in the *path operation decorator*. But the data won't be serialized to JSON bytes with Pydantic, instead it will be converted with the `jsonable_encoder` and then passed to the `JSONResponse` class, which will serialize it to bytes using the standard JSON library in Python.
/// ### JSON Performance { #json-performance }
/// tip In short, if you want the maximum performance, use a [Response Model](../tutorial/response-model.md){.internal-link target=_blank} and don't declare a `response_class` in the *path operation decorator*.
The `ORJSONResponse` is only available in FastAPI, not in Starlette. {* ../../docs_src/response_model/tutorial001_01_py310.py ln[15:17] hl[16] *}
///
## HTML Response { #html-response } ## HTML Response { #html-response }
@ -154,37 +138,11 @@ Takes some data and returns an `application/json` encoded response.
This is the default response used in **FastAPI**, as you read above. This is the default response used in **FastAPI**, as you read above.
### `ORJSONResponse` { #orjsonresponse } /// note | Technical Details
A fast alternative JSON response using <a href="https://github.com/ijl/orjson" class="external-link" target="_blank">`orjson`</a>, as you read above.
/// info
This requires installing `orjson` for example with `pip install orjson`.
///
### `UJSONResponse` { #ujsonresponse }
An alternative JSON response using <a href="https://github.com/ultrajson/ultrajson" class="external-link" target="_blank">`ujson`</a>.
/// info
This requires installing `ujson` for example with `pip install ujson`.
///
/// warning
`ujson` is less careful than Python's built-in implementation in how it handles some edge-cases.
///
{* ../../docs_src/custom_response/tutorial001_py310.py hl[2,7] *}
/// tip But if you declare a response model or return type, that will be used directly to serialize the data to JSON, and a response with the right media type for JSON will be returned directly, without using the `JSONResponse` class.
It's possible that `ORJSONResponse` might be a faster alternative. This is the ideal way to get the best performance.
/// ///
@ -215,31 +173,25 @@ You can also use the `status_code` parameter combined with the `response_class`
### `StreamingResponse` { #streamingresponse } ### `StreamingResponse` { #streamingresponse }
Takes an async generator or a normal generator/iterator and streams the response body. Takes an async generator or a normal generator/iterator (a function with `yield`) and streams the response body.
{* ../../docs_src/custom_response/tutorial007_py310.py hl[2,14] *}
#### Using `StreamingResponse` with file-like objects { #using-streamingresponse-with-file-like-objects }
If you have a <a href="https://docs.python.org/3/glossary.html#term-file-like-object" class="external-link" target="_blank">file-like</a> object (e.g. the object returned by `open()`), you can create a generator function to iterate over that file-like object. {* ../../docs_src/custom_response/tutorial007_py310.py hl[3,16] *}
That way, you don't have to read it all first in memory, and you can pass that generator function to the `StreamingResponse`, and return it. /// note | Technical Details
This includes many libraries to interact with cloud storage, video processing, and others.
{* ../../docs_src/custom_response/tutorial008_py310.py hl[2,10:12,14] *} An `async` task can only be cancelled when it reaches an `await`. If there is no `await`, the generator (function with `yield`) can not be cancelled properly and may keep running even after cancellation is requested.
1. This is the generator function. It's a "generator function" because it contains `yield` statements inside. Since this small example does not need any `await` statements, we add an `await anyio.sleep(0)` to give the event loop a chance to handle cancellation.
2. By using a `with` block, we make sure that the file-like object is closed after the generator function is done. So, after it finishes sending the response.
3. This `yield from` tells the function to iterate over that thing named `file_like`. And then, for each part iterated, yield that part as coming from this generator function (`iterfile`).
So, it is a generator function that transfers the "generating" work to something else internally. This would be even more important with large or infinite streams.
By doing it this way, we can put it in a `with` block, and that way, ensure that the file-like object is closed after finishing. ///
/// tip /// tip
Notice that here as we are using standard `open()` that doesn't support `async` and `await`, we declare the path operation with normal `def`. Instead of returning a `StreamingResponse` directly, you should probably follow the style in [Stream Data](./stream-data.md){.internal-link target=_blank}, it's much more convenient and handles cancellation behind the scenes for you.
If you are streaming JSON Lines, follow the [Stream JSON Lines](../tutorial/stream-json-lines.md){.internal-link target=_blank} tutorial.
/// ///
@ -268,7 +220,7 @@ In this case, you can return the file path directly from your *path operation* f
You can create your own custom response class, inheriting from `Response` and using it. You can create your own custom response class, inheriting from `Response` and using it.
For example, let's say that you want to use <a href="https://github.com/ijl/orjson" class="external-link" target="_blank">`orjson`</a>, but with some custom settings not used in the included `ORJSONResponse` class. For example, let's say that you want to use <a href="https://github.com/ijl/orjson" class="external-link" target="_blank">`orjson`</a> with some settings.
Let's say you want it to return indented and formatted JSON, so you want to use the orjson option `orjson.OPT_INDENT_2`. Let's say you want it to return indented and formatted JSON, so you want to use the orjson option `orjson.OPT_INDENT_2`.
@ -292,13 +244,21 @@ Now instead of returning:
Of course, you will probably find much better ways to take advantage of this than formatting JSON. 😉 Of course, you will probably find much better ways to take advantage of this than formatting JSON. 😉
### `orjson` or Response Model { #orjson-or-response-model }
If what you are looking for is performance, you are probably better off using a [Response Model](../tutorial/response-model.md){.internal-link target=_blank} than an `orjson` response.
With a response model, FastAPI will use Pydantic to serialize the data to JSON, without using intermediate steps, like converting it with `jsonable_encoder`, which would happen in any other case.
And under the hood, Pydantic uses the same underlying Rust mechanisms as `orjson` to serialize to JSON, so you will already get the best performance with a response model.
## Default response class { #default-response-class } ## Default response class { #default-response-class }
When creating a **FastAPI** class instance or an `APIRouter` you can specify which response class to use by default. When creating a **FastAPI** class instance or an `APIRouter` you can specify which response class to use by default.
The parameter that defines this is `default_response_class`. The parameter that defines this is `default_response_class`.
In the example below, **FastAPI** will use `ORJSONResponse` by default, in all *path operations*, instead of `JSONResponse`. In the example below, **FastAPI** will use `HTMLResponse` by default, in all *path operations*, instead of JSON.
{* ../../docs_src/custom_response/tutorial010_py310.py hl[2,4] *} {* ../../docs_src/custom_response/tutorial010_py310.py hl[2,4] *}

63
docs/en/docs/advanced/json-base64-bytes.md

@ -0,0 +1,63 @@
# JSON with Bytes as Base64 { #json-with-bytes-as-base64 }
If your app needs to receive and send JSON data, but you need to include binary data in it, you can encode it as base64.
## Base64 vs Files { #base64-vs-files }
Consider first if you can use [Request Files](../tutorial/request-files.md){.internal-link target=_blank} for uploading binary data and [Custom Response - FileResponse](./custom-response.md#fileresponse--fileresponse-){.internal-link target=_blank} for sending binary data, instead of encoding it in JSON.
JSON can only contain UTF-8 encoded strings, so it can't contain raw bytes.
Base64 can encode binary data in strings, but to do it, it needs to use more characters than the original binary data, so it would normally be less efficient than regular files.
Use base64 only if you definitely need to include binary data in JSON, and you can't use files for that.
## Pydantic `bytes` { #pydantic-bytes }
You can declare a Pydantic model with `bytes` fields, and then use `val_json_bytes` in the model config to tell it to use base64 to *validate* input JSON data, as part of that validation it will decode the base64 string into bytes.
{* ../../docs_src/json_base64_bytes/tutorial001_py310.py ln[1:9,29:35] hl[9] *}
If you check the `/docs`, they will show that the field `data` expects base64 encoded bytes:
<div class="screenshot">
<img src="/img/tutorial/json-base64-bytes/image01.png">
</div>
You could send a request like:
```json
{
"description": "Some data",
"data": "aGVsbG8="
}
```
/// tip
`aGVsbG8=` is the base64 encoding of `hello`.
///
And then Pydantic will decode the base64 string and give you the original bytes in the `data` field of the model.
You will receive a response like:
```json
{
"description": "Some data",
"content": "hello"
}
```
## Pydantic `bytes` for Output Data { #pydantic-bytes-for-output-data }
You can also use `bytes` fields with `ser_json_bytes` in the model config for output data, and Pydantic will *serialize* the bytes as base64 when generating the JSON response.
{* ../../docs_src/json_base64_bytes/tutorial001_py310.py ln[1:2,12:16,29,38:41] hl[16] *}
## Pydantic `bytes` for Input and Output Data { #pydantic-bytes-for-input-and-output-data }
And of course, you can use the same model configured to use base64 to handle both input (*validate*) with `val_json_bytes` and output (*serialize*) with `ser_json_bytes` when receiving and sending JSON data.
{* ../../docs_src/json_base64_bytes/tutorial001_py310.py ln[1:2,19:26,29,44:46] hl[23:26] *}

32
docs/en/docs/advanced/response-directly.md

@ -2,19 +2,23 @@
When you create a **FastAPI** *path operation* you can normally return any data from it: a `dict`, a `list`, a Pydantic model, a database model, etc. When you create a **FastAPI** *path operation* you can normally return any data from it: a `dict`, a `list`, a Pydantic model, a database model, etc.
By default, **FastAPI** would automatically convert that return value to JSON using the `jsonable_encoder` explained in [JSON Compatible Encoder](../tutorial/encoder.md){.internal-link target=_blank}. If you declare a [Response Model](../tutorial/response-model.md){.internal-link target=_blank} FastAPI will use it to serialize the data to JSON, using Pydantic.
Then, behind the scenes, it would put that JSON-compatible data (e.g. a `dict`) inside of a `JSONResponse` that would be used to send the response to the client. If you don't declare a response model, FastAPI will use the `jsonable_encoder` explained in [JSON Compatible Encoder](../tutorial/encoder.md){.internal-link target=_blank} and put it in a `JSONResponse`.
But you can return a `JSONResponse` directly from your *path operations*. You could also create a `JSONResponse` directly and return it.
It might be useful, for example, to return custom headers or cookies. /// tip
You will normally have much better performance using a [Response Model](../tutorial/response-model.md){.internal-link target=_blank} than returning a `JSONResponse` directly, as that way it serializes the data using Pydantic, in Rust.
///
## Return a `Response` { #return-a-response } ## Return a `Response` { #return-a-response }
In fact, you can return any `Response` or any sub-class of it. You can return a `Response` or any sub-class of it.
/// tip /// info
`JSONResponse` itself is a sub-class of `Response`. `JSONResponse` itself is a sub-class of `Response`.
@ -24,7 +28,9 @@ And when you return a `Response`, **FastAPI** will pass it directly.
It won't do any data conversion with Pydantic models, it won't convert the contents to any type, etc. It won't do any data conversion with Pydantic models, it won't convert the contents to any type, etc.
This gives you a lot of flexibility. You can return any data type, override any data declaration or validation, etc. This gives you a lot of **flexibility**. You can return any data type, override any data declaration or validation, etc.
It also gives you a lot of **responsibility**. You have to make sure that the data you return is correct, in the correct format, that it can be serialized, etc.
## Using the `jsonable_encoder` in a `Response` { #using-the-jsonable-encoder-in-a-response } ## Using the `jsonable_encoder` in a `Response` { #using-the-jsonable-encoder-in-a-response }
@ -56,6 +62,18 @@ You could put your XML content in a string, put that in a `Response`, and return
{* ../../docs_src/response_directly/tutorial002_py310.py hl[1,18] *} {* ../../docs_src/response_directly/tutorial002_py310.py hl[1,18] *}
## How a Response Model Works { #how-a-response-model-works }
When you declare a [Response Model - Return Type](../tutorial/response-model.md){.internal-link target=_blank} in a path operation, **FastAPI** will use it to serialize the data to JSON, using Pydantic.
{* ../../docs_src/response_model/tutorial001_01_py310.py hl[16,21] *}
As that will happen on the Rust side, the performance will be much better than if it was done with regular Python and the `JSONResponse` class.
When using a `response_model` or return type, FastAPI won't use the `jsonable_encoder` to convert the data (which would be slower) nor the `JSONResponse` class.
Instead it takes the JSON bytes generated with Pydantic using the response model (or return type) and returns a `Response` with the right media type for JSON directly (`application/json`).
## Notes { #notes } ## Notes { #notes }
When you return a `Response` directly its data is not validated, converted (serialized), or documented automatically. When you return a `Response` directly its data is not validated, converted (serialized), or documented automatically.

117
docs/en/docs/advanced/stream-data.md

@ -0,0 +1,117 @@
# Stream Data { #stream-data }
If you want to stream data that can be structured as JSON, you should [Stream JSON Lines](../tutorial/stream-json-lines.md){.internal-link target=_blank}.
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.
You could also use it to stream **large binary files**, where you stream each chunk of data as you read it, without having to read it all in memory at once.
You could also stream **video** or **audio** this way, it could even be generated as you process and send it.
## A `StreamingResponse` with `yield` { #a-streamingresponse-with-yield }
If you declare a `response_class=StreamingResponse` in your *path operation function*, you can use `yield` to send each chunk of data in turn.
{* ../../docs_src/stream_data/tutorial001_py310.py ln[1:23] hl[20,23] *}
FastAPI will give each chunk of data to the `StreamingResponse` as is, it won't try to convert it to JSON or anything similar.
### 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.
{* ../../docs_src/stream_data/tutorial001_py310.py ln[26:29] hl[27] *}
### No Annotation { #no-annotation }
You don't really need to declare the return type annotation for streaming binary data.
As FastAPI will not try to convert the data to JSON with Pydantic or serialize it in any way, in this case, the type annotation is only for your editor and tools to use, it won't be used by FastAPI.
{* ../../docs_src/stream_data/tutorial001_py310.py ln[32:35] hl[33] *}
This also means that with `StreamingResponse` you have the **freedom** and **responsibility** to produce and encode the data bytes exactly as you need them to be sent, independent of the type annotations. 🤓
### Stream Bytes { #stream-bytes }
One of the main use cases would be to stream `bytes` instead of strings, you can of course do it.
{* ../../docs_src/stream_data/tutorial001_py310.py ln[44:47] hl[47] *}
## A Custom `PNGStreamingResponse` { #a-custom-pngstreamingresponse }
In the examples above, the data bytes were streamed, but the response didn't have a `Content-Type` header, so the client didn't know what type of data it was receiving.
You can create a custom sub-class of `StreamingResponse` that sets the `Content-Type` header to the type of data you're streaming.
For example, you can create a `PNGStreamingResponse` that sets the `Content-Type` header to `image/png` using the `media_type` attribute:
{* ../../docs_src/stream_data/tutorial002_py310.py ln[6,19:20] hl[20] *}
Then you can use this new class in `response_class=PNGStreamingResponse` in your *path operation function*:
{* ../../docs_src/stream_data/tutorial002_py310.py ln[23:27] hl[23] *}
### Simulate a File { #simulate-a-file }
In this example, we are simulating a file with `io.BytesIO`, which is a file-like object that lives only in memory, but lets us use the same interface.
For example, we can iterate over it to consume its contents, as we could with a file.
{* ../../docs_src/stream_data/tutorial002_py310.py ln[1:27] hl[3,12:13,25] *}
/// note | Technical Details
The other two variables, `image_base64` and `binary_image`, are an image encoded in Base64, and then converted to bytes, to then pass it to `io.BytesIO`.
Only so that it can live in the same file for this example and you can copy it and run it as is. 🥚
///
By using a `with` block, we make sure that the file-like object is closed after the generator function (the function with `yield`) is done. So, after it finishes sending the response.
It wouldn't be that important in this specific example because it's a fake in-memory file (with `io.BytesIO`), but with a real file, it would be important to make sure the file is closed after the work with it is done.
### Files and Async { #files-and-async }
In most cases, file-like objects are not compatible with async and await by default.
For example, they don't have an `await file.read()`, or `async for chunk in file`.
And in many cases, reading them would be a blocking operation (that could block the event loop), because they are read from disk or from the network.
/// info
The example above is actually an exception, because the `io.BytesIO` object is already in memory, so reading it won't block anything.
But in many cases reading a file or a file-like object would block.
///
To avoid blocking the event loop, you can simply declare the *path operation function* with regular `def` instead of `async def`, that way FastAPI will run it on a threadpool worker, to avoid blocking the main loop.
{* ../../docs_src/stream_data/tutorial002_py310.py ln[30:34] hl[31] *}
/// tip
If you need to call blocking code from inside of an async function, or an async function from inside of a blocking function, you could use <a href="https://asyncer.tiangolo.com" class="external-link" target="_blank">Asyncer</a>, a sibling library to FastAPI.
///
### `yield from` { #yield-from }
When you are iterating over something, like a file-like object, and then you are doing `yield` for each item, you could also use `yield from` to yield each item directly and skip the `for` loop.
This is not particular to FastAPI, it's just Python, but it's a nice trick to know. 😎
{* ../../docs_src/stream_data/tutorial002_py310.py ln[37:40] hl[40] *}

88
docs/en/docs/advanced/strict-content-type.md

@ -0,0 +1,88 @@
# Strict Content-Type Checking { #strict-content-type-checking }
By default, **FastAPI** uses strict `Content-Type` header checking for JSON request bodies, this means that JSON requests **must** include a valid `Content-Type` header (e.g. `application/json`) in order for the body to be parsed as JSON.
## CSRF Risk { #csrf-risk }
This default behavior provides protection against a class of **Cross-Site Request Forgery (CSRF)** attacks in a very specific scenario.
These attacks exploit the fact that browsers allow scripts to send requests without doing any CORS preflight check when they:
* don't have a `Content-Type` header (e.g. using `fetch()` with a `Blob` body)
* and don't send any authentication credentials.
This type of attack is mainly relevant when:
* the application is running locally (e.g. on `localhost`) or in an internal network
* and the application doesn't have any authentication, it expects that any request from the same network can be trusted.
## Example Attack { #example-attack }
Imagine you build a way to run a local AI agent.
It provides an API at
```
http://localhost:8000/v1/agents/multivac
```
There's also a frontend at
```
http://localhost:8000
```
/// tip
Note that both have the same host.
///
Then using the frontend you can make the AI agent do things on your behalf.
As it's running **locally**, and not in the open internet, you decide to **not have any authentication** set up, just trusting the access to the local network.
Then one of your users could install it and run it locally.
Then they could open a malicious website, e.g. something like
```
https://evilhackers.example.com
```
And that malicious website sends requests using `fetch()` with a `Blob` body to the local API at
```
http://localhost:8000/v1/agents/multivac
```
Even though the host of the malicious website and the local app is different, the browser won't trigger a CORS preflight request because:
* It's running without any authentication, it doesn't have to send any credentials.
* The browser thinks it's not sending JSON (because of the missing `Content-Type` header).
Then the malicious website could make the local AI agent send angry messages to the user's ex-boss... or worse. 😅
## Open Internet { #open-internet }
If your app is in the open internet, you wouldn't "trust the network" and let anyone send privileged requests without authentication.
Attackers could simply run a script to send requests to your API, no need for browser interaction, so you are probably already securing any privileged endpoints.
In that case **this attack / risk doesn't apply to you**.
This risk and attack is mainly relevant when the app runs on the **local network** and that is the **only assumed protection**.
## Allowing Requests Without Content-Type { #allowing-requests-without-content-type }
If you need to support clients that don't send a `Content-Type` header, you can disable strict checking by setting `strict_content_type=False`:
{* ../../docs_src/strict_content_type/tutorial001_py310.py hl[4] *}
With this setting, requests without a `Content-Type` header will have their body parsed as JSON, which is the same behavior as older versions of FastAPI.
/// info
This behavior and configuration was added in FastAPI 0.132.0.
///

10
docs/en/docs/advanced/websockets.md

@ -38,13 +38,13 @@ In production you would have one of the options above.
But it's the simplest way to focus on the server-side of WebSockets and have a working example: But it's the simplest way to focus on the server-side of WebSockets and have a working example:
{* ../../docs_src/websockets/tutorial001_py310.py hl[2,6:38,41:43] *} {* ../../docs_src/websockets_/tutorial001_py310.py hl[2,6:38,41:43] *}
## Create a `websocket` { #create-a-websocket } ## Create a `websocket` { #create-a-websocket }
In your **FastAPI** application, create a `websocket`: In your **FastAPI** application, create a `websocket`:
{* ../../docs_src/websockets/tutorial001_py310.py hl[1,46:47] *} {* ../../docs_src/websockets_/tutorial001_py310.py hl[1,46:47] *}
/// note | Technical Details /// note | Technical Details
@ -58,7 +58,7 @@ You could also use `from starlette.websockets import WebSocket`.
In your WebSocket route you can `await` for messages and send messages. In your WebSocket route you can `await` for messages and send messages.
{* ../../docs_src/websockets/tutorial001_py310.py hl[48:52] *} {* ../../docs_src/websockets_/tutorial001_py310.py hl[48:52] *}
You can receive and send binary, text, and JSON data. You can receive and send binary, text, and JSON data.
@ -109,7 +109,7 @@ In WebSocket endpoints you can import from `fastapi` and use:
They work the same way as for other FastAPI endpoints/*path operations*: They work the same way as for other FastAPI endpoints/*path operations*:
{* ../../docs_src/websockets/tutorial002_an_py310.py hl[68:69,82] *} {* ../../docs_src/websockets_/tutorial002_an_py310.py hl[68:69,82] *}
/// info /// info
@ -154,7 +154,7 @@ With that you can connect the WebSocket and then send and receive messages:
When a WebSocket connection is closed, the `await websocket.receive_text()` will raise a `WebSocketDisconnect` exception, which you can then catch and handle like in this example. When a WebSocket connection is closed, the `await websocket.receive_text()` will raise a `WebSocketDisconnect` exception, which you can then catch and handle like in this example.
{* ../../docs_src/websockets/tutorial003_py310.py hl[79:81] *} {* ../../docs_src/websockets_/tutorial003_py310.py hl[79:81] *}
To try it out: To try it out:

4
docs/en/docs/css/custom.css

@ -61,6 +61,10 @@ a.internal-link::after {
padding-bottom: 2em; padding-bottom: 2em;
} }
.md-footer-meta .md-social {
padding-right: 4rem;
}
.user-list { .user-list {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;

4
docs/en/docs/how-to/general.md

@ -6,6 +6,10 @@ Here are several pointers to other places in the docs, for general or frequent q
To ensure that you don't return more data than you should, read the docs for [Tutorial - Response Model - Return Type](../tutorial/response-model.md){.internal-link target=_blank}. To ensure that you don't return more data than you should, read the docs for [Tutorial - Response Model - Return Type](../tutorial/response-model.md){.internal-link target=_blank}.
## Optimize Response Performance - Response Model - Return Type { #optimize-response-performance-response-model-return-type }
To optimize performance when returning JSON data, use a return type or response model, that way Pydantic will handle the serialization to JSON on the Rust side, without going through Python. Read more in the docs for [Tutorial - Response Model - Return Type](../tutorial/response-model.md){.internal-link target=_blank}.
## Documentation Tags - OpenAPI { #documentation-tags-openapi } ## Documentation Tags - OpenAPI { #documentation-tags-openapi }
To add tags to your *path operations*, and group them in the docs UI, read the docs for [Tutorial - Path Operation Configurations - Tags](../tutorial/path-operation-configuration.md#tags){.internal-link target=_blank}. To add tags to your *path operations*, and group them in the docs UI, read the docs for [Tutorial - Path Operation Configurations - Tags](../tutorial/path-operation-configuration.md#tags){.internal-link target=_blank}.

BIN
docs/en/docs/img/tutorial/json-base64-bytes/image01.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

29
docs/en/docs/js/init_kapa_widget.js

@ -0,0 +1,29 @@
document.addEventListener("DOMContentLoaded", function () {
var script = document.createElement("script");
script.src = "https://widget.kapa.ai/kapa-widget.bundle.js";
script.setAttribute("data-website-id", "91f47f27-b405-4299-bf5f-a1c0ec07b3cc");
script.setAttribute("data-project-name", "FastAPI");
script.setAttribute("data-project-color", "#009485");
script.setAttribute("data-project-logo", "https://fastapi.tiangolo.com/img/favicon.png");
script.setAttribute("data-bot-protection-mechanism", "hcaptcha");
script.setAttribute("data-button-height", "3rem");
script.setAttribute("data-button-width", "3rem");
script.setAttribute("data-button-border-radius", "50%");
script.setAttribute("data-button-padding", "0");
script.setAttribute("data-button-image", "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 8V4H8'/%3E%3Crect width='16' height='12' x='4' y='8' rx='2'/%3E%3Cpath d='M2 14h2'/%3E%3Cpath d='M20 14h2'/%3E%3Cpath d='M15 13v2'/%3E%3Cpath d='M9 13v2'/%3E%3C/svg%3E");
script.setAttribute("data-button-image-height", "20px");
script.setAttribute("data-button-image-width", "20px");
script.setAttribute("data-button-text", "Ask AI");
script.setAttribute("data-button-text-font-size", "0.5rem");
script.setAttribute("data-button-text-font-family", "Roboto, sans-serif");
script.setAttribute("data-button-text-color", "#FFFFFF");
script.setAttribute("data-modal-border-radius", "0.5rem");
script.setAttribute("data-modal-header-bg-color", "#009485");
script.setAttribute("data-modal-title", "FastAPI AI Assistant");
script.setAttribute("data-modal-title-color", "#FFFFFF");
script.setAttribute("data-modal-title-font-family", "Roboto, sans-serif");
script.setAttribute("data-modal-example-questions", "How to define a route?,How to validate models?,How to handle responses?,How to deploy FastAPI?");
script.setAttribute("data-modal-disclaimer", "AI-generated answers based on FastAPI [documentation](https://fastapi.tiangolo.com/) and [community discussions](https://github.com/fastapi/fastapi/discussions). Always verify important information.");
script.async = true;
document.head.appendChild(script);
});

8
docs/en/docs/reference/responses.md

@ -22,7 +22,13 @@ from fastapi.responses import (
## FastAPI Responses ## FastAPI Responses
There are a couple of custom FastAPI response classes, you can use them to optimize JSON performance. There were a couple of custom FastAPI response classes that were intended to optimize JSON performance.
However, they are now deprecated as you will now get better performance by using a [Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/).
That way, Pydantic will serialize the data into JSON bytes on the Rust side, which will achieve better performance than these custom JSON responses.
Read more about it in [Custom Response - HTML, Stream, File, others - `orjson` or Response Model](https://fastapi.tiangolo.com/advanced/custom-response/#orjson-or-response-model).
::: fastapi.responses.UJSONResponse ::: fastapi.responses.UJSONResponse
options: options:

130
docs/en/docs/release-notes.md

@ -7,13 +7,142 @@ hide:
## Latest Changes ## Latest Changes
### Internal
* ⬆ Bump pydantic-ai from 1.62.0 to 1.63.0. PR [#15035](https://github.com/fastapi/fastapi/pull/15035) by [@dependabot[bot]](https://github.com/apps/dependabot).
* ⬆ Bump pytest-codspeed from 4.2.0 to 4.3.0. PR [#15034](https://github.com/fastapi/fastapi/pull/15034) by [@dependabot[bot]](https://github.com/apps/dependabot).
* ⬆ Bump strawberry-graphql from 0.291.2 to 0.307.1. PR [#15033](https://github.com/fastapi/fastapi/pull/15033) by [@dependabot[bot]](https://github.com/apps/dependabot).
* ⬆ Bump typer from 0.21.1 to 0.24.1. PR [#15032](https://github.com/fastapi/fastapi/pull/15032) by [@dependabot[bot]](https://github.com/apps/dependabot).
* ⬆ Bump actions/download-artifact from 7 to 8. PR [#15020](https://github.com/fastapi/fastapi/pull/15020) by [@dependabot[bot]](https://github.com/apps/dependabot).
* ⬆ Bump actions/upload-artifact from 6 to 7. PR [#15019](https://github.com/fastapi/fastapi/pull/15019) by [@dependabot[bot]](https://github.com/apps/dependabot).
## 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
* ✨ Add support for streaming JSON Lines and binary data with `yield`. PR [#15022](https://github.com/fastapi/fastapi/pull/15022) by [@tiangolo](https://github.com/tiangolo).
* This also upgrades Starlette from `>=0.40.0` to `>=0.46.0`, as it's needed to properly unrwap and re-raise exceptions from exception groups.
* New docs: [Stream JSON Lines](https://fastapi.tiangolo.com/tutorial/stream-json-lines/).
* And new docs: [Stream Data](https://fastapi.tiangolo.com/advanced/stream-data/).
### Docs
* 📝 Update Library Agent Skill with streaming responses. PR [#15024](https://github.com/fastapi/fastapi/pull/15024) by [@tiangolo](https://github.com/tiangolo).
* 📝 Update docs for responses and new stream with `yield`. PR [#15023](https://github.com/fastapi/fastapi/pull/15023) by [@tiangolo](https://github.com/tiangolo).
* 📝 Add `await` in `StreamingResponse` code example to allow cancellation. PR [#14681](https://github.com/fastapi/fastapi/pull/14681) by [@casperdcl](https://github.com/casperdcl).
* 📝 Rename `docs_src/websockets` to `docs_src/websockets_` to avoid import errors. PR [#14979](https://github.com/fastapi/fastapi/pull/14979) by [@YuriiMotov](https://github.com/YuriiMotov).
### Internal
* 🔨 Run tests with `pytest-xdist` and `pytest-cov`. PR [#14992](https://github.com/fastapi/fastapi/pull/14992) by [@YuriiMotov](https://github.com/YuriiMotov).
## 0.133.1
### Features
* 🔧 Add FastAPI Agents Skill. PR [#14982](https://github.com/fastapi/fastapi/pull/14982) by [@tiangolo](https://github.com/tiangolo).
* Read more about it in [Library Agent Skills](https://tiangolo.com/ideas/library-agent-skills/).
### Internal
* ✅ Fix all tests are skipped on Windows. PR [#14994](https://github.com/fastapi/fastapi/pull/14994) by [@YuriiMotov](https://github.com/YuriiMotov).
## 0.133.0
### Upgrades
* ⬆️ Add support for Starlette 1.0.0+. PR [#14987](https://github.com/fastapi/fastapi/pull/14987) by [@tiangolo](https://github.com/tiangolo).
## 0.132.1
### Refactors
* ♻️ Refactor logic to handle OpenAPI and Swagger UI escaping data. PR [#14986](https://github.com/fastapi/fastapi/pull/14986) by [@tiangolo](https://github.com/tiangolo).
### Internal
* 👥 Update FastAPI People - Experts. PR [#14972](https://github.com/fastapi/fastapi/pull/14972) by [@tiangolo](https://github.com/tiangolo).
* 👷 Allow skipping `benchmark` job in `test` workflow. PR [#14974](https://github.com/fastapi/fastapi/pull/14974) by [@YuriiMotov](https://github.com/YuriiMotov).
## 0.132.0
### Breaking Changes
* 🔒️ Add `strict_content_type` checking for JSON requests. PR [#14978](https://github.com/fastapi/fastapi/pull/14978) by [@tiangolo](https://github.com/tiangolo).
* Now FastAPI checks, by default, that JSON requests have a `Content-Type` header with a valid JSON value, like `application/json`, and rejects requests that don't.
* If the clients for your app don't send a valid `Content-Type` header you can disable this with `strict_content_type=False`.
* Check the new docs: [Strict Content-Type Checking](https://fastapi.tiangolo.com/advanced/strict-content-type/).
### Internal
* ⬆ Bump flask from 3.1.2 to 3.1.3. PR [#14949](https://github.com/fastapi/fastapi/pull/14949) by [@dependabot[bot]](https://github.com/apps/dependabot).
* ⬆ Update all dependencies to use `griffelib` instead of `griffe`. PR [#14973](https://github.com/fastapi/fastapi/pull/14973) by [@svlandeg](https://github.com/svlandeg).
* 🔨 Fix `FastAPI People` workflow. PR [#14951](https://github.com/fastapi/fastapi/pull/14951) by [@YuriiMotov](https://github.com/YuriiMotov).
* 👷 Do not run codspeed with coverage as it's not tracked. PR [#14966](https://github.com/fastapi/fastapi/pull/14966) by [@tiangolo](https://github.com/tiangolo).
* 👷 Do not include benchmark tests in coverage to speed up coverage processing. PR [#14965](https://github.com/fastapi/fastapi/pull/14965) by [@tiangolo](https://github.com/tiangolo).
## 0.131.0
### Breaking Changes
* 🗑️ Deprecate `ORJSONResponse` and `UJSONResponse`. PR [#14964](https://github.com/fastapi/fastapi/pull/14964) by [@tiangolo](https://github.com/tiangolo).
## 0.130.0
### Features
* ✨ Serialize JSON response with Pydantic (in Rust), when there's a Pydantic return type or response model. PR [#14962](https://github.com/fastapi/fastapi/pull/14962) by [@tiangolo](https://github.com/tiangolo).
* This results in 2x (or more) performance increase for JSON responses.
* New docs: [Custom Response - JSON Performance](https://fastapi.tiangolo.com/advanced/custom-response/#json-performance).
## 0.129.2
### Internal
* ⬆️ Upgrade pytest. PR [#14959](https://github.com/fastapi/fastapi/pull/14959) by [@tiangolo](https://github.com/tiangolo).
* 👷 Fix CI, do not attempt to publish `fastapi-slim`. PR [#14958](https://github.com/fastapi/fastapi/pull/14958) by [@tiangolo](https://github.com/tiangolo).
* ➖ Drop support for `fastapi-slim`, no more versions will be released, use only `"fastapi[standard]"` or `fastapi`. PR [#14957](https://github.com/fastapi/fastapi/pull/14957) by [@tiangolo](https://github.com/tiangolo).
* 🔧 Update pyproject.toml, remove unneeded lines. PR [#14956](https://github.com/fastapi/fastapi/pull/14956) by [@tiangolo](https://github.com/tiangolo).
## 0.129.1
### Fixes
* ♻️ Fix JSON Schema for bytes, use `"contentMediaType": "application/octet-stream"` instead of `"format": "binary"`. PR [#14953](https://github.com/fastapi/fastapi/pull/14953) by [@tiangolo](https://github.com/tiangolo).
### Docs ### Docs
* 🔨 Add Kapa.ai widget (AI chatbot). PR [#14938](https://github.com/fastapi/fastapi/pull/14938) by [@tiangolo](https://github.com/tiangolo).
* 🔥 Remove Python 3.9 specific files, no longer needed after updating translations. PR [#14931](https://github.com/fastapi/fastapi/pull/14931) by [@tiangolo](https://github.com/tiangolo). * 🔥 Remove Python 3.9 specific files, no longer needed after updating translations. PR [#14931](https://github.com/fastapi/fastapi/pull/14931) by [@tiangolo](https://github.com/tiangolo).
* 📝 Update docs for JWT to prevent timing attacks. PR [#14908](https://github.com/fastapi/fastapi/pull/14908) by [@tiangolo](https://github.com/tiangolo). * 📝 Update docs for JWT to prevent timing attacks. PR [#14908](https://github.com/fastapi/fastapi/pull/14908) by [@tiangolo](https://github.com/tiangolo).
### Translations ### Translations
* ✏️ Fix several typos in ru translations. PR [#14934](https://github.com/fastapi/fastapi/pull/14934) by [@argoarsiks](https://github.com/argoarsiks).
* 🌐 Update translations for ko (update-all and add-missing). PR [#14923](https://github.com/fastapi/fastapi/pull/14923) by [@YuriiMotov](https://github.com/YuriiMotov). * 🌐 Update translations for ko (update-all and add-missing). PR [#14923](https://github.com/fastapi/fastapi/pull/14923) by [@YuriiMotov](https://github.com/YuriiMotov).
* 🌐 Update translations for uk (add-missing). PR [#14922](https://github.com/fastapi/fastapi/pull/14922) by [@YuriiMotov](https://github.com/YuriiMotov). * 🌐 Update translations for uk (add-missing). PR [#14922](https://github.com/fastapi/fastapi/pull/14922) by [@YuriiMotov](https://github.com/YuriiMotov).
* 🌐 Update translations for zh-hant (update-all and add-missing). PR [#14921](https://github.com/fastapi/fastapi/pull/14921) by [@YuriiMotov](https://github.com/YuriiMotov). * 🌐 Update translations for zh-hant (update-all and add-missing). PR [#14921](https://github.com/fastapi/fastapi/pull/14921) by [@YuriiMotov](https://github.com/YuriiMotov).
@ -29,6 +158,7 @@ hide:
### Internal ### Internal
* 👷 Always run tests on push to `master` branch and when run by scheduler. PR [#14940](https://github.com/fastapi/fastapi/pull/14940) by [@YuriiMotov](https://github.com/YuriiMotov).
* 🎨 Upgrade typing syntax for Python 3.10. PR [#14932](https://github.com/fastapi/fastapi/pull/14932) by [@tiangolo](https://github.com/tiangolo). * 🎨 Upgrade typing syntax for Python 3.10. PR [#14932](https://github.com/fastapi/fastapi/pull/14932) by [@tiangolo](https://github.com/tiangolo).
* ⬆ Bump cryptography from 46.0.4 to 46.0.5. PR [#14892](https://github.com/fastapi/fastapi/pull/14892) by [@dependabot[bot]](https://github.com/apps/dependabot). * ⬆ Bump cryptography from 46.0.4 to 46.0.5. PR [#14892](https://github.com/fastapi/fastapi/pull/14892) by [@dependabot[bot]](https://github.com/apps/dependabot).
* ⬆ Bump pillow from 12.1.0 to 12.1.1. PR [#14899](https://github.com/fastapi/fastapi/pull/14899) by [@dependabot[bot]](https://github.com/apps/dependabot). * ⬆ Bump pillow from 12.1.0 to 12.1.1. PR [#14899](https://github.com/fastapi/fastapi/pull/14899) by [@dependabot[bot]](https://github.com/apps/dependabot).

1
docs/en/docs/tutorial/response-model.md

@ -13,6 +13,7 @@ FastAPI will use this return type to:
* Add a **JSON Schema** for the response, in the OpenAPI *path operation*. * Add a **JSON Schema** for the response, in the OpenAPI *path operation*.
* This will be used by the **automatic docs**. * This will be used by the **automatic docs**.
* It will also be used by automatic client code generation tools. * It will also be used by automatic client code generation tools.
* **Serialize** the returned data to JSON using Pydantic, which is written in **Rust**, so it will be **much faster**.
But most importantly: But most importantly:

120
docs/en/docs/tutorial/server-sent-events.md

@ -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. 🤓

111
docs/en/docs/tutorial/stream-json-lines.md

@ -0,0 +1,111 @@
# Stream JSON Lines { #stream-json-lines }
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.
So, it will send the first item, the client will receive and start processing it, and you might still be producing the next item.
```mermaid
sequenceDiagram
participant App
participant Client
App->>App: Produce Item 1
App->>Client: Send Item 1
App->>App: Produce Item 2
Client->>Client: Process Item 1
App->>Client: Send Item 2
App->>App: Produce Item 3
Client->>Client: Process Item 2
App->>Client: Send Item 3
Client->>Client: Process Item 3
Note over App: Keeps producing...
Note over Client: Keeps consuming...
```
It could even be an infinite stream, where you keep sending data.
## JSON Lines { #json-lines }
In these cases, it's common to send "**JSON Lines**", which is a format where you send one JSON object per line.
A response would have a content type of `application/jsonl` (instead of `application/json`) and the body would be something like:
```json
{"name": "Plumbus", "description": "A multi-purpose household device."}
{"name": "Portal Gun", "description": "A portal opening device."}
{"name": "Meeseeks Box", "description": "A box that summons a Meeseeks."}
```
It's very similar to a JSON array (equivalent of a Python list), but instead of being wrapped in `[]` and having `,` between the items, it has **one JSON object per line**, they are separated by a new line character.
/// info
The important point is that your app will be able to produce each line in turn, while the client consumes the previous lines.
///
/// note | Technical Details
Because each JSON object will be separated by a new line, they can't contain literal new line characters in their content, but they can contain escaped new lines (`\n`), which is part of the JSON standard.
But normally you won't have to worry about it, it's done automatically, continue reading. 🤓
///
## Use Cases { #use-cases }
You could use this to stream data from an **AI LLM** service, from **logs** or **telemetry**, or from other types of data that can be structured in **JSON** items.
/// tip
If you want to stream binary data, for example video or audio, check the advanced guide: [Stream Data](../advanced/stream-data.md).
///
## Stream JSON Lines with FastAPI { #stream-json-lines-with-fastapi }
To stream JSON Lines with FastAPI you can, instead of using `return` in your *path operation function*, use `yield` to produce each item in turn.
{* ../../docs_src/stream_json_lines/tutorial001_py310.py ln[1:24] hl[24] *}
If each JSON item you want to send back is of type `Item` (a Pydantic model) and it's an async function, you can declare the return type as `AsyncIterable[Item]`:
{* ../../docs_src/stream_json_lines/tutorial001_py310.py ln[1:24] hl[9:11,22] *}
If you declare the return type, FastAPI will use it to **validate** the data, **document** it in OpenAPI, **filter** it, and **serialize** it using Pydantic.
/// 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/stream_json_lines/tutorial001_py310.py ln[27:30] hl[28] *}
### No Return Type { #no-return-type }
You can also omit the return type. FastAPI will then use the [`jsonable_encoder`](./encoder.md){.internal-link target=_blank} to convert the data to something that can be serialized to JSON and then send it as JSON Lines.
{* ../../docs_src/stream_json_lines/tutorial001_py310.py ln[33:36] hl[34] *}
## Server-Sent Events (SSE) { #server-sent-events-sse }
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}. 🤓

6
docs/en/mkdocs.yml

@ -154,6 +154,8 @@ nav:
- tutorial/cors.md - tutorial/cors.md
- tutorial/sql-databases.md - tutorial/sql-databases.md
- tutorial/bigger-applications.md - tutorial/bigger-applications.md
- tutorial/stream-json-lines.md
- tutorial/server-sent-events.md
- tutorial/background-tasks.md - tutorial/background-tasks.md
- tutorial/metadata.md - tutorial/metadata.md
- tutorial/static-files.md - tutorial/static-files.md
@ -161,6 +163,7 @@ nav:
- tutorial/debugging.md - tutorial/debugging.md
- Advanced User Guide: - Advanced User Guide:
- advanced/index.md - advanced/index.md
- advanced/stream-data.md
- advanced/path-operation-advanced-configuration.md - advanced/path-operation-advanced-configuration.md
- advanced/additional-status-codes.md - advanced/additional-status-codes.md
- advanced/response-directly.md - advanced/response-directly.md
@ -192,6 +195,8 @@ nav:
- advanced/wsgi.md - advanced/wsgi.md
- advanced/generate-clients.md - advanced/generate-clients.md
- advanced/advanced-python-types.md - advanced/advanced-python-types.md
- advanced/json-base64-bytes.md
- advanced/strict-content-type.md
- fastapi-cli.md - fastapi-cli.md
- Deployment: - Deployment:
- deployment/index.md - deployment/index.md
@ -342,5 +347,6 @@ extra_css:
extra_javascript: extra_javascript:
- js/termynal.js - js/termynal.js
- js/custom.js - js/custom.js
- js/init_kapa_widget.js
hooks: hooks:
- ../../scripts/mkdocs_hooks.py - ../../scripts/mkdocs_hooks.py

10
docs/es/docs/advanced/websockets.md

@ -38,13 +38,13 @@ En producción tendrías una de las opciones anteriores.
Pero es la forma más sencilla de enfocarse en el lado del servidor de WebSockets y tener un ejemplo funcional: Pero es la forma más sencilla de enfocarse en el lado del servidor de WebSockets y tener un ejemplo funcional:
{* ../../docs_src/websockets/tutorial001_py310.py hl[2,6:38,41:43] *} {* ../../docs_src/websockets_/tutorial001_py310.py hl[2,6:38,41:43] *}
## Crear un `websocket` { #create-a-websocket } ## Crear un `websocket` { #create-a-websocket }
En tu aplicación de **FastAPI**, crea un `websocket`: En tu aplicación de **FastAPI**, crea un `websocket`:
{* ../../docs_src/websockets/tutorial001_py310.py hl[1,46:47] *} {* ../../docs_src/websockets_/tutorial001_py310.py hl[1,46:47] *}
/// note | Detalles Técnicos /// note | Detalles Técnicos
@ -58,7 +58,7 @@ También podrías usar `from starlette.websockets import WebSocket`.
En tu ruta de WebSocket puedes `await` para recibir mensajes y enviar mensajes. En tu ruta de WebSocket puedes `await` para recibir mensajes y enviar mensajes.
{* ../../docs_src/websockets/tutorial001_py310.py hl[48:52] *} {* ../../docs_src/websockets_/tutorial001_py310.py hl[48:52] *}
Puedes recibir y enviar datos binarios, de texto y JSON. Puedes recibir y enviar datos binarios, de texto y JSON.
@ -109,7 +109,7 @@ En endpoints de WebSocket puedes importar desde `fastapi` y usar:
Funcionan de la misma manera que para otros endpoints de FastAPI/*path operations*: Funcionan de la misma manera que para otros endpoints de FastAPI/*path operations*:
{* ../../docs_src/websockets/tutorial002_an_py310.py hl[68:69,82] *} {* ../../docs_src/websockets_/tutorial002_an_py310.py hl[68:69,82] *}
/// info | Información /// info | Información
@ -154,7 +154,7 @@ Con eso puedes conectar el WebSocket y luego enviar y recibir mensajes:
Cuando una conexión de WebSocket se cierra, el `await websocket.receive_text()` lanzará una excepción `WebSocketDisconnect`, que puedes capturar y manejar como en este ejemplo. Cuando una conexión de WebSocket se cierra, el `await websocket.receive_text()` lanzará una excepción `WebSocketDisconnect`, que puedes capturar y manejar como en este ejemplo.
{* ../../docs_src/websockets/tutorial003_py310.py hl[79:81] *} {* ../../docs_src/websockets_/tutorial003_py310.py hl[79:81] *}
Para probarlo: Para probarlo:

10
docs/fr/docs/advanced/websockets.md

@ -38,13 +38,13 @@ En production, vous auriez l'une des options ci-dessus.
Mais c'est la façon la plus simple de se concentrer sur la partie serveur des WebSockets et d'avoir un exemple fonctionnel : Mais c'est la façon la plus simple de se concentrer sur la partie serveur des WebSockets et d'avoir un exemple fonctionnel :
{* ../../docs_src/websockets/tutorial001_py310.py hl[2,6:38,41:43] *} {* ../../docs_src/websockets_/tutorial001_py310.py hl[2,6:38,41:43] *}
## Créer un `websocket` { #create-a-websocket } ## Créer un `websocket` { #create-a-websocket }
Dans votre application **FastAPI**, créez un `websocket` : Dans votre application **FastAPI**, créez un `websocket` :
{* ../../docs_src/websockets/tutorial001_py310.py hl[1,46:47] *} {* ../../docs_src/websockets_/tutorial001_py310.py hl[1,46:47] *}
/// note | Détails techniques /// note | Détails techniques
@ -58,7 +58,7 @@ Vous pourriez aussi utiliser `from starlette.websockets import WebSocket`.
Dans votre route WebSocket, vous pouvez `await` des messages et envoyer des messages. Dans votre route WebSocket, vous pouvez `await` des messages et envoyer des messages.
{* ../../docs_src/websockets/tutorial001_py310.py hl[48:52] *} {* ../../docs_src/websockets_/tutorial001_py310.py hl[48:52] *}
Vous pouvez recevoir et envoyer des données binaires, texte et JSON. Vous pouvez recevoir et envoyer des données binaires, texte et JSON.
@ -109,7 +109,7 @@ Dans les endpoints WebSocket, vous pouvez importer depuis `fastapi` et utiliser
Ils fonctionnent de la même manière que pour les autres endpoints/*chemins d'accès* FastAPI : Ils fonctionnent de la même manière que pour les autres endpoints/*chemins d'accès* FastAPI :
{* ../../docs_src/websockets/tutorial002_an_py310.py hl[68:69,82] *} {* ../../docs_src/websockets_/tutorial002_an_py310.py hl[68:69,82] *}
/// info /// info
@ -154,7 +154,7 @@ Avec cela, vous pouvez connecter le WebSocket puis envoyer et recevoir des messa
Lorsqu'une connexion WebSocket est fermée, l'instruction `await websocket.receive_text()` lèvera une exception `WebSocketDisconnect`, que vous pouvez ensuite intercepter et gérer comme dans cet exemple. Lorsqu'une connexion WebSocket est fermée, l'instruction `await websocket.receive_text()` lèvera une exception `WebSocketDisconnect`, que vous pouvez ensuite intercepter et gérer comme dans cet exemple.
{* ../../docs_src/websockets/tutorial003_py310.py hl[79:81] *} {* ../../docs_src/websockets_/tutorial003_py310.py hl[79:81] *}
Pour l'essayer : Pour l'essayer :

10
docs/ja/docs/advanced/websockets.md

@ -38,13 +38,13 @@ $ pip install websockets
しかし、これはWebSocketsのサーバーサイドに焦点を当て、動作する例を示す最も簡単な方法です。 しかし、これはWebSocketsのサーバーサイドに焦点を当て、動作する例を示す最も簡単な方法です。
{* ../../docs_src/websockets/tutorial001_py310.py hl[2,6:38,41:43] *} {* ../../docs_src/websockets_/tutorial001_py310.py hl[2,6:38,41:43] *}
## `websocket` を作成する { #create-a-websocket } ## `websocket` を作成する { #create-a-websocket }
**FastAPI** アプリケーションで、`websocket` を作成します。 **FastAPI** アプリケーションで、`websocket` を作成します。
{* ../../docs_src/websockets/tutorial001_py310.py hl[1,46:47] *} {* ../../docs_src/websockets_/tutorial001_py310.py hl[1,46:47] *}
/// note | 技術詳細 /// note | 技術詳細
@ -58,7 +58,7 @@ $ pip install websockets
WebSocketルートでは、メッセージを待機して送信するために `await` を使用できます。 WebSocketルートでは、メッセージを待機して送信するために `await` を使用できます。
{* ../../docs_src/websockets/tutorial001_py310.py hl[48:52] *} {* ../../docs_src/websockets_/tutorial001_py310.py hl[48:52] *}
バイナリやテキストデータ、JSONデータを送受信できます。 バイナリやテキストデータ、JSONデータを送受信できます。
@ -109,7 +109,7 @@ WebSocketエンドポイントでは、`fastapi` から以下をインポート
これらは、他のFastAPI エンドポイント/*path operations* の場合と同じように機能します。 これらは、他のFastAPI エンドポイント/*path operations* の場合と同じように機能します。
{* ../../docs_src/websockets/tutorial002_an_py310.py hl[68:69,82] *} {* ../../docs_src/websockets_/tutorial002_an_py310.py hl[68:69,82] *}
/// info | 情報 /// info | 情報
@ -154,7 +154,7 @@ $ fastapi dev main.py
WebSocket接続が閉じられると、 `await websocket.receive_text()` は例外 `WebSocketDisconnect` を発生させ、この例のようにキャッチして処理することができます。 WebSocket接続が閉じられると、 `await websocket.receive_text()` は例外 `WebSocketDisconnect` を発生させ、この例のようにキャッチして処理することができます。
{* ../../docs_src/websockets/tutorial003_py310.py hl[79:81] *} {* ../../docs_src/websockets_/tutorial003_py310.py hl[79:81] *}
試してみるには、 試してみるには、

10
docs/ko/docs/advanced/websockets.md

@ -38,13 +38,13 @@ $ pip install websockets
그러나 이는 WebSockets의 서버 측에 집중하고 동작하는 예제를 제공하는 가장 간단한 방법입니다: 그러나 이는 WebSockets의 서버 측에 집중하고 동작하는 예제를 제공하는 가장 간단한 방법입니다:
{* ../../docs_src/websockets/tutorial001_py310.py hl[2,6:38,41:43] *} {* ../../docs_src/websockets_/tutorial001_py310.py hl[2,6:38,41:43] *}
## `websocket` 생성하기 { #create-a-websocket } ## `websocket` 생성하기 { #create-a-websocket }
**FastAPI** 애플리케이션에서 `websocket`을 생성합니다: **FastAPI** 애플리케이션에서 `websocket`을 생성합니다:
{* ../../docs_src/websockets/tutorial001_py310.py hl[1,46:47] *} {* ../../docs_src/websockets_/tutorial001_py310.py hl[1,46:47] *}
/// note | 기술 세부사항 /// note | 기술 세부사항
@ -58,7 +58,7 @@ $ pip install websockets
WebSocket 경로에서 메시지를 대기(`await`)하고 전송할 수 있습니다. WebSocket 경로에서 메시지를 대기(`await`)하고 전송할 수 있습니다.
{* ../../docs_src/websockets/tutorial001_py310.py hl[48:52] *} {* ../../docs_src/websockets_/tutorial001_py310.py hl[48:52] *}
여러분은 이진 데이터, 텍스트, JSON 데이터를 받을 수 있고 전송할 수 있습니다. 여러분은 이진 데이터, 텍스트, JSON 데이터를 받을 수 있고 전송할 수 있습니다.
@ -109,7 +109,7 @@ WebSocket 엔드포인트에서 `fastapi`에서 다음을 가져와 사용할
이들은 다른 FastAPI 엔드포인트/*경로 처리*와 동일하게 동작합니다: 이들은 다른 FastAPI 엔드포인트/*경로 처리*와 동일하게 동작합니다:
{* ../../docs_src/websockets/tutorial002_an_py310.py hl[68:69,82] *} {* ../../docs_src/websockets_/tutorial002_an_py310.py hl[68:69,82] *}
/// info | 정보 /// info | 정보
@ -154,7 +154,7 @@ $ fastapi dev main.py
WebSocket 연결이 닫히면, `await websocket.receive_text()``WebSocketDisconnect` 예외를 발생시킵니다. 그러면 이 예제처럼 이를 잡아 처리할 수 있습니다. WebSocket 연결이 닫히면, `await websocket.receive_text()``WebSocketDisconnect` 예외를 발생시킵니다. 그러면 이 예제처럼 이를 잡아 처리할 수 있습니다.
{* ../../docs_src/websockets/tutorial003_py310.py hl[79:81] *} {* ../../docs_src/websockets_/tutorial003_py310.py hl[79:81] *}
테스트해보기: 테스트해보기:

10
docs/pt/docs/advanced/websockets.md

@ -38,13 +38,13 @@ Na produção, você teria uma das opções acima.
Mas é a maneira mais simples de focar no lado do servidor de WebSockets e ter um exemplo funcional: Mas é a maneira mais simples de focar no lado do servidor de WebSockets e ter um exemplo funcional:
{* ../../docs_src/websockets/tutorial001_py310.py hl[2,6:38,41:43] *} {* ../../docs_src/websockets_/tutorial001_py310.py hl[2,6:38,41:43] *}
## Crie um `websocket` { #create-a-websocket } ## Crie um `websocket` { #create-a-websocket }
Em sua aplicação **FastAPI**, crie um `websocket`: Em sua aplicação **FastAPI**, crie um `websocket`:
{* ../../docs_src/websockets/tutorial001_py310.py hl[1,46:47] *} {* ../../docs_src/websockets_/tutorial001_py310.py hl[1,46:47] *}
/// note | Detalhes Técnicos /// note | Detalhes Técnicos
@ -58,7 +58,7 @@ A **FastAPI** fornece o mesmo `WebSocket` diretamente apenas como uma conveniên
Em sua rota WebSocket você pode esperar (`await`) por mensagens e enviar mensagens. Em sua rota WebSocket você pode esperar (`await`) por mensagens e enviar mensagens.
{* ../../docs_src/websockets/tutorial001_py310.py hl[48:52] *} {* ../../docs_src/websockets_/tutorial001_py310.py hl[48:52] *}
Você pode receber e enviar dados binários, de texto e JSON. Você pode receber e enviar dados binários, de texto e JSON.
@ -109,7 +109,7 @@ Nos endpoints WebSocket você pode importar do `fastapi` e usar:
Eles funcionam da mesma forma que para outros endpoints FastAPI/*operações de rota*: Eles funcionam da mesma forma que para outros endpoints FastAPI/*operações de rota*:
{* ../../docs_src/websockets/tutorial002_an_py310.py hl[68:69,82] *} {* ../../docs_src/websockets_/tutorial002_an_py310.py hl[68:69,82] *}
/// info | Informação /// info | Informação
@ -154,7 +154,7 @@ Com isso você pode conectar o WebSocket e então enviar e receber mensagens:
Quando uma conexão WebSocket é fechada, o `await websocket.receive_text()` levantará uma exceção `WebSocketDisconnect`, que você pode então capturar e lidar como neste exemplo. Quando uma conexão WebSocket é fechada, o `await websocket.receive_text()` levantará uma exceção `WebSocketDisconnect`, que você pode então capturar e lidar como neste exemplo.
{* ../../docs_src/websockets/tutorial003_py310.py hl[79:81] *} {* ../../docs_src/websockets_/tutorial003_py310.py hl[79:81] *}
Para testar: Para testar:

2
docs/ru/docs/advanced/middleware.md

@ -83,7 +83,7 @@ app.add_middleware(UnicornMiddleware, some_config="rainbow")
Поддерживаются следующие аргументы: Поддерживаются следующие аргументы:
- `minimum_size` — не сжимать GZip‑ом ответы, размер которых меньше этого минимального значения в байтах. По умолчанию — `500`. - `minimum_size` — не сжимать GZip‑ом ответы, размер которых меньше этого минимального значения в байтах. По умолчанию — `500`.
- `compresslevel` — уровень GZip‑сжатия. Целое число от 1 до 9. По умолчанию — `9`. Более низкое значение — быстреее сжатие, но больший размер файла; более высокое значение — более медленное сжатие, но меньший размер файла. - `compresslevel` — уровень GZip‑сжатия. Целое число от 1 до 9. По умолчанию — `9`. Более низкое значение — быстрее сжатие, но больший размер файла; более высокое значение — более медленное сжатие, но меньший размер файла.
## Другие middleware { #other-middlewares } ## Другие middleware { #other-middlewares }

10
docs/ru/docs/advanced/websockets.md

@ -38,13 +38,13 @@ $ pip install websockets
Для примера нам нужен наиболее простой способ, который позволит сосредоточиться на серверной части веб‑сокетов и получить рабочий код: Для примера нам нужен наиболее простой способ, который позволит сосредоточиться на серверной части веб‑сокетов и получить рабочий код:
{* ../../docs_src/websockets/tutorial001_py310.py hl[2,6:38,41:43] *} {* ../../docs_src/websockets_/tutorial001_py310.py hl[2,6:38,41:43] *}
## Создание `websocket` { #create-a-websocket } ## Создание `websocket` { #create-a-websocket }
Создайте `websocket` в своем **FastAPI** приложении: Создайте `websocket` в своем **FastAPI** приложении:
{* ../../docs_src/websockets/tutorial001_py310.py hl[1,46:47] *} {* ../../docs_src/websockets_/tutorial001_py310.py hl[1,46:47] *}
/// note | Технические детали /// note | Технические детали
@ -58,7 +58,7 @@ $ pip install websockets
Через эндпоинт веб-сокета вы можете получать и отправлять сообщения. Через эндпоинт веб-сокета вы можете получать и отправлять сообщения.
{* ../../docs_src/websockets/tutorial001_py310.py hl[48:52] *} {* ../../docs_src/websockets_/tutorial001_py310.py hl[48:52] *}
Вы можете получать и отправлять двоичные, текстовые и JSON данные. Вы можете получать и отправлять двоичные, текстовые и JSON данные.
@ -109,7 +109,7 @@ $ fastapi dev main.py
Они работают так же, как и в других FastAPI эндпоинтах/*операциях пути*: Они работают так же, как и в других FastAPI эндпоинтах/*операциях пути*:
{* ../../docs_src/websockets/tutorial002_an_py310.py hl[68:69,82] *} {* ../../docs_src/websockets_/tutorial002_an_py310.py hl[68:69,82] *}
/// info | Примечание /// info | Примечание
@ -154,7 +154,7 @@ $ fastapi dev main.py
Если веб-сокет соединение закрыто, то `await websocket.receive_text()` вызовет исключение `WebSocketDisconnect`, которое можно поймать и обработать как в этом примере: Если веб-сокет соединение закрыто, то `await websocket.receive_text()` вызовет исключение `WebSocketDisconnect`, которое можно поймать и обработать как в этом примере:
{* ../../docs_src/websockets/tutorial003_py310.py hl[79:81] *} {* ../../docs_src/websockets_/tutorial003_py310.py hl[79:81] *}
Чтобы воспроизвести пример: Чтобы воспроизвести пример:

2
docs/ru/docs/deployment/docker.md

@ -214,7 +214,7 @@ CMD ["fastapi", "run", "app/main.py", "--port", "80"]
5. Копируем директорию `./app` внутрь директории `/code`. 5. Копируем директорию `./app` внутрь директории `/code`.
Так как здесь весь код, который **меняется чаще всего**, кэш Docker **вряд ли** будет использоваться для этого шагa или **последующих шагов**. Так как здесь весь код, который **меняется чаще всего**, кэш Docker **вряд ли** будет использоваться для этого шага или **последующих шагов**.
Поэтому важно разместить этот шаг **ближе к концу** `Dockerfile`, чтобы оптимизировать время сборки образа контейнера. Поэтому важно разместить этот шаг **ближе к концу** `Dockerfile`, чтобы оптимизировать время сборки образа контейнера.

2
docs/ru/docs/history-design-future.md

@ -76,4 +76,4 @@
У **FastAPI** великое будущее. У **FastAPI** великое будущее.
И [ваш вклад в это](help-fastapi.md){.internal-link target=_blank} - очень ценнен. И [ваш вклад в это](help-fastapi.md){.internal-link target=_blank} - очень ценен.

2
docs/ru/docs/tutorial/security/oauth2-jwt.md

@ -20,7 +20,7 @@ eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4
Но он подписан. Следовательно, когда вы получаете токен, который вы эмитировали (выдавали), вы можете убедиться, что это именно вы его эмитировали. Но он подписан. Следовательно, когда вы получаете токен, который вы эмитировали (выдавали), вы можете убедиться, что это именно вы его эмитировали.
Таким образом, можно создать токен со сроком действия, скажем, 1 неделя. А когда пользователь вернется на следующий день с тем же токеном, вы будете знать, что он все еще авторизирован в вашей системе. Таким образом, можно создать токен со сроком действия, скажем, 1 неделя. А когда пользователь вернется на следующий день с тем же токеном, вы будете знать, что он все еще авторизован в вашей системе.
Через неделю срок действия токена истечет, пользователь не будет авторизован и ему придется заново входить в систему, чтобы получить новый токен. А если пользователь (или третье лицо) попытается модифицировать токен, чтобы изменить срок действия, вы сможете это обнаружить, поскольку подписи не будут совпадать. Через неделю срок действия токена истечет, пользователь не будет авторизован и ему придется заново входить в систему, чтобы получить новый токен. А если пользователь (или третье лицо) попытается модифицировать токен, чтобы изменить срок действия, вы сможете это обнаружить, поскольку подписи не будут совпадать.

10
docs/tr/docs/advanced/websockets.md

@ -38,13 +38,13 @@ Production'da yukarıdaki seçeneklerden birini kullanırsınız.
Ama WebSockets'in server tarafına odaklanmak ve çalışan bir örnek görmek için en basit yol bu: Ama WebSockets'in server tarafına odaklanmak ve çalışan bir örnek görmek için en basit yol bu:
{* ../../docs_src/websockets/tutorial001_py310.py hl[2,6:38,41:43] *} {* ../../docs_src/websockets_/tutorial001_py310.py hl[2,6:38,41:43] *}
## Bir `websocket` Oluşturun { #create-a-websocket } ## Bir `websocket` Oluşturun { #create-a-websocket }
**FastAPI** uygulamanızda bir `websocket` oluşturun: **FastAPI** uygulamanızda bir `websocket` oluşturun:
{* ../../docs_src/websockets/tutorial001_py310.py hl[1,46:47] *} {* ../../docs_src/websockets_/tutorial001_py310.py hl[1,46:47] *}
/// note | Teknik Detaylar /// note | Teknik Detaylar
@ -58,7 +58,7 @@ Ama WebSockets'in server tarafına odaklanmak ve çalışan bir örnek görmek i
WebSocket route'unuzda mesajları `await` edebilir ve mesaj gönderebilirsiniz. WebSocket route'unuzda mesajları `await` edebilir ve mesaj gönderebilirsiniz.
{* ../../docs_src/websockets/tutorial001_py310.py hl[48:52] *} {* ../../docs_src/websockets_/tutorial001_py310.py hl[48:52] *}
Binary, text ve JSON verisi alıp gönderebilirsiniz. Binary, text ve JSON verisi alıp gönderebilirsiniz.
@ -109,7 +109,7 @@ WebSocket endpoint'lerinde `fastapi` içinden import edip şunları kullanabilir
Diğer FastAPI endpoint'leri/*path operations* ile aynı şekilde çalışırlar: Diğer FastAPI endpoint'leri/*path operations* ile aynı şekilde çalışırlar:
{* ../../docs_src/websockets/tutorial002_an_py310.py hl[68:69,82] *} {* ../../docs_src/websockets_/tutorial002_an_py310.py hl[68:69,82] *}
/// info | Bilgi /// info | Bilgi
@ -154,7 +154,7 @@ Bununla WebSocket'e bağlanabilir, ardından mesaj gönderip alabilirsiniz:
Bir WebSocket bağlantısı kapandığında, `await websocket.receive_text()` bir `WebSocketDisconnect` exception'ı raise eder; ardından bunu bu örnekteki gibi yakalayıp (catch) yönetebilirsiniz. Bir WebSocket bağlantısı kapandığında, `await websocket.receive_text()` bir `WebSocketDisconnect` exception'ı raise eder; ardından bunu bu örnekteki gibi yakalayıp (catch) yönetebilirsiniz.
{* ../../docs_src/websockets/tutorial003_py310.py hl[79:81] *} {* ../../docs_src/websockets_/tutorial003_py310.py hl[79:81] *}
Denemek için: Denemek için:

10
docs/uk/docs/advanced/websockets.md

@ -38,13 +38,13 @@ $ pip install websockets
Але це найпростіший спосіб зосередитися на серверній частині WebSockets і мати робочий приклад: Але це найпростіший спосіб зосередитися на серверній частині WebSockets і мати робочий приклад:
{* ../../docs_src/websockets/tutorial001_py310.py hl[2,6:38,41:43] *} {* ../../docs_src/websockets_/tutorial001_py310.py hl[2,6:38,41:43] *}
## Створіть `websocket` { #create-a-websocket } ## Створіть `websocket` { #create-a-websocket }
У вашому застосунку **FastAPI** створіть `websocket`: У вашому застосунку **FastAPI** створіть `websocket`:
{* ../../docs_src/websockets/tutorial001_py310.py hl[1,46:47] *} {* ../../docs_src/websockets_/tutorial001_py310.py hl[1,46:47] *}
/// note | Технічні деталі /// note | Технічні деталі
@ -58,7 +58,7 @@ $ pip install websockets
У вашому маршруті WebSocket ви можете `await` повідомлення і надсилати повідомлення. У вашому маршруті WebSocket ви можете `await` повідомлення і надсилати повідомлення.
{* ../../docs_src/websockets/tutorial001_py310.py hl[48:52] *} {* ../../docs_src/websockets_/tutorial001_py310.py hl[48:52] *}
Ви можете отримувати та надсилати бінарні, текстові та JSON-дані. Ви можете отримувати та надсилати бінарні, текстові та JSON-дані.
@ -109,7 +109,7 @@ $ fastapi dev main.py
Вони працюють так само, як для інших ендпойнтів FastAPI/*операцій шляху*: Вони працюють так само, як для інших ендпойнтів FastAPI/*операцій шляху*:
{* ../../docs_src/websockets/tutorial002_an_py310.py hl[68:69,82] *} {* ../../docs_src/websockets_/tutorial002_an_py310.py hl[68:69,82] *}
/// info /// info
@ -154,7 +154,7 @@ $ fastapi dev main.py
Коли з'єднання WebSocket закривається, `await websocket.receive_text()` підніме виняток `WebSocketDisconnect`, який ви можете перехопити й обробити, як у цьому прикладі. Коли з'єднання WebSocket закривається, `await websocket.receive_text()` підніме виняток `WebSocketDisconnect`, який ви можете перехопити й обробити, як у цьому прикладі.
{* ../../docs_src/websockets/tutorial003_py310.py hl[79:81] *} {* ../../docs_src/websockets_/tutorial003_py310.py hl[79:81] *}
Щоб спробувати: Щоб спробувати:

10
docs/zh-hant/docs/advanced/websockets.md

@ -38,13 +38,13 @@ $ pip install websockets
但這是能讓我們專注於 WebSocket 伺服端並跑起一個可運作範例的最簡單方式: 但這是能讓我們專注於 WebSocket 伺服端並跑起一個可運作範例的最簡單方式:
{* ../../docs_src/websockets/tutorial001_py310.py hl[2,6:38,41:43] *} {* ../../docs_src/websockets_/tutorial001_py310.py hl[2,6:38,41:43] *}
## 建立一個 `websocket` { #create-a-websocket } ## 建立一個 `websocket` { #create-a-websocket }
在你的 **FastAPI** 應用中,建立一個 `websocket` 在你的 **FastAPI** 應用中,建立一個 `websocket`
{* ../../docs_src/websockets/tutorial001_py310.py hl[1,46:47] *} {* ../../docs_src/websockets_/tutorial001_py310.py hl[1,46:47] *}
/// note | 技術細節 /// note | 技術細節
@ -58,7 +58,7 @@ $ pip install websockets
在你的 WebSocket 路由中,你可以 `await` 接收訊息並傳送訊息。 在你的 WebSocket 路由中,你可以 `await` 接收訊息並傳送訊息。
{* ../../docs_src/websockets/tutorial001_py310.py hl[48:52] *} {* ../../docs_src/websockets_/tutorial001_py310.py hl[48:52] *}
你可以接收與傳送二進位、文字與 JSON 資料。 你可以接收與傳送二進位、文字與 JSON 資料。
@ -109,7 +109,7 @@ $ fastapi dev main.py
它們的運作方式與其他 FastAPI 端點/*路徑操作* 相同: 它們的運作方式與其他 FastAPI 端點/*路徑操作* 相同:
{* ../../docs_src/websockets/tutorial002_an_py310.py hl[68:69,82] *} {* ../../docs_src/websockets_/tutorial002_an_py310.py hl[68:69,82] *}
/// info /// info
@ -154,7 +154,7 @@ $ fastapi dev main.py
當 WebSocket 連線關閉時,`await websocket.receive_text()` 會拋出 `WebSocketDisconnect` 例外,你可以像範例中那樣捕捉並處理。 當 WebSocket 連線關閉時,`await websocket.receive_text()` 會拋出 `WebSocketDisconnect` 例外,你可以像範例中那樣捕捉並處理。
{* ../../docs_src/websockets/tutorial003_py310.py hl[79:81] *} {* ../../docs_src/websockets_/tutorial003_py310.py hl[79:81] *}
試用方式: 試用方式:

10
docs/zh/docs/advanced/websockets.md

@ -38,13 +38,13 @@ $ pip install websockets
但这是一种专注于 WebSockets 的服务器端并提供一个工作示例的最简单方式: 但这是一种专注于 WebSockets 的服务器端并提供一个工作示例的最简单方式:
{* ../../docs_src/websockets/tutorial001_py310.py hl[2,6:38,41:43] *} {* ../../docs_src/websockets_/tutorial001_py310.py hl[2,6:38,41:43] *}
## 创建 `websocket` { #create-a-websocket } ## 创建 `websocket` { #create-a-websocket }
在您的 **FastAPI** 应用程序中,创建一个 `websocket` 在您的 **FastAPI** 应用程序中,创建一个 `websocket`
{* ../../docs_src/websockets/tutorial001_py310.py hl[1,46:47] *} {* ../../docs_src/websockets_/tutorial001_py310.py hl[1,46:47] *}
/// note | 技术细节 /// note | 技术细节
@ -58,7 +58,7 @@ $ pip install websockets
在您的 WebSocket 路由中,您可以使用 `await` 等待消息并发送消息。 在您的 WebSocket 路由中,您可以使用 `await` 等待消息并发送消息。
{* ../../docs_src/websockets/tutorial001_py310.py hl[48:52] *} {* ../../docs_src/websockets_/tutorial001_py310.py hl[48:52] *}
您可以接收和发送二进制、文本和 JSON 数据。 您可以接收和发送二进制、文本和 JSON 数据。
@ -109,7 +109,7 @@ $ fastapi dev main.py
它们的工作方式与其他 FastAPI 端点/*路径操作* 相同: 它们的工作方式与其他 FastAPI 端点/*路径操作* 相同:
{* ../../docs_src/websockets/tutorial002_an_py310.py hl[68:69,82] *} {* ../../docs_src/websockets_/tutorial002_an_py310.py hl[68:69,82] *}
/// info /// info
@ -154,7 +154,7 @@ $ fastapi dev main.py
当 WebSocket 连接关闭时,`await websocket.receive_text()` 将引发 `WebSocketDisconnect` 异常,您可以捕获并处理该异常,就像本示例中的示例一样。 当 WebSocket 连接关闭时,`await websocket.receive_text()` 将引发 `WebSocketDisconnect` 异常,您可以捕获并处理该异常,就像本示例中的示例一样。
{* ../../docs_src/websockets/tutorial003_py310.py hl[79:81] *} {* ../../docs_src/websockets_/tutorial003_py310.py hl[79:81] *}
尝试以下操作: 尝试以下操作:

2
docs_src/custom_response/tutorial007_py310.py

@ -1,3 +1,4 @@
import anyio
from fastapi import FastAPI from fastapi import FastAPI
from fastapi.responses import StreamingResponse from fastapi.responses import StreamingResponse
@ -7,6 +8,7 @@ app = FastAPI()
async def fake_video_streamer(): async def fake_video_streamer():
for i in range(10): for i in range(10):
yield b"some fake video bytes" yield b"some fake video bytes"
await anyio.sleep(0)
@app.get("/") @app.get("/")

6
docs_src/custom_response/tutorial010_py310.py

@ -1,9 +1,9 @@
from fastapi import FastAPI from fastapi import FastAPI
from fastapi.responses import ORJSONResponse from fastapi.responses import HTMLResponse
app = FastAPI(default_response_class=ORJSONResponse) app = FastAPI(default_response_class=HTMLResponse)
@app.get("/items/") @app.get("/items/")
async def read_items(): async def read_items():
return [{"item_id": "Foo"}] return "<h1>Items</h1><p>This is a list of items.</p>"

0
docs_src/websockets/__init__.py → docs_src/json_base64_bytes/__init__.py

46
docs_src/json_base64_bytes/tutorial001_py310.py

@ -0,0 +1,46 @@
from fastapi import FastAPI
from pydantic import BaseModel
class DataInput(BaseModel):
description: str
data: bytes
model_config = {"val_json_bytes": "base64"}
class DataOutput(BaseModel):
description: str
data: bytes
model_config = {"ser_json_bytes": "base64"}
class DataInputOutput(BaseModel):
description: str
data: bytes
model_config = {
"val_json_bytes": "base64",
"ser_json_bytes": "base64",
}
app = FastAPI()
@app.post("/data")
def post_data(body: DataInput):
content = body.data.decode("utf-8")
return {"description": body.description, "content": content}
@app.get("/data")
def get_data() -> DataOutput:
data = "hello".encode("utf-8")
return DataOutput(description="A plumbus", data=data)
@app.post("/data-in-out")
def post_data_in_out(body: DataInputOutput) -> DataInputOutput:
return body

2
docs_src/python_types/tutorial005_py39.py

@ -1,2 +0,0 @@
def get_items(item_a: str, item_b: int, item_c: float, item_d: bool, item_e: bytes):
return item_a, item_b, item_c, item_d, item_e

0
docs_src/server_sent_events/__init__.py

43
docs_src/server_sent_events/tutorial001_py310.py

@ -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

26
docs_src/server_sent_events/tutorial002_py310.py

@ -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)

17
docs_src/server_sent_events/tutorial003_py310.py

@ -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)

31
docs_src/server_sent_events/tutorial004_py310.py

@ -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))

19
docs_src/server_sent_events/tutorial005_py310.py

@ -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")

0
docs_src/stream_data/__init__.py

65
docs_src/stream_data/tutorial001_py310.py

@ -0,0 +1,65 @@
from collections.abc import AsyncIterable, Iterable
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
app = FastAPI()
message = """
Rick: (stumbles in drunkenly, and turns on the lights) Morty! You gotta come on. You got--... you gotta come with me.
Morty: (rubs his eyes) What, Rick? What's going on?
Rick: I got a surprise for you, Morty.
Morty: It's the middle of the night. What are you talking about?
Rick: (spills alcohol on Morty's bed) Come on, I got a surprise for you. (drags Morty by the ankle) Come on, hurry up. (pulls Morty out of his bed and into the hall)
Morty: Ow! Ow! You're tugging me too hard!
Rick: We gotta go, gotta get outta here, come on. Got a surprise for you Morty.
"""
@app.get("/story/stream", response_class=StreamingResponse)
async def stream_story() -> AsyncIterable[str]:
for line in message.splitlines():
yield line
@app.get("/story/stream-no-async", response_class=StreamingResponse)
def stream_story_no_async() -> Iterable[str]:
for line in message.splitlines():
yield line
@app.get("/story/stream-no-annotation", response_class=StreamingResponse)
async def stream_story_no_annotation():
for line in message.splitlines():
yield line
@app.get("/story/stream-no-async-no-annotation", response_class=StreamingResponse)
def stream_story_no_async_no_annotation():
for line in message.splitlines():
yield line
@app.get("/story/stream-bytes", response_class=StreamingResponse)
async def stream_story_bytes() -> AsyncIterable[bytes]:
for line in message.splitlines():
yield line.encode("utf-8")
@app.get("/story/stream-no-async-bytes", response_class=StreamingResponse)
def stream_story_no_async_bytes() -> Iterable[bytes]:
for line in message.splitlines():
yield line.encode("utf-8")
@app.get("/story/stream-no-annotation-bytes", response_class=StreamingResponse)
async def stream_story_no_annotation_bytes():
for line in message.splitlines():
yield line.encode("utf-8")
@app.get("/story/stream-no-async-no-annotation-bytes", response_class=StreamingResponse)
def stream_story_no_async_no_annotation_bytes():
for line in message.splitlines():
yield line.encode("utf-8")

54
docs_src/stream_data/tutorial002_py310.py

@ -0,0 +1,54 @@
import base64
from collections.abc import AsyncIterable, Iterable
from io import BytesIO
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
image_base64 = "iVBORw0KGgoAAAANSUhEUgAAAB0AAAAdCAYAAABWk2cPAAAAbnpUWHRSYXcgcHJvZmlsZSB0eXBlIGV4aWYAAHjadYzRDYAwCET/mcIRDoq0jGOiJm7g+NJK0vjhS4DjIEfHfZ20DKqSrrWZmyFQV5ctRMOLACxglNCcXk7zVqFzJzF8kV6R5vOJ97yVH78HjfYAtg0ged033ZgAAAoCaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJYTVAgQ29yZSA0LjQuMC1FeGl2MiI+CiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICB4bWxuczpleGlmPSJodHRwOi8vbnMuYWRvYmUuY29tL2V4aWYvMS4wLyIKICAgIHhtbG5zOnRpZmY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vdGlmZi8xLjAvIgogICBleGlmOlBpeGVsWERpbWVuc2lvbj0iMjkiCiAgIGV4aWY6UGl4ZWxZRGltZW5zaW9uPSIyOSIKICAgdGlmZjpJbWFnZVdpZHRoPSIyOSIKICAgdGlmZjpJbWFnZUxlbmd0aD0iMjkiCiAgIHRpZmY6T3JpZW50YXRpb249IjEiLz4KIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAKPD94cGFja2V0IGVuZD0idyI/PnQkBZAAAAAEc0JJVAgICAh8CGSIAAABoklEQVRIx8VXwY7FIAjE5iXWU+P/f6RHPNW9LIaOoHYP+0yMShVkwNGG1lqjfy4HfaF0oyEEt+oSQqBaa//m9Wd6PlqhhbRMDiEQM3e59FNKw5qZHpnQfuPaW6lazsztvu/eElFj5j63lNLlMz2ttbZtVMu1MTGo5Sujn93gMzOllKiUQjHGB9QxxneZhJ5iwZ1rL2fwenoGeL0q3wVGhBPHMz0PeFccIfASEeWcO8xEROd50q6eAV6s1s5XXoncas1EKqVQznnwUBdJJmm1l3hmmdlOMrGO8Vl5gZ56Y0y8IZF0BuqkQWM4B6HXrRCKa1SEqyzEo7KK59RT/VHDjX3ZvSefeW3CO6O6vsiA1NrwVkxxAcYTCcHyTjZmJd00pugBQoTnzjvn+kzLBh9GtRDjhleZFwbx3kugP3GvFzdkqRlbDYw0u/HxKjuOw2QxZCGL5V5f4l7cd6qsffUa1DcLM9N1XcTMvep5ul1e4jNPtZfWGIkE6dI8MquXg/dS2CGVJQ2ushd5GmlxFdOw+1tRa32MY4zDQ9yaZ60J3/iX+QG4U3qGrFHmswAAAABJRU5ErkJggg=="
binary_image = base64.b64decode(image_base64)
def read_image() -> BytesIO:
return BytesIO(binary_image)
app = FastAPI()
class PNGStreamingResponse(StreamingResponse):
media_type = "image/png"
@app.get("/image/stream", response_class=PNGStreamingResponse)
async def stream_image() -> AsyncIterable[bytes]:
with read_image() as image_file:
for chunk in image_file:
yield chunk
@app.get("/image/stream-no-async", response_class=PNGStreamingResponse)
def stream_image_no_async() -> Iterable[bytes]:
with read_image() as image_file:
for chunk in image_file:
yield chunk
@app.get("/image/stream-no-async-yield-from", response_class=PNGStreamingResponse)
def stream_image_no_async_yield_from() -> Iterable[bytes]:
with read_image() as image_file:
yield from image_file
@app.get("/image/stream-no-annotation", response_class=PNGStreamingResponse)
async def stream_image_no_annotation():
with read_image() as image_file:
for chunk in image_file:
yield chunk
@app.get("/image/stream-no-async-no-annotation", response_class=PNGStreamingResponse)
def stream_image_no_async_no_annotation():
with read_image() as image_file:
for chunk in image_file:
yield chunk

0
docs_src/stream_json_lines/__init__.py

42
docs_src/stream_json_lines/tutorial001_py310.py

@ -0,0 +1,42 @@
from collections.abc import AsyncIterable, Iterable
from fastapi import FastAPI
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")
async def stream_items() -> AsyncIterable[Item]:
for item in items:
yield item
@app.get("/items/stream-no-async")
def stream_items_no_async() -> Iterable[Item]:
for item in items:
yield item
@app.get("/items/stream-no-annotation")
async def stream_items_no_annotation():
for item in items:
yield item
@app.get("/items/stream-no-async-no-annotation")
def stream_items_no_async_no_annotation():
for item in items:
yield item

0
docs_src/strict_content_type/__init__.py

14
docs_src/strict_content_type/tutorial001_py310.py

@ -0,0 +1,14 @@
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI(strict_content_type=False)
class Item(BaseModel):
name: str
price: float
@app.post("/items/")
async def create_item(item: Item):
return item

0
docs_src/websockets_/__init__.py

0
docs_src/websockets/tutorial001_py310.py → docs_src/websockets_/tutorial001_py310.py

0
docs_src/websockets/tutorial002_an_py310.py → docs_src/websockets_/tutorial002_an_py310.py

0
docs_src/websockets/tutorial002_py310.py → docs_src/websockets_/tutorial002_py310.py

0
docs_src/websockets/tutorial003_py310.py → docs_src/websockets_/tutorial003_py310.py

436
fastapi/.agents/skills/fastapi/SKILL.md

@ -0,0 +1,436 @@
---
name: fastapi
description: FastAPI best practices and conventions. Use when working with FastAPI APIs and Pydantic models for them. Keeps FastAPI code clean and up to date with the latest features and patterns, updated with new versions. Write new code or refactor and update old code.
---
# FastAPI
Official FastAPI skill to write code with best practices, keeping up to date with new versions and features.
## Use the `fastapi` CLI
Run the development server on localhost with reload:
```bash
fastapi dev
```
Run the production server:
```bash
fastapi run
```
### Add an entrypoint in `pyproject.toml`
FastAPI CLI will read the entrypoint in `pyproject.toml` to know where the FastAPI app is declared.
```toml
[tool.fastapi]
entrypoint = "my_app.main:app"
```
### Use `fastapi` with a path
When adding the entrypoint to `pyproject.toml` is not possible, or the user explicitly asks not to, or it's running an independent small app, you can pass the app file path to the `fastapi` command:
```bash
fastapi dev my_app/main.py
```
Prefer to set the entrypoint in `pyproject.toml` when possible.
## Use `Annotated`
Always prefer the `Annotated` style for parameter and dependency declarations.
It keeps the function signatures working in other contexts, respects the types, allows reusability.
### In Parameter Declarations
Use `Annotated` for parameter declarations, including `Path`, `Query`, `Header`, etc.:
```python
from typing import Annotated
from fastapi import FastAPI, Path, Query
app = FastAPI()
@app.get("/items/{item_id}")
async def read_item(
item_id: Annotated[int, Path(ge=1, description="The item ID")],
q: Annotated[str | None, Query(max_length=50)] = None,
):
return {"message": "Hello World"}
```
instead of:
```python
# DO NOT DO THIS
@app.get("/items/{item_id}")
async def read_item(
item_id: int = Path(ge=1, description="The item ID"),
q: str | None = Query(default=None, max_length=50),
):
return {"message": "Hello World"}
```
### For Dependencies
Use `Annotated` for dependencies with `Depends()`.
Unless asked not to, create a new type alias for the dependency to allow re-using it.
```python
from typing import Annotated
from fastapi import Depends, FastAPI
app = FastAPI()
def get_current_user():
return {"username": "johndoe"}
CurrentUserDep = Annotated[dict, Depends(get_current_user)]
@app.get("/items/")
async def read_item(current_user: CurrentUserDep):
return {"message": "Hello World"}
```
instead of:
```python
# DO NOT DO THIS
@app.get("/items/")
async def read_item(current_user: dict = Depends(get_current_user)):
return {"message": "Hello World"}
```
## Do not use Ellipsis for *path operations* or Pydantic models
Do not use `...` as a default value for required parameters, it's not needed and not recommended.
Do this, without Ellipsis (`...`):
```python
from typing import Annotated
from fastapi import FastAPI, Query
from pydantic import BaseModel, Field
class Item(BaseModel):
name: str
description: str | None = None
price: float = Field(gt=0)
app = FastAPI()
@app.post("/items/")
async def create_item(item: Item, project_id: Annotated[int, Query()]): ...
```
instead of this:
```python
# DO NOT DO THIS
class Item(BaseModel):
name: str = ...
description: str | None = None
price: float = Field(..., gt=0)
app = FastAPI()
@app.post("/items/")
async def create_item(item: Item, project_id: Annotated[int, Query(...)]): ...
```
## Return Type or Response Model
When possible, include a return type. It will be used to validate, filter, document, and serialize the response.
```python
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
@app.get("/items/me")
async def get_item() -> Item:
return Item(name="Plumbus", description="All-purpose home device")
```
**Important**: Return types or response models are what filter data ensuring no sensitive information is exposed. And they are used to serialize data with Pydantic (in Rust), this is the main idea that can increase response performance.
The return type doesn't have to be a Pydantic model, it could be a different type, like a list of integers, or a dict, etc.
### When to use `response_model` instead
If the return type is not the same as the type that you want to use to validate, filter, or serialize, use the `response_model` parameter on the decorator instead.
```python
from typing import Any
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
@app.get("/items/me", response_model=Item)
async def get_item() -> Any:
return {"name": "Foo", "description": "A very nice Item"}
```
This can be particularly useful when filtering data to expose only the public fields and avoid exposing sensitive information.
```python
from typing import Any
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class InternalItem(BaseModel):
name: str
description: str | None = None
secret_key: str
class Item(BaseModel):
name: str
description: str | None = None
@app.get("/items/me", response_model=Item)
async def get_item() -> Any:
item = InternalItem(
name="Foo", description="A very nice Item", secret_key="supersecret"
)
return item
```
## Performance
Do not use `ORJSONResponse` or `UJSONResponse`, they are deprecated.
Instead, declare a return type or response model. Pydantic will handle the data serialization on the Rust side.
## Including Routers
When declaring routers, prefer to add router level parameters like prefix, tags, etc. to the router itself, instead of in `include_router()`.
Do this:
```python
from fastapi import APIRouter, FastAPI
app = FastAPI()
router = APIRouter(prefix="/items", tags=["items"])
@router.get("/")
async def list_items():
return []
# In main.py
app.include_router(router)
```
instead of this:
```python
# DO NOT DO THIS
from fastapi import APIRouter, FastAPI
app = FastAPI()
router = APIRouter()
@router.get("/")
async def list_items():
return []
# In main.py
app.include_router(router, prefix="/items", tags=["items"])
```
There could be exceptions, but try to follow this convention.
Apply shared dependencies at the router level via `dependencies=[Depends(...)]`.
## Dependency Injection
See [the dependency injection reference](references/dependencies.md) for detailed patterns including `yield` with `scope`, and class dependencies.
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.
Apply shared dependencies at the router level via `dependencies=[Depends(...)]`.
## Async vs Sync *path operations*
Use `async` *path operations* only when fully certain that the logic called inside is compatible with async and await (it's called with `await`) or that doesn't block.
```python
from fastapi import FastAPI
app = FastAPI()
# Use async def when calling async code
@app.get("/async-items/")
async def read_async_items():
data = await some_async_library.fetch_items()
return data
# Use plain def when calling blocking/sync code or when in doubt
@app.get("/items/")
def read_items():
data = some_blocking_library.fetch_items()
return data
```
In case of doubt, or by default, use regular `def` functions, those will be run in a threadpool so they don't block the event loop.
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.
When needing to mix blocking and async code, see Asyncer in [the other tools reference](references/other-tools.md).
## Streaming (JSON Lines, SSE, bytes)
See [the streaming reference](references/streaming.md) for JSON Lines, Server-Sent Events (`EventSourceResponse`, `ServerSentEvent`), and byte streaming (`StreamingResponse`) patterns.
## Tooling
See [the other tools reference](references/other-tools.md) for details on uv, Ruff, ty for package management, linting, type checking, formatting, etc.
## Other Libraries
See [the other tools reference](references/other-tools.md) for details on other libraries:
* 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
Do not use Pydantic `RootModel`, instead use regular type annotations with `Annotated` and Pydantic validation utilities.
For example, for a list with validations you could do:
```python
from typing import Annotated
from fastapi import Body, FastAPI
from pydantic import Field
app = FastAPI()
@app.post("/items/")
async def create_items(items: Annotated[list[int], Field(min_length=1), Body()]):
return items
```
instead of:
```python
# DO NOT DO THIS
from typing import Annotated
from fastapi import FastAPI
from pydantic import Field, RootModel
app = FastAPI()
class ItemList(RootModel[Annotated[list[int], Field(min_length=1)]]):
pass
@app.post("/items/")
async def create_items(items: ItemList):
return items
```
FastAPI supports these type annotations and will create a Pydantic `TypeAdapter` for them, so that types can work as normally and there's no need for the custom logic and types in RootModels.
## Use one HTTP operation per function
Don't mix HTTP operations in a single function, having one function per HTTP operation helps separate concerns and organize the code.
Do this:
```python
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
@app.get("/items/")
async def list_items():
return []
@app.post("/items/")
async def create_item(item: Item):
return item
```
instead of this:
```python
# DO NOT DO THIS
from fastapi import FastAPI, Request
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
@app.api_route("/items/", methods=["GET", "POST"])
async def handle_items(request: Request):
if request.method == "GET":
return []
```

142
fastapi/.agents/skills/fastapi/references/dependencies.md

@ -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()
```

76
fastapi/.agents/skills/fastapi/references/other-tools.md

@ -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.

105
fastapi/.agents/skills/fastapi/references/streaming.md

@ -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())
```

2
fastapi/__init__.py

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

45
fastapi/_compat/v2.py

@ -27,7 +27,7 @@ from pydantic._internal._schema_generation_shared import ( # type: ignore[attr-
) )
from pydantic._internal._typing_extra import eval_type_lenient from pydantic._internal._typing_extra import eval_type_lenient
from pydantic.fields import FieldInfo as FieldInfo from pydantic.fields import FieldInfo as FieldInfo
from pydantic.json_schema import GenerateJsonSchema as GenerateJsonSchema from pydantic.json_schema import GenerateJsonSchema as _GenerateJsonSchema
from pydantic.json_schema import JsonSchemaValue as JsonSchemaValue from pydantic.json_schema import JsonSchemaValue as JsonSchemaValue
from pydantic_core import CoreSchema as CoreSchema from pydantic_core import CoreSchema as CoreSchema
from pydantic_core import PydanticUndefined from pydantic_core import PydanticUndefined
@ -40,6 +40,23 @@ RequiredParam = PydanticUndefined
Undefined = PydanticUndefined Undefined = PydanticUndefined
evaluate_forwardref = eval_type_lenient evaluate_forwardref = eval_type_lenient
class GenerateJsonSchema(_GenerateJsonSchema):
# TODO: remove when this is merged (or equivalent): https://github.com/pydantic/pydantic/pull/12841
# and dropping support for any version of Pydantic before that one (so, in a very long time)
def bytes_schema(self, schema: CoreSchema) -> JsonSchemaValue:
json_schema = {"type": "string", "contentMediaType": "application/octet-stream"}
bytes_mode = (
self._config.ser_json_bytes
if self.mode == "serialization"
else self._config.val_json_bytes
)
if bytes_mode == "base64":
json_schema["contentEncoding"] = "base64"
self.update_with_validations(json_schema, schema, self.ValidationsMapping.bytes)
return json_schema
# TODO: remove when dropping support for Pydantic < v2.12.3 # TODO: remove when dropping support for Pydantic < v2.12.3
_Attrs = { _Attrs = {
"default": ..., "default": ...,
@ -182,6 +199,32 @@ class ModelField:
exclude_none=exclude_none, exclude_none=exclude_none,
) )
def serialize_json(
self,
value: Any,
*,
include: IncEx | None = None,
exclude: IncEx | None = None,
by_alias: bool = True,
exclude_unset: bool = False,
exclude_defaults: bool = False,
exclude_none: bool = False,
) -> bytes:
# What calls this code passes a value that already called
# self._type_adapter.validate_python(value)
# This uses Pydantic's dump_json() which serializes directly to JSON
# bytes in one pass (via Rust), avoiding the intermediate Python dict
# step of dump_python(mode="json") + json.dumps().
return self._type_adapter.dump_json(
value,
include=include,
exclude=exclude,
by_alias=by_alias,
exclude_unset=exclude_unset,
exclude_defaults=exclude_defaults,
exclude_none=exclude_none,
)
def __hash__(self) -> int: def __hash__(self) -> int:
# Each ModelField is unique for our purposes, to allow making a dict from # Each ModelField is unique for our purposes, to allow making a dict from
# ModelField to its JSON Schema. # ModelField to its JSON Schema.

40
fastapi/applications.py

@ -840,6 +840,29 @@ class FastAPI(Starlette):
""" """
), ),
] = None, ] = None,
strict_content_type: Annotated[
bool,
Doc(
"""
Enable strict checking for request Content-Type headers.
When `True` (the default), requests with a body that do not include
a `Content-Type` header will **not** be parsed as JSON.
This prevents potential cross-site request forgery (CSRF) attacks
that exploit the browser's ability to send requests without a
Content-Type header, bypassing CORS preflight checks. In particular
applicable for apps that need to be run locally (in localhost).
When `False`, requests without a `Content-Type` header will have
their body parsed as JSON, which maintains compatibility with
certain clients that don't send `Content-Type` headers.
Read more about it in the
[FastAPI docs for Strict Content-Type](https://fastapi.tiangolo.com/advanced/strict-content-type/).
"""
),
] = True,
**extra: Annotated[ **extra: Annotated[
Any, Any,
Doc( Doc(
@ -974,6 +997,7 @@ class FastAPI(Starlette):
include_in_schema=include_in_schema, include_in_schema=include_in_schema,
responses=responses, responses=responses,
generate_unique_id_function=generate_unique_id_function, generate_unique_id_function=generate_unique_id_function,
strict_content_type=strict_content_type,
) )
self.exception_handlers: dict[ self.exception_handlers: dict[
Any, Callable[[Request, Any], Response | Awaitable[Response]] Any, Callable[[Request, Any], Response | Awaitable[Response]]
@ -1077,16 +1101,18 @@ class FastAPI(Starlette):
def setup(self) -> None: def setup(self) -> None:
if self.openapi_url: if self.openapi_url:
urls = (server_data.get("url") for server_data in self.servers)
server_urls = {url for url in urls if url}
async def openapi(req: Request) -> JSONResponse: async def openapi(req: Request) -> JSONResponse:
root_path = req.scope.get("root_path", "").rstrip("/") root_path = req.scope.get("root_path", "").rstrip("/")
if root_path not in server_urls: schema = self.openapi()
if root_path and self.root_path_in_servers: if root_path and self.root_path_in_servers:
self.servers.insert(0, {"url": root_path}) server_urls = {s.get("url") for s in schema.get("servers", [])}
server_urls.add(root_path) if root_path not in server_urls:
return JSONResponse(self.openapi()) schema = dict(schema)
schema["servers"] = [{"url": root_path}] + schema.get(
"servers", []
)
return JSONResponse(schema)
self.add_route(self.openapi_url, openapi, include_in_schema=False) self.add_route(self.openapi_url, openapi, include_in_schema=False)
if self.openapi_url and self.docs_url: if self.openapi_url and self.docs_url:

2
fastapi/datastructures.py

@ -139,7 +139,7 @@ class UploadFile(StarletteUploadFile):
def __get_pydantic_json_schema__( def __get_pydantic_json_schema__(
cls, core_schema: Mapping[str, Any], handler: GetJsonSchemaHandler cls, core_schema: Mapping[str, Any], handler: GetJsonSchemaHandler
) -> dict[str, Any]: ) -> dict[str, Any]:
return {"type": "string", "format": "binary"} return {"type": "string", "contentMediaType": "application/octet-stream"}
@classmethod @classmethod
def __get_pydantic_core_schema__( def __get_pydantic_core_schema__(

32
fastapi/dependencies/utils.py

@ -1,7 +1,17 @@
import dataclasses import dataclasses
import inspect import inspect
import sys import sys
from collections.abc import Callable, Mapping, Sequence from collections.abc import (
AsyncGenerator,
AsyncIterable,
AsyncIterator,
Callable,
Generator,
Iterable,
Iterator,
Mapping,
Sequence,
)
from contextlib import AsyncExitStack, contextmanager from contextlib import AsyncExitStack, contextmanager
from copy import copy, deepcopy from copy import copy, deepcopy
from dataclasses import dataclass from dataclasses import dataclass
@ -251,6 +261,26 @@ def get_typed_return_annotation(call: Callable[..., Any]) -> Any:
return get_typed_annotation(annotation, globalns) return get_typed_annotation(annotation, globalns)
_STREAM_ORIGINS = {
AsyncIterable,
AsyncIterator,
AsyncGenerator,
Iterable,
Iterator,
Generator,
}
def get_stream_item_type(annotation: Any) -> Any | None:
origin = get_origin(annotation)
if origin is not None and origin in _STREAM_ORIGINS:
type_args = get_args(annotation)
if type_args:
return type_args[0]
return Any
return None
def get_dependant( def get_dependant(
*, *,
path: str, path: str,

18
fastapi/openapi/docs.py

@ -5,6 +5,20 @@ from annotated_doc import Doc
from fastapi.encoders import jsonable_encoder from fastapi.encoders import jsonable_encoder
from starlette.responses import HTMLResponse from starlette.responses import HTMLResponse
def _html_safe_json(value: Any) -> str:
"""Serialize a value to JSON with HTML special characters escaped.
This prevents injection when the JSON is embedded inside a <script> tag.
"""
return (
json.dumps(value)
.replace("<", "\\u003c")
.replace(">", "\\u003e")
.replace("&", "\\u0026")
)
swagger_ui_default_parameters: Annotated[ swagger_ui_default_parameters: Annotated[
dict[str, Any], dict[str, Any],
Doc( Doc(
@ -155,7 +169,7 @@ def get_swagger_ui_html(
""" """
for key, value in current_swagger_ui_parameters.items(): for key, value in current_swagger_ui_parameters.items():
html += f"{json.dumps(key)}: {json.dumps(jsonable_encoder(value))},\n" html += f"{_html_safe_json(key)}: {_html_safe_json(jsonable_encoder(value))},\n"
if oauth2_redirect_url: if oauth2_redirect_url:
html += f"oauth2RedirectUrl: window.location.origin + '{oauth2_redirect_url}'," html += f"oauth2RedirectUrl: window.location.origin + '{oauth2_redirect_url}',"
@ -169,7 +183,7 @@ def get_swagger_ui_html(
if init_oauth: if init_oauth:
html += f""" html += f"""
ui.initOAuth({json.dumps(jsonable_encoder(init_oauth))}) ui.initOAuth({_html_safe_json(jsonable_encoder(init_oauth))})
""" """
html += """ html += """

72
fastapi/openapi/utils.py

@ -29,6 +29,7 @@ from fastapi.openapi.constants import METHODS_WITH_BODY, REF_PREFIX
from fastapi.openapi.models import OpenAPI from fastapi.openapi.models import OpenAPI
from fastapi.params import Body, ParamTypes from fastapi.params import Body, ParamTypes
from fastapi.responses import Response from fastapi.responses import Response
from fastapi.sse import _SSE_EVENT_SCHEMA
from fastapi.types import ModelNameMap from fastapi.types import ModelNameMap
from fastapi.utils import ( from fastapi.utils import (
deep_dict_update, deep_dict_update,
@ -355,25 +356,60 @@ def get_openapi_path(
operation.setdefault("responses", {}).setdefault(status_code, {})[ operation.setdefault("responses", {}).setdefault(status_code, {})[
"description" "description"
] = route.response_description ] = route.response_description
if route_response_media_type and is_body_allowed_for_status_code( if is_body_allowed_for_status_code(route.status_code):
route.status_code # Check for JSONL streaming (generator endpoints)
): if route.is_json_stream:
response_schema = {"type": "string"} jsonl_content: dict[str, Any] = {}
if lenient_issubclass(current_response_class, JSONResponse): if route.stream_item_field:
if route.response_field: item_schema = get_schema_from_model_field(
response_schema = get_schema_from_model_field( field=route.stream_item_field,
field=route.response_field,
model_name_map=model_name_map, model_name_map=model_name_map,
field_mapping=field_mapping, field_mapping=field_mapping,
separate_input_output_schemas=separate_input_output_schemas, separate_input_output_schemas=separate_input_output_schemas,
) )
jsonl_content["itemSchema"] = item_schema
else: else:
response_schema = {} jsonl_content["itemSchema"] = {}
operation.setdefault("responses", {}).setdefault( operation.setdefault("responses", {}).setdefault(
status_code, {} status_code, {}
).setdefault("content", {}).setdefault(route_response_media_type, {})[ ).setdefault("content", {})["application/jsonl"] = jsonl_content
"schema" elif route.is_sse_stream:
] = response_schema 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):
if route.response_field:
response_schema = get_schema_from_model_field(
field=route.response_field,
model_name_map=model_name_map,
field_mapping=field_mapping,
separate_input_output_schemas=separate_input_output_schemas,
)
else:
response_schema = {}
operation.setdefault("responses", {}).setdefault(
status_code, {}
).setdefault("content", {}).setdefault(
route_response_media_type, {}
)["schema"] = response_schema
if route.responses: if route.responses:
operation_responses = operation.setdefault("responses", {}) operation_responses = operation.setdefault("responses", {})
for ( for (
@ -453,9 +489,9 @@ def get_fields_from_routes(
request_fields_from_routes: list[ModelField] = [] request_fields_from_routes: list[ModelField] = []
callback_flat_models: list[ModelField] = [] callback_flat_models: list[ModelField] = []
for route in routes: for route in routes:
if getattr(route, "include_in_schema", None) and isinstance( if not isinstance(route, routing.APIRoute):
route, routing.APIRoute continue
): if route.include_in_schema:
if route.body_field: if route.body_field:
assert isinstance(route.body_field, ModelField), ( assert isinstance(route.body_field, ModelField), (
"A request body must be a Pydantic Field" "A request body must be a Pydantic Field"
@ -465,6 +501,8 @@ def get_fields_from_routes(
responses_from_routes.append(route.response_field) responses_from_routes.append(route.response_field)
if route.response_fields: if route.response_fields:
responses_from_routes.extend(route.response_fields.values()) responses_from_routes.extend(route.response_fields.values())
if route.stream_item_field:
responses_from_routes.append(route.stream_item_field)
if route.callbacks: if route.callbacks:
callback_flat_models.extend(get_fields_from_routes(route.callbacks)) callback_flat_models.extend(get_fields_from_routes(route.callbacks))
params = get_flat_params(route.dependant) params = get_flat_params(route.dependant)

53
fastapi/responses.py

@ -1,5 +1,7 @@
from typing import Any 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 FileResponse as FileResponse # noqa
from starlette.responses import HTMLResponse as HTMLResponse # noqa from starlette.responses import HTMLResponse as HTMLResponse # noqa
from starlette.responses import JSONResponse as JSONResponse # noqa from starlette.responses import JSONResponse as JSONResponse # noqa
@ -7,6 +9,7 @@ from starlette.responses import PlainTextResponse as PlainTextResponse # noqa
from starlette.responses import RedirectResponse as RedirectResponse # noqa from starlette.responses import RedirectResponse as RedirectResponse # noqa
from starlette.responses import Response as Response # noqa from starlette.responses import Response as Response # noqa
from starlette.responses import StreamingResponse as StreamingResponse # noqa from starlette.responses import StreamingResponse as StreamingResponse # noqa
from typing_extensions import deprecated
try: try:
import ujson import ujson
@ -20,12 +23,29 @@ except ImportError: # pragma: nocover
orjson = None # type: ignore orjson = None # type: ignore
@deprecated(
"UJSONResponse is deprecated, FastAPI now serializes data directly to JSON "
"bytes via Pydantic when a return type or response model is set, which is "
"faster and doesn't need a custom response class. Read more in the FastAPI "
"docs: https://fastapi.tiangolo.com/advanced/custom-response/#orjson-or-response-model "
"and https://fastapi.tiangolo.com/tutorial/response-model/",
category=FastAPIDeprecationWarning,
stacklevel=2,
)
class UJSONResponse(JSONResponse): class UJSONResponse(JSONResponse):
""" """JSON response using the ujson library to serialize data to JSON.
JSON response using the high-performance ujson library to serialize data to JSON.
**Deprecated**: `UJSONResponse` is deprecated. FastAPI now serializes data
directly to JSON bytes via Pydantic when a return type or response model is
set, which is faster and doesn't need a custom response class.
Read more in the
[FastAPI docs for Custom Response](https://fastapi.tiangolo.com/advanced/custom-response/#orjson-or-response-model)
and the
[FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/).
Read more about it in the **Note**: `ujson` is not included with FastAPI and must be installed
[FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/). separately, e.g. `pip install ujson`.
""" """
def render(self, content: Any) -> bytes: def render(self, content: Any) -> bytes:
@ -33,12 +53,29 @@ class UJSONResponse(JSONResponse):
return ujson.dumps(content, ensure_ascii=False).encode("utf-8") return ujson.dumps(content, ensure_ascii=False).encode("utf-8")
@deprecated(
"ORJSONResponse is deprecated, FastAPI now serializes data directly to JSON "
"bytes via Pydantic when a return type or response model is set, which is "
"faster and doesn't need a custom response class. Read more in the FastAPI "
"docs: https://fastapi.tiangolo.com/advanced/custom-response/#orjson-or-response-model "
"and https://fastapi.tiangolo.com/tutorial/response-model/",
category=FastAPIDeprecationWarning,
stacklevel=2,
)
class ORJSONResponse(JSONResponse): class ORJSONResponse(JSONResponse):
""" """JSON response using the orjson library to serialize data to JSON.
JSON response using the high-performance orjson library to serialize data to JSON.
**Deprecated**: `ORJSONResponse` is deprecated. FastAPI now serializes data
directly to JSON bytes via Pydantic when a return type or response model is
set, which is faster and doesn't need a custom response class.
Read more in the
[FastAPI docs for Custom Response](https://fastapi.tiangolo.com/advanced/custom-response/#orjson-or-response-model)
and the
[FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/).
Read more about it in the **Note**: `orjson` is not included with FastAPI and must be installed
[FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/). separately, e.g. `pip install orjson`.
""" """
def render(self, content: Any) -> bytes: def render(self, content: Any) -> bytes:

408
fastapi/routing.py

@ -11,6 +11,7 @@ from collections.abc import (
Collection, Collection,
Coroutine, Coroutine,
Generator, Generator,
Iterator,
Mapping, Mapping,
Sequence, Sequence,
) )
@ -27,7 +28,9 @@ from typing import (
TypeVar, TypeVar,
) )
import anyio
from annotated_doc import Doc from annotated_doc import Doc
from anyio.abc import ObjectReceiveStream
from fastapi import params from fastapi import params
from fastapi._compat import ( from fastapi._compat import (
ModelField, ModelField,
@ -42,6 +45,7 @@ from fastapi.dependencies.utils import (
get_dependant, get_dependant,
get_flat_dependant, get_flat_dependant,
get_parameterless_sub_dependant, get_parameterless_sub_dependant,
get_stream_item_type,
get_typed_return_annotation, get_typed_return_annotation,
solve_dependencies, solve_dependencies,
) )
@ -53,6 +57,13 @@ from fastapi.exceptions import (
ResponseValidationError, ResponseValidationError,
WebSocketRequestValidationError, WebSocketRequestValidationError,
) )
from fastapi.sse import (
_PING_INTERVAL,
KEEPALIVE_COMMENT,
EventSourceResponse,
ServerSentEvent,
format_sse_event,
)
from fastapi.types import DecoratedCallable, IncEx from fastapi.types import DecoratedCallable, IncEx
from fastapi.utils import ( from fastapi.utils import (
create_model_field, create_model_field,
@ -63,10 +74,10 @@ from fastapi.utils import (
from starlette import routing from starlette import routing
from starlette._exception_handler import wrap_app_handling_exceptions from starlette._exception_handler import wrap_app_handling_exceptions
from starlette._utils import is_async_callable 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.exceptions import HTTPException
from starlette.requests import Request from starlette.requests import Request
from starlette.responses import JSONResponse, Response from starlette.responses import JSONResponse, Response, StreamingResponse
from starlette.routing import ( from starlette.routing import (
BaseRoute, BaseRoute,
Match, Match,
@ -271,6 +282,7 @@ async def serialize_response(
exclude_none: bool = False, exclude_none: bool = False,
is_coroutine: bool = True, is_coroutine: bool = True,
endpoint_ctx: EndpointContext | None = None, endpoint_ctx: EndpointContext | None = None,
dump_json: bool = False,
) -> Any: ) -> Any:
if field: if field:
if is_coroutine: if is_coroutine:
@ -286,8 +298,8 @@ async def serialize_response(
body=response_content, body=response_content,
endpoint_ctx=ctx, endpoint_ctx=ctx,
) )
serializer = field.serialize_json if dump_json else field.serialize
return field.serialize( return serializer(
value, value,
include=include, include=include,
exclude=exclude, exclude=exclude,
@ -314,6 +326,24 @@ async def run_endpoint_function(
return await run_in_threadpool(dependant.call, **values) return await run_in_threadpool(dependant.call, **values)
def _build_response_args(
*, status_code: int | None, solved_result: Any
) -> dict[str, Any]:
response_args: dict[str, Any] = {
"background": solved_result.background_tasks,
}
# If status_code was set, use it, otherwise use the default from the
# response class, in the case of redirect it's 307
current_status_code = (
status_code if status_code else solved_result.response.status_code
)
if current_status_code is not None:
response_args["status_code"] = current_status_code
if solved_result.response.status_code:
response_args["status_code"] = solved_result.response.status_code
return response_args
def get_request_handler( def get_request_handler(
dependant: Dependant, dependant: Dependant,
body_field: ModelField | None = None, body_field: ModelField | None = None,
@ -328,6 +358,9 @@ def get_request_handler(
response_model_exclude_none: bool = False, response_model_exclude_none: bool = False,
dependency_overrides_provider: Any | None = None, dependency_overrides_provider: Any | None = None,
embed_body_fields: bool = False, embed_body_fields: bool = False,
strict_content_type: bool | DefaultPlaceholder = Default(True),
stream_item_field: ModelField | None = None,
is_json_stream: bool = False,
) -> Callable[[Request], Coroutine[Any, Any, Response]]: ) -> Callable[[Request], Coroutine[Any, Any, Response]]:
assert dependant.call is not None, "dependant.call must be a function" assert dependant.call is not None, "dependant.call must be a function"
is_coroutine = dependant.is_coroutine_callable is_coroutine = dependant.is_coroutine_callable
@ -336,6 +369,11 @@ def get_request_handler(
actual_response_class: type[Response] = response_class.value actual_response_class: type[Response] = response_class.value
else: else:
actual_response_class = response_class 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:
actual_strict_content_type = strict_content_type
async def app(request: Request) -> Response: async def app(request: Request) -> Response:
response: Response | None = None response: Response | None = None
@ -369,7 +407,8 @@ def get_request_handler(
json_body: Any = Undefined json_body: Any = Undefined
content_type_value = request.headers.get("content-type") content_type_value = request.headers.get("content-type")
if not content_type_value: if not content_type_value:
json_body = await request.json() if not actual_strict_content_type:
json_body = await request.json()
else: else:
message = email.message.Message() message = email.message.Message()
message["content-type"] = content_type_value message["content-type"] = content_type_value
@ -420,45 +459,259 @@ def get_request_handler(
embed_body_fields=embed_body_fields, embed_body_fields=embed_body_fields,
) )
errors = solved_result.errors errors = solved_result.errors
assert dependant.call # For types
if not errors: if not errors:
raw_response = await run_endpoint_function( # Shared serializer for stream items (JSONL and SSE).
dependant=dependant, # Validates against stream_item_field when set, then
values=solved_result.values, # serializes to JSON bytes.
is_coroutine=is_coroutine, def _serialize_data(data: Any) -> bytes:
) if stream_item_field:
if isinstance(raw_response, Response): value, errors_ = stream_item_field.validate(
if raw_response.background is None: data, {}, loc=("response",)
raw_response.background = solved_result.background_tasks )
response = raw_response if errors_:
else: ctx = endpoint_ctx or EndpointContext()
response_args: dict[str, Any] = { raise ResponseValidationError(
"background": solved_result.background_tasks errors=errors_,
} body=data,
# If status_code was set, use it, otherwise use the default from the endpoint_ctx=ctx,
# response class, in the case of redirect it's 307 )
current_status_code = ( return stream_item_field.serialize_json(
status_code if status_code else solved_result.response.status_code 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()
) )
if current_status_code is not None: # Ensure the receive stream is closed when the exit stack
response_args["status_code"] = current_status_code # unwinds, preventing ResourceWarning from __del__.
if solved_result.response.status_code: async_exit_stack.push_async_callback(sse_receive_stream.aclose)
response_args["status_code"] = solved_result.response.status_code
content = await serialize_response( async def _sse_with_checkpoints(
field=response_field, stream: ObjectReceiveStream[bytes],
response_content=raw_response, ) -> AsyncIterator[bytes]:
include=response_model_include, async for data in stream:
exclude=response_model_exclude, yield data
by_alias=response_model_by_alias, # Guarantee a checkpoint so cancellation can be
exclude_unset=response_model_exclude_unset, # delivered even when the producer is faster than
exclude_defaults=response_model_exclude_defaults, # the consumer and receive() never suspends.
exclude_none=response_model_exclude_none, await anyio.sleep(0)
is_coroutine=is_coroutine,
endpoint_ctx=endpoint_ctx, 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:
return _serialize_data(item) + b"\n"
if dependant.is_async_gen_callable:
async def _async_stream_jsonl() -> AsyncIterator[bytes]:
async for item in gen:
yield _serialize_item(item)
# To allow for cancellation to trigger
# Ref: https://github.com/fastapi/fastapi/issues/14680
await anyio.sleep(0)
jsonl_stream_content: AsyncIterator[bytes] | Iterator[bytes] = (
_async_stream_jsonl()
)
else:
def _sync_stream_jsonl() -> Iterator[bytes]:
for item in gen:
yield _serialize_item(item)
jsonl_stream_content = _sync_stream_jsonl()
response = StreamingResponse(
jsonl_stream_content,
media_type="application/jsonl",
background=solved_result.background_tasks,
)
response.headers.raw.extend(solved_result.response.headers.raw)
elif dependant.is_async_gen_callable or dependant.is_gen_callable:
# Raw streaming with explicit response_class (e.g. StreamingResponse)
gen = dependant.call(**solved_result.values)
if dependant.is_async_gen_callable:
async def _async_stream_raw(
async_gen: AsyncIterator[Any],
) -> AsyncIterator[Any]:
async for chunk in async_gen:
yield chunk
# To allow for cancellation to trigger
# Ref: https://github.com/fastapi/fastapi/issues/14680
await anyio.sleep(0)
gen = _async_stream_raw(gen)
response_args = _build_response_args(
status_code=status_code, solved_result=solved_result
) )
response = actual_response_class(content, **response_args) response = actual_response_class(content=gen, **response_args)
if not is_body_allowed_for_status_code(response.status_code):
response.body = b""
response.headers.raw.extend(solved_result.response.headers.raw) response.headers.raw.extend(solved_result.response.headers.raw)
else:
raw_response = await run_endpoint_function(
dependant=dependant,
values=solved_result.values,
is_coroutine=is_coroutine,
)
if isinstance(raw_response, Response):
if raw_response.background is None:
raw_response.background = solved_result.background_tasks
response = raw_response
else:
response_args = _build_response_args(
status_code=status_code, solved_result=solved_result
)
# Use the fast path (dump_json) when no custom response
# class was set and a response field with a TypeAdapter
# exists. Serializes directly to JSON bytes via Pydantic's
# Rust core, skipping the intermediate Python dict +
# json.dumps() step.
use_dump_json = response_field is not None and isinstance(
response_class, DefaultPlaceholder
)
content = await serialize_response(
field=response_field,
response_content=raw_response,
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,
is_coroutine=is_coroutine,
endpoint_ctx=endpoint_ctx,
dump_json=use_dump_json,
)
if use_dump_json:
response = Response(
content=content,
media_type="application/json",
**response_args,
)
else:
response = actual_response_class(content, **response_args)
if not is_body_allowed_for_status_code(response.status_code):
response.body = b""
response.headers.raw.extend(solved_result.response.headers.raw)
if errors: if errors:
validation_error = RequestValidationError( validation_error = RequestValidationError(
errors, body=body, endpoint_ctx=endpoint_ctx errors, body=body, endpoint_ctx=endpoint_ctx
@ -582,15 +835,32 @@ class APIRoute(routing.Route):
openapi_extra: dict[str, Any] | None = None, openapi_extra: dict[str, Any] | None = None,
generate_unique_id_function: Callable[["APIRoute"], str] generate_unique_id_function: Callable[["APIRoute"], str]
| DefaultPlaceholder = Default(generate_unique_id), | DefaultPlaceholder = Default(generate_unique_id),
strict_content_type: bool | DefaultPlaceholder = Default(True),
) -> None: ) -> None:
self.path = path self.path = path
self.endpoint = endpoint self.endpoint = endpoint
self.stream_item_type: Any | None = None
if isinstance(response_model, DefaultPlaceholder): if isinstance(response_model, DefaultPlaceholder):
return_annotation = get_typed_return_annotation(endpoint) return_annotation = get_typed_return_annotation(endpoint)
if lenient_issubclass(return_annotation, Response): if lenient_issubclass(return_annotation, Response):
response_model = None response_model = None
else: else:
response_model = return_annotation stream_item = get_stream_item_type(return_annotation)
if stream_item is not None:
# 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:
response_model = return_annotation
self.response_model = response_model self.response_model = response_model
self.summary = summary self.summary = summary
self.response_description = response_description self.response_description = response_description
@ -608,6 +878,7 @@ class APIRoute(routing.Route):
self.callbacks = callbacks self.callbacks = callbacks
self.openapi_extra = openapi_extra self.openapi_extra = openapi_extra
self.generate_unique_id_function = generate_unique_id_function self.generate_unique_id_function = generate_unique_id_function
self.strict_content_type = strict_content_type
self.tags = tags or [] self.tags = tags or []
self.responses = responses or {} self.responses = responses or {}
self.name = get_name(endpoint) if name is None else name self.name = get_name(endpoint) if name is None else name
@ -638,6 +909,15 @@ class APIRoute(routing.Route):
) )
else: else:
self.response_field = None # type: ignore self.response_field = None # type: ignore
if self.stream_item_type:
stream_item_name = "StreamItem_" + self.unique_id
self.stream_item_field: ModelField | None = create_model_field(
name=stream_item_name,
type_=self.stream_item_type,
mode="serialization",
)
else:
self.stream_item_field = None
self.dependencies = list(dependencies or []) self.dependencies = list(dependencies or [])
self.description = description or inspect.cleandoc(self.endpoint.__doc__ or "") self.description = description or inspect.cleandoc(self.endpoint.__doc__ or "")
# if a "form feed" character (page break) is found in the description text, # if a "form feed" character (page break) is found in the description text,
@ -679,6 +959,16 @@ class APIRoute(routing.Route):
name=self.unique_id, name=self.unique_id,
embed_body_fields=self._embed_body_fields, embed_body_fields=self._embed_body_fields,
) )
# 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()) self.app = request_response(self.get_route_handler())
def get_route_handler(self) -> Callable[[Request], Coroutine[Any, Any, Response]]: def get_route_handler(self) -> Callable[[Request], Coroutine[Any, Any, Response]]:
@ -696,6 +986,9 @@ class APIRoute(routing.Route):
response_model_exclude_none=self.response_model_exclude_none, response_model_exclude_none=self.response_model_exclude_none,
dependency_overrides_provider=self.dependency_overrides_provider, dependency_overrides_provider=self.dependency_overrides_provider,
embed_body_fields=self._embed_body_fields, embed_body_fields=self._embed_body_fields,
strict_content_type=self.strict_content_type,
stream_item_field=self.stream_item_field,
is_json_stream=self.is_json_stream,
) )
def matches(self, scope: Scope) -> tuple[Match, Scope]: def matches(self, scope: Scope) -> tuple[Match, Scope]:
@ -946,6 +1239,29 @@ class APIRouter(routing.Router):
""" """
), ),
] = Default(generate_unique_id), ] = Default(generate_unique_id),
strict_content_type: Annotated[
bool,
Doc(
"""
Enable strict checking for request Content-Type headers.
When `True` (the default), requests with a body that do not include
a `Content-Type` header will **not** be parsed as JSON.
This prevents potential cross-site request forgery (CSRF) attacks
that exploit the browser's ability to send requests without a
Content-Type header, bypassing CORS preflight checks. In particular
applicable for apps that need to be run locally (in localhost).
When `False`, requests without a `Content-Type` header will have
their body parsed as JSON, which maintains compatibility with
certain clients that don't send `Content-Type` headers.
Read more about it in the
[FastAPI docs for Strict Content-Type](https://fastapi.tiangolo.com/advanced/strict-content-type/).
"""
),
] = Default(True),
) -> None: ) -> None:
# Determine the lifespan context to use # Determine the lifespan context to use
if lifespan is None: if lifespan is None:
@ -992,6 +1308,7 @@ class APIRouter(routing.Router):
self.route_class = route_class self.route_class = route_class
self.default_response_class = default_response_class self.default_response_class = default_response_class
self.generate_unique_id_function = generate_unique_id_function self.generate_unique_id_function = generate_unique_id_function
self.strict_content_type = strict_content_type
def route( def route(
self, self,
@ -1042,6 +1359,7 @@ class APIRouter(routing.Router):
openapi_extra: dict[str, Any] | None = None, openapi_extra: dict[str, Any] | None = None,
generate_unique_id_function: Callable[[APIRoute], str] generate_unique_id_function: Callable[[APIRoute], str]
| DefaultPlaceholder = Default(generate_unique_id), | DefaultPlaceholder = Default(generate_unique_id),
strict_content_type: bool | DefaultPlaceholder = Default(True),
) -> None: ) -> None:
route_class = route_class_override or self.route_class route_class = route_class_override or self.route_class
responses = responses or {} responses = responses or {}
@ -1088,6 +1406,9 @@ class APIRouter(routing.Router):
callbacks=current_callbacks, callbacks=current_callbacks,
openapi_extra=openapi_extra, openapi_extra=openapi_extra,
generate_unique_id_function=current_generate_unique_id, generate_unique_id_function=current_generate_unique_id,
strict_content_type=get_value_or_default(
strict_content_type, self.strict_content_type
),
) )
self.routes.append(route) self.routes.append(route)
@ -1463,6 +1784,11 @@ class APIRouter(routing.Router):
callbacks=current_callbacks, callbacks=current_callbacks,
openapi_extra=route.openapi_extra, openapi_extra=route.openapi_extra,
generate_unique_id_function=current_generate_unique_id, generate_unique_id_function=current_generate_unique_id,
strict_content_type=get_value_or_default(
route.strict_content_type,
router.strict_content_type,
self.strict_content_type,
),
) )
elif isinstance(route, routing.Route): elif isinstance(route, routing.Route):
methods = list(route.methods or []) methods = list(route.methods or [])

222
fastapi/sse.py

@ -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

40
pdm_build.py

@ -1,40 +0,0 @@
import os
from typing import Any
from pdm.backend.hooks import Context
TIANGOLO_BUILD_PACKAGE = os.getenv("TIANGOLO_BUILD_PACKAGE")
def pdm_build_initialize(context: Context) -> None:
metadata = context.config.metadata
# Get main version
version = metadata["version"]
# Get custom config for the current package, from the env var
all_configs_config: dict[str, Any] = context.config.data["tool"]["tiangolo"][
"_internal-slim-build"
]["packages"]
if TIANGOLO_BUILD_PACKAGE not in all_configs_config:
return
config = all_configs_config[TIANGOLO_BUILD_PACKAGE]
project_config: dict[str, Any] = config["project"]
# Override main [project] configs with custom configs for this package
for key, value in project_config.items():
metadata[key] = value
# Get custom build config for the current package
build_config: dict[str, Any] = (
config.get("tool", {}).get("pdm", {}).get("build", {})
)
# Override PDM build config with custom build config for this package
for key, value in build_config.items():
context.config.build_config[key] = value
# Get main dependencies
dependencies: list[str] = metadata.get("dependencies", [])
# Sync versions in dependencies
new_dependencies = []
for dep in dependencies:
new_dep = f"{dep}>={version}"
new_dependencies.append(new_dep)
metadata["dependencies"] = new_dependencies

265
pyproject.toml

@ -42,7 +42,7 @@ classifiers = [
"Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP",
] ]
dependencies = [ dependencies = [
"starlette>=0.40.0,<1.0.0", "starlette>=0.46.0",
"pydantic>=2.7.0", "pydantic>=2.7.0",
"typing-extensions>=4.8.0", "typing-extensions>=4.8.0",
"typing-inspection>=0.4.2", "typing-inspection>=0.4.2",
@ -57,7 +57,6 @@ Issues = "https://github.com/fastapi/fastapi/issues"
Changelog = "https://fastapi.tiangolo.com/release-notes/" Changelog = "https://fastapi.tiangolo.com/release-notes/"
[project.optional-dependencies] [project.optional-dependencies]
standard = [ standard = [
"fastapi-cli[standard] >=0.0.8", "fastapi-cli[standard] >=0.0.8",
# For the test client # For the test client
@ -106,10 +105,6 @@ all = [
"itsdangerous >=1.1.0", "itsdangerous >=1.1.0",
# For Starlette's schema generation, would not be used with FastAPI # For Starlette's schema generation, would not be used with FastAPI
"pyyaml >=5.3.1", "pyyaml >=5.3.1",
# For UJSONResponse
"ujson >=5.8.0",
# For ORJSONResponse
"orjson >=3.9.3",
# To validate email fields # To validate email fields
"email-validator >=2.0.0", "email-validator >=2.0.0",
# Uvicorn with uvloop # Uvicorn with uvloop
@ -152,6 +147,10 @@ docs = [
docs-tests = [ docs-tests = [
"httpx >=0.23.0,<1.0.0", "httpx >=0.23.0,<1.0.0",
"ruff >=0.14.14", "ruff >=0.14.14",
# For UJSONResponse
"ujson >=5.8.0",
# For ORJSONResponse
"orjson >=3.9.3",
] ]
github-actions = [ github-actions = [
"httpx >=0.27.0,<1.0.0", "httpx >=0.27.0,<1.0.0",
@ -164,14 +163,14 @@ github-actions = [
tests = [ tests = [
{ include-group = "docs-tests" }, { include-group = "docs-tests" },
"anyio[trio] >=3.2.1,<5.0.0", "anyio[trio] >=3.2.1,<5.0.0",
"coverage[toml] >=6.5.0,<8.0", "coverage[toml] >=7.13,<8.0",
"dirty-equals >=0.9.0", "dirty-equals >=0.9.0",
"flask >=3.0.0,<4.0.0", "flask >=3.0.0,<4.0.0",
"inline-snapshot >=0.21.1", "inline-snapshot >=0.21.1",
"mypy >=1.14.1", "mypy >=1.14.1",
"pwdlib[argon2] >=0.2.1", "pwdlib[argon2] >=0.2.1",
"pyjwt >=2.9.0", "pyjwt >=2.9.0",
"pytest >=7.1.3,<9.0.0", "pytest >=9.0.0",
"pytest-codspeed >=4.2.0", "pytest-codspeed >=4.2.0",
"pyyaml >=5.3.1,<7.0.0", "pyyaml >=5.3.1,<7.0.0",
"sqlmodel >=0.0.31", "sqlmodel >=0.0.31",
@ -179,6 +178,10 @@ tests = [
"types-orjson >=3.6.2", "types-orjson >=3.6.2",
"types-ujson >=5.10.0.20240515", "types-ujson >=5.10.0.20240515",
"a2wsgi >=1.9.0,<=2.0.0", "a2wsgi >=1.9.0,<=2.0.0",
"pytest-xdist[psutil]>=2.5.0",
"pytest-cov>=4.0.0",
"pytest-sugar>=1.0.0",
"pytest-timeout>=2.4.0",
] ]
translations = [ translations = [
"gitpython >=3.1.46", "gitpython >=3.1.46",
@ -199,32 +202,6 @@ source-includes = [
"docs/en/docs/img/favicon.png", "docs/en/docs/img/favicon.png",
] ]
[tool.tiangolo._internal-slim-build.packages.fastapi-slim.project]
name = "fastapi-slim"
readme = "fastapi-slim/README.md"
dependencies = [
"fastapi",
]
optional-dependencies = {}
scripts = {}
[tool.tiangolo._internal-slim-build.packages.fastapi-slim.tool.pdm.build]
# excludes needs to explicitly exclude the top level python packages,
# otherwise PDM includes them by default
# A "*" glob pattern can't be used here because in PDM internals, the patterns are put
# in a set (unordered, order varies) and each excluded file is assigned one of the
# glob patterns that matches, as the set is unordered, the matched pattern could be "*"
# independent of the order here. And then the internal code would give it a lower score
# than the one for a default included file.
# By not using "*" and explicitly excluding the top level packages, they get a higher
# score than the default inclusion
excludes = ["fastapi", "tests", "pdm_build.py"]
# source-includes needs to explicitly define some value because PDM will check the
# truthy value of the list, and if empty, will include some defaults, including "tests",
# an empty string doesn't match anything, but makes the list truthy, so that PDM
# doesn't override it during the build.
source-includes = [""]
[tool.mypy] [tool.mypy]
plugins = ["pydantic.mypy"] plugins = ["pydantic.mypy"]
strict = true strict = true
@ -245,26 +222,18 @@ disallow_incomplete_defs = false
disallow_untyped_defs = false disallow_untyped_defs = false
disallow_untyped_calls = false disallow_untyped_calls = false
[tool.pytest.ini_options] [tool.pytest]
minversion = "9.0"
addopts = [ addopts = [
"--strict-config", "--strict-config",
"--strict-markers", "--strict-markers",
"--ignore=docs_src", "--ignore=docs_src",
] ]
xfail_strict = true strict_xfail = true
junit_family = "xunit2"
filterwarnings = [ filterwarnings = [
"error", "error",
# see https://trio.readthedocs.io/en/stable/history.html#trio-0-22-0-2022-09-28
"ignore:You seem to already have a custom.*:RuntimeWarning:trio",
# TODO: remove after upgrading SQLAlchemy to a version that includes the following changes
# https://github.com/sqlalchemy/sqlalchemy/commit/59521abcc0676e936b31a523bd968fc157fef0c2
'ignore:datetime\.datetime\.utcfromtimestamp\(\) is deprecated and scheduled for removal in a future version\..*:DeprecationWarning:sqlalchemy',
# Trio 24.1.0 raises a warning from attrs
# Ref: https://github.com/python-trio/trio/pull/3054
# Remove once there's a new version of Trio
'ignore:The `hash` argument is deprecated*:DeprecationWarning:trio',
] ]
timeout = "20"
[tool.coverage.run] [tool.coverage.run]
parallel = true parallel = true
@ -276,11 +245,10 @@ source = [
] ]
relative_files = true relative_files = true
context = '${CONTEXT}' context = '${CONTEXT}'
dynamic_context = "test_function"
omit = [ omit = [
"tests/benchmarks/*",
"docs_src/response_model/tutorial003_04_py39.py", "docs_src/response_model/tutorial003_04_py39.py",
"docs_src/response_model/tutorial003_04_py310.py", "docs_src/response_model/tutorial003_04_py310.py",
"docs_src/dependencies/tutorial008_an_py39.py", # difficult to mock
"docs_src/dependencies/tutorial013_an_py310.py", # temporary code example? "docs_src/dependencies/tutorial013_an_py310.py", # temporary code example?
"docs_src/dependencies/tutorial014_an_py310.py", # temporary code example? "docs_src/dependencies/tutorial014_an_py310.py", # temporary code example?
# Pydantic v1 migration, no longer tested # Pydantic v1 migration, no longer tested
@ -288,202 +256,6 @@ omit = [
"docs_src/pydantic_v1_in_v2/tutorial002_an_py310.py", "docs_src/pydantic_v1_in_v2/tutorial002_an_py310.py",
"docs_src/pydantic_v1_in_v2/tutorial003_an_py310.py", "docs_src/pydantic_v1_in_v2/tutorial003_an_py310.py",
"docs_src/pydantic_v1_in_v2/tutorial004_an_py310.py", "docs_src/pydantic_v1_in_v2/tutorial004_an_py310.py",
# TODO: remove all the ignores below when all translations use the new Python 3.10 files
"docs_src/additional_responses/tutorial001_py39.py",
"docs_src/additional_responses/tutorial003_py39.py",
"docs_src/advanced_middleware/tutorial001_py39.py",
"docs_src/advanced_middleware/tutorial002_py39.py",
"docs_src/advanced_middleware/tutorial003_py39.py",
"docs_src/app_testing/app_a_py39/main.py",
"docs_src/app_testing/app_a_py39/test_main.py",
"docs_src/app_testing/tutorial001_py39.py",
"docs_src/app_testing/tutorial002_py39.py",
"docs_src/app_testing/tutorial003_py39.py",
"docs_src/app_testing/tutorial004_py39.py",
"docs_src/async_tests/app_a_py39/main.py",
"docs_src/async_tests/app_a_py39/test_main.py",
"docs_src/authentication_error_status_code/tutorial001_an_py39.py",
"docs_src/background_tasks/tutorial001_py39.py",
"docs_src/behind_a_proxy/tutorial001_01_py39.py",
"docs_src/behind_a_proxy/tutorial001_py39.py",
"docs_src/behind_a_proxy/tutorial002_py39.py",
"docs_src/behind_a_proxy/tutorial003_py39.py",
"docs_src/behind_a_proxy/tutorial004_py39.py",
"docs_src/bigger_applications/app_an_py39/dependencies.py",
"docs_src/bigger_applications/app_an_py39/internal/admin.py",
"docs_src/bigger_applications/app_an_py39/main.py",
"docs_src/bigger_applications/app_an_py39/routers/items.py",
"docs_src/bigger_applications/app_an_py39/routers/users.py",
"docs_src/bigger_applications/app_py39/dependencies.py",
"docs_src/bigger_applications/app_py39/main.py",
"docs_src/body_nested_models/tutorial008_py39.py",
"docs_src/body_nested_models/tutorial009_py39.py",
"docs_src/conditional_openapi/tutorial001_py39.py",
"docs_src/configure_swagger_ui/tutorial001_py39.py",
"docs_src/configure_swagger_ui/tutorial002_py39.py",
"docs_src/configure_swagger_ui/tutorial003_py39.py",
"docs_src/cors/tutorial001_py39.py",
"docs_src/custom_docs_ui/tutorial001_py39.py",
"docs_src/custom_docs_ui/tutorial002_py39.py",
"docs_src/custom_response/tutorial001_py39.py",
"docs_src/custom_response/tutorial001b_py39.py",
"docs_src/custom_response/tutorial002_py39.py",
"docs_src/custom_response/tutorial003_py39.py",
"docs_src/custom_response/tutorial004_py39.py",
"docs_src/custom_response/tutorial005_py39.py",
"docs_src/custom_response/tutorial006_py39.py",
"docs_src/custom_response/tutorial006b_py39.py",
"docs_src/custom_response/tutorial006c_py39.py",
"docs_src/custom_response/tutorial007_py39.py",
"docs_src/custom_response/tutorial008_py39.py",
"docs_src/custom_response/tutorial009_py39.py",
"docs_src/custom_response/tutorial009b_py39.py",
"docs_src/custom_response/tutorial009c_py39.py",
"docs_src/custom_response/tutorial010_py39.py",
"docs_src/debugging/tutorial001_py39.py",
"docs_src/dependencies/tutorial006_an_py39.py",
"docs_src/dependencies/tutorial006_py39.py",
"docs_src/dependencies/tutorial007_py39.py",
"docs_src/dependencies/tutorial008_py39.py",
"docs_src/dependencies/tutorial008b_an_py39.py",
"docs_src/dependencies/tutorial008b_py39.py",
"docs_src/dependencies/tutorial008c_an_py39.py",
"docs_src/dependencies/tutorial008c_py39.py",
"docs_src/dependencies/tutorial008d_an_py39.py",
"docs_src/dependencies/tutorial008d_py39.py",
"docs_src/dependencies/tutorial008e_an_py39.py",
"docs_src/dependencies/tutorial008e_py39.py",
"docs_src/dependencies/tutorial010_py39.py",
"docs_src/dependencies/tutorial011_an_py39.py",
"docs_src/dependencies/tutorial011_py39.py",
"docs_src/dependencies/tutorial012_an_py39.py",
"docs_src/dependencies/tutorial012_py39.py",
"docs_src/events/tutorial001_py39.py",
"docs_src/events/tutorial002_py39.py",
"docs_src/events/tutorial003_py39.py",
"docs_src/extending_openapi/tutorial001_py39.py",
"docs_src/extra_models/tutorial004_py39.py",
"docs_src/extra_models/tutorial005_py39.py",
"docs_src/first_steps/tutorial001_py39.py",
"docs_src/first_steps/tutorial003_py39.py",
"docs_src/generate_clients/tutorial001_py39.py",
"docs_src/generate_clients/tutorial002_py39.py",
"docs_src/generate_clients/tutorial003_py39.py",
"docs_src/generate_clients/tutorial004_py39.py",
"docs_src/graphql_/tutorial001_py39.py",
"docs_src/handling_errors/tutorial001_py39.py",
"docs_src/handling_errors/tutorial002_py39.py",
"docs_src/handling_errors/tutorial003_py39.py",
"docs_src/handling_errors/tutorial004_py39.py",
"docs_src/handling_errors/tutorial005_py39.py",
"docs_src/handling_errors/tutorial006_py39.py",
"docs_src/metadata/tutorial001_1_py39.py",
"docs_src/metadata/tutorial001_py39.py",
"docs_src/metadata/tutorial002_py39.py",
"docs_src/metadata/tutorial003_py39.py",
"docs_src/metadata/tutorial004_py39.py",
"docs_src/middleware/tutorial001_py39.py",
"docs_src/openapi_webhooks/tutorial001_py39.py",
"docs_src/path_operation_advanced_configuration/tutorial001_py39.py",
"docs_src/path_operation_advanced_configuration/tutorial002_py39.py",
"docs_src/path_operation_advanced_configuration/tutorial003_py39.py",
"docs_src/path_operation_advanced_configuration/tutorial005_py39.py",
"docs_src/path_operation_advanced_configuration/tutorial006_py39.py",
"docs_src/path_operation_advanced_configuration/tutorial007_py39.py",
"docs_src/path_operation_configuration/tutorial002b_py39.py",
"docs_src/path_operation_configuration/tutorial006_py39.py",
"docs_src/path_params/tutorial001_py39.py",
"docs_src/path_params/tutorial002_py39.py",
"docs_src/path_params/tutorial003_py39.py",
"docs_src/path_params/tutorial003b_py39.py",
"docs_src/path_params/tutorial004_py39.py",
"docs_src/path_params/tutorial005_py39.py",
"docs_src/path_params_numeric_validations/tutorial002_an_py39.py",
"docs_src/path_params_numeric_validations/tutorial002_py39.py",
"docs_src/path_params_numeric_validations/tutorial003_an_py39.py",
"docs_src/path_params_numeric_validations/tutorial003_py39.py",
"docs_src/path_params_numeric_validations/tutorial004_an_py39.py",
"docs_src/path_params_numeric_validations/tutorial004_py39.py",
"docs_src/path_params_numeric_validations/tutorial005_an_py39.py",
"docs_src/path_params_numeric_validations/tutorial005_py39.py",
"docs_src/path_params_numeric_validations/tutorial006_an_py39.py",
"docs_src/path_params_numeric_validations/tutorial006_py39.py",
"docs_src/python_types/tutorial001_py39.py",
"docs_src/python_types/tutorial002_py39.py",
"docs_src/python_types/tutorial003_py39.py",
"docs_src/python_types/tutorial004_py39.py",
"docs_src/python_types/tutorial005_py39.py",
"docs_src/python_types/tutorial006_py39.py",
"docs_src/python_types/tutorial007_py39.py",
"docs_src/python_types/tutorial008_py39.py",
"docs_src/python_types/tutorial008b_py39.py",
"docs_src/python_types/tutorial009_py39.py",
"docs_src/python_types/tutorial009b_py39.py",
"docs_src/python_types/tutorial009c_py39.py",
"docs_src/python_types/tutorial010_py39.py",
"docs_src/python_types/tutorial013_py39.py",
"docs_src/query_params/tutorial001_py39.py",
"docs_src/query_params/tutorial005_py39.py",
"docs_src/query_params_str_validations/tutorial005_an_py39.py",
"docs_src/query_params_str_validations/tutorial005_py39.py",
"docs_src/query_params_str_validations/tutorial006_an_py39.py",
"docs_src/query_params_str_validations/tutorial006_py39.py",
"docs_src/query_params_str_validations/tutorial012_an_py39.py",
"docs_src/query_params_str_validations/tutorial012_py39.py",
"docs_src/query_params_str_validations/tutorial013_an_py39.py",
"docs_src/query_params_str_validations/tutorial013_py39.py",
"docs_src/request_files/tutorial001_03_an_py39.py",
"docs_src/request_files/tutorial001_03_py39.py",
"docs_src/request_files/tutorial001_an_py39.py",
"docs_src/request_files/tutorial001_py39.py",
"docs_src/request_files/tutorial002_an_py39.py",
"docs_src/request_files/tutorial002_py39.py",
"docs_src/request_files/tutorial003_an_py39.py",
"docs_src/request_files/tutorial003_py39.py",
"docs_src/request_form_models/tutorial001_an_py39.py",
"docs_src/request_form_models/tutorial001_py39.py",
"docs_src/request_form_models/tutorial002_an_py39.py",
"docs_src/request_form_models/tutorial002_py39.py",
"docs_src/request_forms/tutorial001_an_py39.py",
"docs_src/request_forms/tutorial001_py39.py",
"docs_src/request_forms_and_files/tutorial001_an_py39.py",
"docs_src/request_forms_and_files/tutorial001_py39.py",
"docs_src/response_change_status_code/tutorial001_py39.py",
"docs_src/response_cookies/tutorial001_py39.py",
"docs_src/response_cookies/tutorial002_py39.py",
"docs_src/response_directly/tutorial002_py39.py",
"docs_src/response_headers/tutorial001_py39.py",
"docs_src/response_headers/tutorial002_py39.py",
"docs_src/response_model/tutorial003_02_py39.py",
"docs_src/response_model/tutorial003_03_py39.py",
"docs_src/response_status_code/tutorial001_py39.py",
"docs_src/response_status_code/tutorial002_py39.py",
"docs_src/security/tutorial001_an_py39.py",
"docs_src/security/tutorial001_py39.py",
"docs_src/security/tutorial006_an_py39.py",
"docs_src/security/tutorial006_py39.py",
"docs_src/security/tutorial007_an_py39.py",
"docs_src/security/tutorial007_py39.py",
"docs_src/settings/app01_py39/config.py",
"docs_src/settings/app01_py39/main.py",
"docs_src/settings/app02_an_py39/config.py",
"docs_src/settings/app02_an_py39/main.py",
"docs_src/settings/app02_an_py39/test_main.py",
"docs_src/settings/app02_py39/config.py",
"docs_src/settings/app02_py39/main.py",
"docs_src/settings/app02_py39/test_main.py",
"docs_src/settings/app03_an_py39/config.py",
"docs_src/settings/app03_an_py39/main.py",
"docs_src/settings/app03_py39/config.py",
"docs_src/settings/app03_py39/main.py",
"docs_src/settings/tutorial001_py39.py",
"docs_src/static_files/tutorial001_py39.py",
"docs_src/sub_applications/tutorial001_py39.py",
"docs_src/templates/tutorial001_py39.py",
"docs_src/using_request_directly/tutorial001_py39.py",
"docs_src/websockets/tutorial001_py39.py",
"docs_src/websockets/tutorial003_py39.py",
"docs_src/wsgi/tutorial001_py39.py",
] ]
[tool.coverage.report] [tool.coverage.report]
@ -548,6 +320,11 @@ ignore = [
"docs_src/security/tutorial005_an_py39.py" = ["B904"] "docs_src/security/tutorial005_an_py39.py" = ["B904"]
"docs_src/security/tutorial005_py310.py" = ["B904"] "docs_src/security/tutorial005_py310.py" = ["B904"]
"docs_src/security/tutorial005_py39.py" = ["B904"] "docs_src/security/tutorial005_py39.py" = ["B904"]
"docs_src/json_base64_bytes/tutorial001_py310.py" = ["UP012"]
"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] [tool.ruff.lint.isort]
known-third-party = ["fastapi", "pydantic", "starlette"] known-third-party = ["fastapi", "pydantic", "starlette"]

83
scripts/people.py

@ -5,6 +5,7 @@ import time
from collections import Counter from collections import Counter
from collections.abc import Container from collections.abc import Container
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
from math import ceil
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any
@ -15,12 +16,63 @@ from pydantic import BaseModel, SecretStr
from pydantic_settings import BaseSettings from pydantic_settings import BaseSettings
github_graphql_url = "https://api.github.com/graphql" github_graphql_url = "https://api.github.com/graphql"
questions_category_id = "MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDAxNDM0" questions_category_id = "DIC_kwDOCZduT84B6E2a"
POINTS_PER_MINUTE_LIMIT = 84 # 5000 points per hour
class RateLimiter:
def __init__(self) -> None:
self.last_query_cost: int = 1
self.remaining_points: int = 5000
self.reset_at: datetime = datetime.fromtimestamp(0, timezone.utc)
self.last_request_start_time: datetime = datetime.fromtimestamp(0, timezone.utc)
self.speed_multiplier: float = 1.0
def __enter__(self) -> "RateLimiter":
now = datetime.now(tz=timezone.utc)
# Handle primary rate limits
primary_limit_wait_time = 0.0
if self.remaining_points <= self.last_query_cost:
primary_limit_wait_time = (self.reset_at - now).total_seconds() + 2
logging.warning(
f"Approaching GitHub API rate limit, remaining points: {self.remaining_points}, "
f"reset time in {primary_limit_wait_time} seconds"
)
# Handle secondary rate limits
secondary_limit_wait_time = 0.0
points_per_minute = POINTS_PER_MINUTE_LIMIT * self.speed_multiplier
interval = 60 / (points_per_minute / self.last_query_cost)
time_since_last_request = (now - self.last_request_start_time).total_seconds()
if time_since_last_request < interval:
secondary_limit_wait_time = interval - time_since_last_request
final_wait_time = ceil(max(primary_limit_wait_time, secondary_limit_wait_time))
logging.info(f"Sleeping for {final_wait_time} seconds to respect rate limit")
time.sleep(max(final_wait_time, 1))
self.last_request_start_time = datetime.now(tz=timezone.utc)
return self
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
pass
def update_request_info(self, cost: int, remaining: int, reset_at: str) -> None:
self.last_query_cost = cost
self.remaining_points = remaining
self.reset_at = datetime.fromisoformat(reset_at.replace("Z", "+00:00"))
rate_limiter = RateLimiter()
discussions_query = """ discussions_query = """
query Q($after: String, $category_id: ID) { query Q($after: String, $category_id: ID) {
repository(name: "fastapi", owner: "fastapi") { repository(name: "fastapi", owner: "fastapi") {
discussions(first: 100, after: $after, categoryId: $category_id) { discussions(first: 30, after: $after, categoryId: $category_id) {
edges { edges {
cursor cursor
node { node {
@ -58,6 +110,11 @@ query Q($after: String, $category_id: ID) {
} }
} }
} }
rateLimit {
cost
remaining
resetAt
}
} }
""" """
@ -120,7 +177,7 @@ class Settings(BaseSettings):
github_token: SecretStr github_token: SecretStr
github_repository: str github_repository: str
httpx_timeout: int = 30 httpx_timeout: int = 30
sleep_interval: int = 5 speed_multiplier: float = 1.0
def get_graphql_response( def get_graphql_response(
@ -158,11 +215,18 @@ def get_graphql_question_discussion_edges(
settings: Settings, settings: Settings,
after: str | None = None, after: str | None = None,
) -> list[DiscussionsEdge]: ) -> list[DiscussionsEdge]:
data = get_graphql_response( with rate_limiter:
settings=settings, data = get_graphql_response(
query=discussions_query, settings=settings,
after=after, query=discussions_query,
category_id=questions_category_id, after=after,
category_id=questions_category_id,
)
rate_limiter.update_request_info(
cost=data["data"]["rateLimit"]["cost"],
remaining=data["data"]["rateLimit"]["remaining"],
reset_at=data["data"]["rateLimit"]["resetAt"],
) )
graphql_response = DiscussionsResponse.model_validate(data) graphql_response = DiscussionsResponse.model_validate(data)
return graphql_response.data.repository.discussions.edges return graphql_response.data.repository.discussions.edges
@ -185,8 +249,6 @@ def get_discussion_nodes(settings: Settings) -> list[DiscussionsNode]:
for discussion_edge in discussion_edges: for discussion_edge in discussion_edges:
discussion_nodes.append(discussion_edge.node) discussion_nodes.append(discussion_edge.node)
last_edge = discussion_edges[-1] last_edge = discussion_edges[-1]
# Handle GitHub secondary rate limits, requests per minute
time.sleep(settings.sleep_interval)
discussion_edges = get_graphql_question_discussion_edges( discussion_edges = get_graphql_question_discussion_edges(
settings=settings, after=last_edge.cursor settings=settings, after=last_edge.cursor
) )
@ -318,6 +380,7 @@ def main() -> None:
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
settings = Settings() settings = Settings()
logging.info(f"Using config: {settings.model_dump_json()}") logging.info(f"Using config: {settings.model_dump_json()}")
rate_limiter.speed_multiplier = settings.speed_multiplier
g = Github(settings.github_token.get_secret_value()) g = Github(settings.github_token.get_secret_value())
repo = g.get_repo(settings.github_repository) repo = g.get_repo(settings.github_repository)

37
scripts/playwright/json_base64_bytes/image01.py

@ -0,0 +1,37 @@
import subprocess
import time
import httpx
from playwright.sync_api import Playwright, sync_playwright
# Run playwright codegen to generate the code below, copy paste the sections in run()
def run(playwright: Playwright) -> None:
browser = playwright.chromium.launch(headless=False)
# Update the viewport manually
context = browser.new_context(viewport={"width": 960, "height": 1080})
page = context.new_page()
page.goto("http://localhost:8000/docs")
page.get_by_role("button", name="POST /data Post Data").click()
# Manually add the screenshot
page.screenshot(path="docs/en/docs/img/tutorial/json-base64-bytes/image01.png")
# ---------------------
context.close()
browser.close()
process = subprocess.Popen(
["fastapi", "run", "docs_src/json_base64_bytes/tutorial001_py310.py"]
)
try:
for _ in range(3):
try:
response = httpx.get("http://localhost:8000/docs")
except httpx.ConnectError:
time.sleep(1)
break
with sync_playwright() as playwright:
run(playwright)
finally:
process.terminate()

3
scripts/test-cov-html.sh

@ -3,5 +3,4 @@
set -e set -e
set -x set -x
bash scripts/test.sh ${@} bash scripts/test-cov.sh --cov-report=term-missing --cov-report=html ${@}
bash scripts/coverage.sh

6
scripts/test-cov.sh

@ -0,0 +1,6 @@
#!/usr/bin/env bash
set -e
set -x
bash scripts/test.sh --cov --cov-context=test ${@}

2
scripts/test.sh

@ -4,4 +4,4 @@ set -e
set -x set -x
export PYTHONPATH=./docs_src export PYTHONPATH=./docs_src
coverage run -m pytest tests scripts/tests/ ${@} pytest -n auto --dist loadgroup tests scripts/tests/ ${@}

12
scripts/tests/test_translation_fixer/conftest.py

@ -10,9 +10,17 @@ skip_on_windows = pytest.mark.skipif(
) )
def pytest_collection_modifyitems(items: list[pytest.Item]) -> None: THIS_DIR = Path(__file__).parent.resolve()
def pytest_collection_modifyitems(config, items: list[pytest.Item]) -> None:
if sys.platform != "win32":
return
for item in items: for item in items:
item.add_marker(skip_on_windows) item_path = Path(item.fspath).resolve()
if item_path.is_relative_to(THIS_DIR):
item.add_marker(skip_on_windows)
@pytest.fixture(name="runner") @pytest.fixture(name="runner")

73
tests/test_deprecated_responses.py

@ -0,0 +1,73 @@
import warnings
import pytest
from fastapi import FastAPI
from fastapi.exceptions import FastAPIDeprecationWarning
from fastapi.responses import ORJSONResponse, UJSONResponse
from fastapi.testclient import TestClient
from pydantic import BaseModel
class Item(BaseModel):
name: str
price: float
# ORJSON
def _make_orjson_app() -> FastAPI:
with warnings.catch_warnings():
warnings.simplefilter("ignore", FastAPIDeprecationWarning)
app = FastAPI(default_response_class=ORJSONResponse)
@app.get("/items")
def get_items() -> Item:
return Item(name="widget", price=9.99)
return app
def test_orjson_response_returns_correct_data():
app = _make_orjson_app()
client = TestClient(app)
with warnings.catch_warnings():
warnings.simplefilter("ignore", FastAPIDeprecationWarning)
response = client.get("/items")
assert response.status_code == 200
assert response.json() == {"name": "widget", "price": 9.99}
def test_orjson_response_emits_deprecation_warning():
with pytest.warns(FastAPIDeprecationWarning, match="ORJSONResponse is deprecated"):
ORJSONResponse(content={"hello": "world"})
# UJSON
def _make_ujson_app() -> FastAPI:
with warnings.catch_warnings():
warnings.simplefilter("ignore", FastAPIDeprecationWarning)
app = FastAPI(default_response_class=UJSONResponse)
@app.get("/items")
def get_items() -> Item:
return Item(name="widget", price=9.99)
return app
def test_ujson_response_returns_correct_data():
app = _make_ujson_app()
client = TestClient(app)
with warnings.catch_warnings():
warnings.simplefilter("ignore", FastAPIDeprecationWarning)
response = client.get("/items")
assert response.status_code == 200
assert response.json() == {"name": "widget", "price": 9.99}
def test_ujson_response_emits_deprecation_warning():
with pytest.warns(FastAPIDeprecationWarning, match="UJSONResponse is deprecated"):
UJSONResponse(content={"hello": "world"})

51
tests/test_dump_json_fast_path.py

@ -0,0 +1,51 @@
from unittest.mock import patch
from fastapi import FastAPI
from fastapi.responses import JSONResponse
from fastapi.testclient import TestClient
from pydantic import BaseModel
class Item(BaseModel):
name: str
price: float
app = FastAPI()
@app.get("/default")
def get_default() -> Item:
return Item(name="widget", price=9.99)
@app.get("/explicit", response_class=JSONResponse)
def get_explicit() -> Item:
return Item(name="widget", price=9.99)
client = TestClient(app)
def test_default_response_class_skips_json_dumps():
"""When no response_class is set, the fast path serializes directly to
JSON bytes via Pydantic's dump_json and never calls json.dumps."""
with patch(
"starlette.responses.json.dumps", wraps=__import__("json").dumps
) as mock_dumps:
response = client.get("/default")
assert response.status_code == 200
assert response.json() == {"name": "widget", "price": 9.99}
mock_dumps.assert_not_called()
def test_explicit_response_class_uses_json_dumps():
"""When response_class is explicitly set to JSONResponse, the normal path
is used and json.dumps is called via JSONResponse.render()."""
with patch(
"starlette.responses.json.dumps", wraps=__import__("json").dumps
) as mock_dumps:
response = client.get("/explicit")
assert response.status_code == 200
assert response.json() == {"name": "widget", "price": 9.99}
mock_dumps.assert_called_once()

75
tests/test_openapi_cache_root_path.py

@ -0,0 +1,75 @@
from fastapi import FastAPI
from fastapi.testclient import TestClient
def test_root_path_does_not_persist_across_requests():
app = FastAPI()
@app.get("/")
def read_root(): # pragma: no cover
return {"ok": True}
# Attacker request with a spoofed root_path
attacker_client = TestClient(app, root_path="/evil-api")
response1 = attacker_client.get("/openapi.json")
data1 = response1.json()
assert any(s.get("url") == "/evil-api" for s in data1.get("servers", []))
# Subsequent legitimate request with no root_path
clean_client = TestClient(app)
response2 = clean_client.get("/openapi.json")
data2 = response2.json()
servers = [s.get("url") for s in data2.get("servers", [])]
assert "/evil-api" not in servers
def test_multiple_different_root_paths_do_not_accumulate():
app = FastAPI()
@app.get("/")
def read_root(): # pragma: no cover
return {"ok": True}
for prefix in ["/path-a", "/path-b", "/path-c"]:
c = TestClient(app, root_path=prefix)
c.get("/openapi.json")
# A clean request should not have any of them
clean_client = TestClient(app)
response = clean_client.get("/openapi.json")
data = response.json()
servers = [s.get("url") for s in data.get("servers", [])]
for prefix in ["/path-a", "/path-b", "/path-c"]:
assert prefix not in servers, (
f"root_path '{prefix}' leaked into clean request: {servers}"
)
def test_legitimate_root_path_still_appears():
app = FastAPI()
@app.get("/")
def read_root(): # pragma: no cover
return {"ok": True}
client = TestClient(app, root_path="/api/v1")
response = client.get("/openapi.json")
data = response.json()
servers = [s.get("url") for s in data.get("servers", [])]
assert "/api/v1" in servers
def test_configured_servers_not_mutated():
configured_servers = [{"url": "https://prod.example.com"}]
app = FastAPI(servers=configured_servers)
@app.get("/")
def read_root(): # pragma: no cover
return {"ok": True}
# Request with a rogue root_path
attacker_client = TestClient(app, root_path="/evil")
attacker_client.get("/openapi.json")
# The original servers list must be untouched
assert configured_servers == [{"url": "https://prod.example.com"}]

13
tests/test_orjson_response_class.py

@ -1,9 +1,14 @@
import warnings
from fastapi import FastAPI from fastapi import FastAPI
from fastapi.exceptions import FastAPIDeprecationWarning
from fastapi.responses import ORJSONResponse from fastapi.responses import ORJSONResponse
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
from sqlalchemy.sql.elements import quoted_name from sqlalchemy.sql.elements import quoted_name
app = FastAPI(default_response_class=ORJSONResponse) with warnings.catch_warnings():
warnings.simplefilter("ignore", FastAPIDeprecationWarning)
app = FastAPI(default_response_class=ORJSONResponse)
@app.get("/orjson_non_str_keys") @app.get("/orjson_non_str_keys")
@ -16,6 +21,8 @@ client = TestClient(app)
def test_orjson_non_str_keys(): def test_orjson_non_str_keys():
with client: with warnings.catch_warnings():
response = client.get("/orjson_non_str_keys") warnings.simplefilter("ignore", FastAPIDeprecationWarning)
with client:
response = client.get("/orjson_non_str_keys")
assert response.json() == {"msg": "Hello World", "1": 1} assert response.json() == {"msg": "Hello World", "1": 1}

20
tests/test_request_params/test_file/test_list.py

@ -37,7 +37,10 @@ def test_list_schema(path: str):
"properties": { "properties": {
"p": { "p": {
"type": "array", "type": "array",
"items": {"type": "string", "format": "binary"}, "items": {
"type": "string",
"contentMediaType": "application/octet-stream",
},
"title": "P", "title": "P",
}, },
}, },
@ -115,7 +118,10 @@ def test_list_alias_schema(path: str):
"properties": { "properties": {
"p_alias": { "p_alias": {
"type": "array", "type": "array",
"items": {"type": "string", "format": "binary"}, "items": {
"type": "string",
"contentMediaType": "application/octet-stream",
},
"title": "P Alias", "title": "P Alias",
}, },
}, },
@ -221,7 +227,10 @@ def test_list_validation_alias_schema(path: str):
"properties": { "properties": {
"p_val_alias": { "p_val_alias": {
"type": "array", "type": "array",
"items": {"type": "string", "format": "binary"}, "items": {
"type": "string",
"contentMediaType": "application/octet-stream",
},
"title": "P Val Alias", "title": "P Val Alias",
}, },
}, },
@ -338,7 +347,10 @@ def test_list_alias_and_validation_alias_schema(path: str):
"properties": { "properties": {
"p_val_alias": { "p_val_alias": {
"type": "array", "type": "array",
"items": {"type": "string", "format": "binary"}, "items": {
"type": "string",
"contentMediaType": "application/octet-stream",
},
"title": "P Val Alias", "title": "P Val Alias",
}, },
}, },

8
tests/test_request_params/test_file/test_optional.py

@ -37,7 +37,7 @@ def test_optional_schema(path: str):
"properties": { "properties": {
"p": { "p": {
"anyOf": [ "anyOf": [
{"type": "string", "format": "binary"}, {"type": "string", "contentMediaType": "application/octet-stream"},
{"type": "null"}, {"type": "null"},
], ],
"title": "P", "title": "P",
@ -109,7 +109,7 @@ def test_optional_alias_schema(path: str):
"properties": { "properties": {
"p_alias": { "p_alias": {
"anyOf": [ "anyOf": [
{"type": "string", "format": "binary"}, {"type": "string", "contentMediaType": "application/octet-stream"},
{"type": "null"}, {"type": "null"},
], ],
"title": "P Alias", "title": "P Alias",
@ -200,7 +200,7 @@ def test_optional_validation_alias_schema(path: str):
"properties": { "properties": {
"p_val_alias": { "p_val_alias": {
"anyOf": [ "anyOf": [
{"type": "string", "format": "binary"}, {"type": "string", "contentMediaType": "application/octet-stream"},
{"type": "null"}, {"type": "null"},
], ],
"title": "P Val Alias", "title": "P Val Alias",
@ -296,7 +296,7 @@ def test_optional_alias_and_validation_alias_schema(path: str):
"properties": { "properties": {
"p_val_alias": { "p_val_alias": {
"anyOf": [ "anyOf": [
{"type": "string", "format": "binary"}, {"type": "string", "contentMediaType": "application/octet-stream"},
{"type": "null"}, {"type": "null"},
], ],
"title": "P Val Alias", "title": "P Val Alias",

20
tests/test_request_params/test_file/test_optional_list.py

@ -41,7 +41,10 @@ def test_optional_list_schema(path: str):
"anyOf": [ "anyOf": [
{ {
"type": "array", "type": "array",
"items": {"type": "string", "format": "binary"}, "items": {
"type": "string",
"contentMediaType": "application/octet-stream",
},
}, },
{"type": "null"}, {"type": "null"},
], ],
@ -116,7 +119,10 @@ def test_optional_list_alias_schema(path: str):
"anyOf": [ "anyOf": [
{ {
"type": "array", "type": "array",
"items": {"type": "string", "format": "binary"}, "items": {
"type": "string",
"contentMediaType": "application/octet-stream",
},
}, },
{"type": "null"}, {"type": "null"},
], ],
@ -205,7 +211,10 @@ def test_optional_validation_alias_schema(path: str):
"anyOf": [ "anyOf": [
{ {
"type": "array", "type": "array",
"items": {"type": "string", "format": "binary"}, "items": {
"type": "string",
"contentMediaType": "application/octet-stream",
},
}, },
{"type": "null"}, {"type": "null"},
], ],
@ -301,7 +310,10 @@ def test_optional_list_alias_and_validation_alias_schema(path: str):
"anyOf": [ "anyOf": [
{ {
"type": "array", "type": "array",
"items": {"type": "string", "format": "binary"}, "items": {
"type": "string",
"contentMediaType": "application/octet-stream",
},
}, },
{"type": "null"}, {"type": "null"},
], ],

16
tests/test_request_params/test_file/test_required.py

@ -35,7 +35,11 @@ def test_required_schema(path: str):
assert app.openapi()["components"]["schemas"][body_model_name] == { assert app.openapi()["components"]["schemas"][body_model_name] == {
"properties": { "properties": {
"p": {"title": "P", "type": "string", "format": "binary"}, "p": {
"title": "P",
"type": "string",
"contentMediaType": "application/octet-stream",
},
}, },
"required": ["p"], "required": ["p"],
"title": body_model_name, "title": body_model_name,
@ -109,7 +113,11 @@ def test_required_alias_schema(path: str):
assert app.openapi()["components"]["schemas"][body_model_name] == { assert app.openapi()["components"]["schemas"][body_model_name] == {
"properties": { "properties": {
"p_alias": {"title": "P Alias", "type": "string", "format": "binary"}, "p_alias": {
"title": "P Alias",
"type": "string",
"contentMediaType": "application/octet-stream",
},
}, },
"required": ["p_alias"], "required": ["p_alias"],
"title": body_model_name, "title": body_model_name,
@ -216,7 +224,7 @@ def test_required_validation_alias_schema(path: str):
"p_val_alias": { "p_val_alias": {
"title": "P Val Alias", "title": "P Val Alias",
"type": "string", "type": "string",
"format": "binary", "contentMediaType": "application/octet-stream",
}, },
}, },
"required": ["p_val_alias"], "required": ["p_val_alias"],
@ -329,7 +337,7 @@ def test_required_alias_and_validation_alias_schema(path: str):
"p_val_alias": { "p_val_alias": {
"title": "P Val Alias", "title": "P Val Alias",
"type": "string", "type": "string",
"format": "binary", "contentMediaType": "application/octet-stream",
}, },
}, },
"required": ["p_val_alias"], "required": ["p_val_alias"],

318
tests/test_sse.py

@ -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

42
tests/test_stream_bare_type.py

@ -0,0 +1,42 @@
import json
from typing import AsyncIterable, Iterable # noqa: UP035 to test coverage
from fastapi import FastAPI
from fastapi.testclient import TestClient
from pydantic import BaseModel
class Item(BaseModel):
name: str
app = FastAPI()
@app.get("/items/stream-bare-async")
async def stream_bare_async() -> AsyncIterable:
yield {"name": "foo"}
@app.get("/items/stream-bare-sync")
def stream_bare_sync() -> Iterable:
yield {"name": "bar"}
client = TestClient(app)
def test_stream_bare_async_iterable():
response = client.get("/items/stream-bare-async")
assert response.status_code == 200
assert response.headers["content-type"] == "application/jsonl"
lines = [json.loads(line) for line in response.text.strip().splitlines()]
assert lines == [{"name": "foo"}]
def test_stream_bare_sync_iterable():
response = client.get("/items/stream-bare-sync")
assert response.status_code == 200
assert response.headers["content-type"] == "application/jsonl"
lines = [json.loads(line) for line in response.text.strip().splitlines()]
assert lines == [{"name": "bar"}]

88
tests/test_stream_cancellation.py

@ -0,0 +1,88 @@
"""
Test that async streaming endpoints can be cancelled without hanging.
Ref: https://github.com/fastapi/fastapi/issues/14680
"""
from collections.abc import AsyncIterable
import anyio
import pytest
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
pytestmark = [
pytest.mark.anyio,
pytest.mark.filterwarnings("ignore::pytest.PytestUnraisableExceptionWarning"),
]
app = FastAPI()
@app.get("/stream-raw", response_class=StreamingResponse)
async def stream_raw() -> AsyncIterable[str]:
"""Async generator with no internal await - would hang without checkpoint."""
i = 0
while True:
yield f"item {i}\n"
i += 1
@app.get("/stream-jsonl")
async def stream_jsonl() -> AsyncIterable[int]:
"""JSONL async generator with no internal await."""
i = 0
while True:
yield i
i += 1
async def _run_asgi_and_cancel(app: FastAPI, path: str, timeout: float) -> bool:
"""Call the ASGI app for *path* and cancel after *timeout* seconds.
Returns `True` if the cancellation was delivered (i.e. it did not hang).
"""
chunks: list[bytes] = []
async def receive(): # type: ignore[no-untyped-def]
# Simulate a client that never disconnects, rely on cancellation
await anyio.sleep(float("inf"))
return {"type": "http.disconnect"} # pragma: no cover
async def send(message: dict) -> None: # type: ignore[type-arg]
if message["type"] == "http.response.body":
chunks.append(message.get("body", b""))
scope = {
"type": "http",
"asgi": {"version": "3.0", "spec_version": "2.0"},
"http_version": "1.1",
"method": "GET",
"path": path,
"query_string": b"",
"root_path": "",
"headers": [],
"server": ("test", 80),
}
with anyio.move_on_after(timeout) as cancel_scope:
await app(scope, receive, send) # type: ignore[arg-type]
# If we got here within the timeout the generator was cancellable.
# cancel_scope.cancelled_caught is True when move_on_after fired.
return cancel_scope.cancelled_caught or len(chunks) > 0
async def test_raw_stream_cancellation() -> None:
"""Raw streaming endpoint should be cancellable within a reasonable time."""
cancelled = await _run_asgi_and_cancel(app, "/stream-raw", timeout=3.0)
# The key assertion: we reached this line at all (didn't hang).
# cancelled will be True because the infinite generator was interrupted.
assert cancelled
async def test_jsonl_stream_cancellation() -> None:
"""JSONL streaming endpoint should be cancellable within a reasonable time."""
cancelled = await _run_asgi_and_cancel(app, "/stream-jsonl", timeout=3.0)
assert cancelled

40
tests/test_stream_json_validation_error.py

@ -0,0 +1,40 @@
from collections.abc import AsyncIterable, Iterable
import pytest
from fastapi import FastAPI
from fastapi.exceptions import ResponseValidationError
from fastapi.testclient import TestClient
from pydantic import BaseModel
class Item(BaseModel):
name: str
price: float
app = FastAPI()
@app.get("/items/stream-invalid")
async def stream_items_invalid() -> AsyncIterable[Item]:
yield {"name": "valid", "price": 1.0}
yield {"name": "invalid", "price": "not-a-number"}
@app.get("/items/stream-invalid-sync")
def stream_items_invalid_sync() -> Iterable[Item]:
yield {"name": "valid", "price": 1.0}
yield {"name": "invalid", "price": "not-a-number"}
client = TestClient(app)
def test_stream_json_validation_error_async():
with pytest.raises(ResponseValidationError):
client.get("/items/stream-invalid")
def test_stream_json_validation_error_sync():
with pytest.raises(ResponseValidationError):
client.get("/items/stream-invalid-sync")

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save